鑒於原理有點復雜,詳細原理可以參考這篇文章http://blog.csdn.net/v_july_v/article/details/7041827
本文直接從結論入手,應付考試和競賽足夠了。
設T為目標串("aaabbbaabbabcabcabbaba"),pat為模式串("aabbabc")。
這是模式串的next數組:
j(下標) | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
pat | a | a | b | b | a | b | c |
next[j] | -1 | 0 | 1 | 0 | 0 | 1 | 0 |
KMP算法:
j=0時,next[j]=-1。表示下一趟匹配比較時,模式串的第-1個字符與目標串上次失配的位置對齊。(其實等同於第0個字符與目標串上次失配的下一個位置對齊),模式串需要移動到posT-next[j]的位置。(posT為T串下標)
j=1時,next[j]=0。表示下一趟匹配比較時,模式串的第0個字符與目標串上次失配的位置對齊。模式串需要移動到posT-next[j]個位置。
j=2時,next[j]=1。表示下一趟匹配比較時,模式串的第1個字符與目標串上次失配的位置對齊。模式串需要移動到posT-next[j]個位置。
以此類推......
那么下面只要求出next數組即可,next數組是如何形成的?
從下標0開始,一直到lengthP-1為止(lengthP是模式串的長度),每次尋找該下標前面前綴與后綴相同的最大長度(前后綴不包括前面整個字符串,即起始位置和終止位置都相等的那一個字符串,下面有解釋)。
j=0時,字符a前面無字符,故標記-1;
j=1時,字符a前面有字符a,但由於“前后綴不包括前面整個字符串”的規則,所以並沒有相同的前后綴一說,故標記為0。
j=2時,字符b前面有字符aa,前后綴相同的字符串為a,故標記為前后綴的長度1。
以此類推......(PS:前后綴的計算都是從左到右的)
其實這樣說是為了方便理解next數組,而next數組的實際形成也是一次KMP算法,它也是一個匹配字符串的過程,用后綴去匹配前綴的過程。
代碼如下:
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 string T; 5 string pat; 6 void getNext(int next[],int lengthP){//lengthP為模式串P的長度 7 int j=0,k=-1;//j為P串的下標,k用來記錄該下標對應的next數組的值 8 next[0]=-1;//初始化0下標下的next數組值為-1 9 while(j<lengthP){ //對模式串進行掃描 10 if(k==-1||pat[j]==pat[k]){//串后綴與前綴沒有相等的子串或者此時j下標下的字符與k下的字符相等。 11 j++;k++; 12 next[j]=k;//設置next數組j下標的值為k 13 }else 14 k=next[k];//縮小子串的范圍繼續比較 15 } 16 } 17 18 int kmp(int k,int next[]){ 19 int posP=0,posT=k;//posP和posT分別是模式串pat和目標串T的下標,先初始化它們的起始位置 20 int lengthP=pat.length();//lengthP是模式串pat長 21 int lengthT=T.length();//lengthT是目標串T長 22 while(posP<lengthP&&posT<lengthT){//對兩串掃描 23 if(posP==-1||pat[posP]==T[posT]){//對應字符匹配 24 posP++;posT++; 25 }else 26 posP=next[posP];//失配時,用next數組值選擇下一次匹配的位置 27 } 28 if(posP<lengthP) return -1; 29 else return posT-lengthP;//匹配成功 30 } 31 32 int main(){ 33 T="aaabbbaabbabcabcabbaba"; 34 pat="aabbabc"; 35 int lengthP=pat.length(); 36 int next[lengthP]={0}; 37 getNext(next,lengthP); 38 int pos=kmp(0,next); 39 cout<<pos<<endl; 40 cout<<"next[]:"; 41 for(int i=0;i<lengthP;i++){ 42 cout<<next[i]<<" "; 43 } 44 return 0; 45 }