基友前兩天參加了阿里的實習生面試,問了個問題,就是關於字串的子串搜尋的問題。想想實現方式無非就是兩層迴圈,但是 java 中是有現成實現的,於是我就去查查源碼,看看 java 語言怎麼實現這個的,發現也就是差不多的意思。
java.lang 包中 String 類 有幾個 indexOf() 函數,我要尋找的是 indexOf(String str) 這個的具體實現,發現了
public int indexOf(String str) {return indexOf(str, 0);}
然後 F3 繼續找,
public int indexOf(String str, int fromIndex) {return indexOf(value, offset, count, str.value, str.offset, str.count,fromIndex);}
這個調用應該就是演算法的實現了,繼續 F3
/** * Code shared by String and StringBuffer to do searches. The source is the * character array being searched, and the target is the string being * searched for. * * @param source * the characters being searched. * @param sourceOffset * offset of the source string. * @param sourceCount * count of the source string. * @param target * the characters being searched for. * @param targetOffset * offset of the target string. * @param targetCount * count of the target string. * @param fromIndex * the index to begin searching from. */static int indexOf(char[] source, int sourceOffset, int sourceCount,char[] target, int targetOffset, int targetCount, int fromIndex) {if (fromIndex >= sourceCount) {return (targetCount == 0 ? sourceCount : -1);}if (fromIndex < 0) {fromIndex = 0;}if (targetCount == 0) {return fromIndex;}char first = target[targetOffset];int max = sourceOffset + (sourceCount - targetCount);for (int i = sourceOffset + fromIndex; i <= max; i++) {/* Look for first character. */if (source[i] != first) {while (++i <= max && source[i] != first);}/* Found first character, now look at the rest of v2 */if (i <= max) {int j = i + 1;int end = j + targetCount - 1;for (int k = targetOffset + 1; j < end&& source[j] == target[k]; j++, k++);if (j == end) {/* Found whole string. */return i - sourceOffset;}}}return -1;}
注意這個函數是靜態函數,是String and StringBuffer公用的一個工具方法,具體演算法原理代碼中很顯而易見。
又查閱了一些資料,目前子串搜尋的方法有下面幾種,
KMP演算法, BM演算法,Sunday演算法
其中無論是簡單程度還是效率排序均為下面:
Sunday > BM > KMP
Sunday 演算法的核心思想如下(轉自百度百科):
字串模式比對中超越BF、KMP和BM的演算法sunday演算法的概念如下:Sunday演算法是Daniel M.Sunday於1990年提出的一種比BM演算法搜尋速度更快的演算法。其核心思想是:在匹配過程中,模式串並不被要求一定要按從左向右進行比較還是從右向左進行比較,它在發現不匹配時,演算法能跳過儘可能多的字元以進行下一步的匹配,從而提高了匹配效率。假設在發生不匹配時S[i]≠T[j],1≤i≤N,1≤j≤M。此時已經匹配的部分為u,並假設字串u的長度為L。1。明顯的,S[L+i+1]肯定要參加下一輪的匹配,並且T[M]至少要移動到這個位置(即模式串T至少向右移動一個字元的位置)。
圖1 Sunday演算法不匹配的情況分如下兩種情況:(1) S[L+i+1]在模式串T中沒有出現。這個時候模式串T[0]移動到S[T+i+1]之後的字元的位置。2。
圖2 Sunday演算法移動的第1種情況(2)S[L+i+1]在模式串中出現。這裡S[L+i+1]從模式串T的右側,即按T[M-1]、T[M-2]、…T[0]的次序尋找。如果發現S[L+i+1]和T中的某個字元相同,則記下這個位置,記為k,1≤k≤M,且T[k]=S[L+i+1]。此時,應該把模式串T向右移動M-k個字元的位置,即移動到T[k]和S[L+i+1]對齊的位置。3。
圖3 Sunday演算法移動的第2種情況依次類推,如果完全符合了,則匹配成功;否則,再進行下一輪的移動,直到主串S的最右端結束。該演算法最壞情況下的時間複雜度為O(N*M)。對於短模式串的匹配問題,該演算法執行速度較快。Sunday演算法思想跟BM演算法很相似,在匹配失敗時關注的是文本串中參加匹配的最末位字元的下一位字元。如果該字元沒有在匹配串中出現則直接跳過,即移動步長= 匹配串長度+1;否則,同BM演算法一樣其移動步長=匹配串中最右端的該字元到末尾的距離+1。現舉個例子來說明:比如:匹配串:abcbczdxzc模式串:zbcac這裡我們看到b-a沒有對上,我們就看匹配串中的z在模式串的位置,然後對齊。匹配串:abcbczdxzc模式串: zbcac如果模式串中的沒有那個字元的話就跳過去。匹配串:abcbcedxzcs模式串:zbcace不在模式串中出現,那麼我們就匹配串:abcbcedxzcs模式串: zbcac
附一個Sunday演算法的 C++ 實現(原文連結:http://hi.baidu.com/azuryy/item/8a50f54a2f8c72e51381dad3)
/* Sunday.h */class Sunday {public: Sunday(); ~Sunday();public: int find(const char* pattern, const char* text);private: void preCompute(const char* pattern);private: //Let's assume all characters are all ASCII static const int ASSIZE = 128; int _td[ASSIZE] ; int _patLength; int _textLength;};源檔案/* Sunday.cpp */Sunday::Sunday(){}Sunday::~Sunday(){}void Sunday::preCompute(const char* pattern){ for(int i = 0; i < ASSIZE; i++ ) _td[i] = _patLength + 1; const char* p; for ( p = pattern; *p; p++) _td[*p] = _patLength - (p - pattern);}int Sunday::find(const char* pattern, const char* text){ _patLength = strlen( pattern ); _textLength = strlen( text ); if ( _patLength <= 0 || _textLength <= 0) return -1; preCompute( pattern ); const char *t, *p, *tx = text; while (tx + _patLength <= text + _textLength) { for (p = pattern, t = tx; *p; ++p, ++t) { if (*p != *t) break; } if (*p == 0) return tx-text; tx += _td[tx[_patLength]]; } return -1;}簡單測試下:int main(){ char* text = "blog.csdn,blog.net"; char* pattern = "csdn,blog" ; Sunday sunday; printf("The First Occurence at: %d\n",sunday.find(pattern,text)); return 1;}
注,上述演算法中數組_td[],是用於記錄 pattern 中每個字元的位置