(能夠轉載,但請註明出處!)html
下面是有關學習KMP的參考網站算法
http://blog.csdn.net/yaochunnian/article/details/7059486數組
http://blog.csdn.net/v_JULY_v/article/details/6111565ide
http://blog.csdn.net/v_JULY_v/article/details/6545192函數
http://blog.csdn.net/oneil_sally/article/details/3440784學習
http://billhoo.blog.51cto.com/2337751/411486優化
先說說next數組的含義:網站
next[i]就是前面長度爲i的字符串前綴和後綴相等的最大長度,也即索引爲i的字符失配時的前綴函數。spa
下面幾個版本的next函數,除了next[0]不一樣外(版本一中爲-1,版本二中爲0),其他無差異.net
一:KMP算法模板
版本一:
//求str對應的next數組 void getNext(char const* str, int len) { int i = 0; next[i] = -1; int j = -1; while( i < len ) { if( j == -1 || str[i] == str[j] ) //循環的if部分 { ++i; ++j; //修正的地方就發生下面這4行 if( str[i] != str[j] ) //++i,++j以後,再次判斷ptrn[i]與ptrn[j]的關係 next[i] = j; //以前的錯誤解法就在於整個判斷只有這一句。 else next[i] = next[j]; //這裏實際上是優化了後的,也能夠還是next[i]=j //當str[i]==str[j]時,若是str[i]匹配失敗,那麼換成str[j]確定也匹配失敗, //因此不是令next[i]=j,而是next[i] = next[j],跳過了第j個字符, //即省去了沒必要要的比較 //非優化前的next[i]表示前i個字符中前綴與後綴相同的最大長度 } else //循環的else部分 j = next[j]; } } //在目標字符串target中,字符str出現的個數 //n爲target字符串的長度,m爲str字符串的長度 int kmp_match(char *target,int n,char *str,int m){ int i=0,j=0; //i爲target中字符的下標,j爲str中字符的下標 int cnt=0; //統計str字符串在target字符串中出現的次數 while(i<=n-1){ if(j<0||target[i]==str[j]){ i++; j++; } else{ j=next[j]; //當j=0的時候,suffix[0]=-1,這樣j就會小於0,因此一開始有判斷j是否小於0 } //str在target中找到匹配 if(j==m){ cnt++; j=next[j]; } } return cnt; } //在目標字符串target中,若存在str字符串,返回匹配成功的第一個字符的位置 int kmp_search(char *target,int n,char *str,int m){ int i=0,j=0; //i爲target中字符的下標,j爲str中字符的下標 int cnt=0; //統計str字符串在target字符串中出現的次數 while(i<n && j<m){ if(j<0||target[i]==str[j]){ i++; j++; } else{ j=suffix[j]; //當j=0的時候,suffix[0]=-1,這樣j就會小於0,因此一開始有判斷j是否小於0 } } if(j>=m) return i-m; else return -1; }
版本二(算法導論):
//這裏的next和前面同樣,next[i]就是前面長度爲i的字符串前綴和後綴相等的長度, //即索引爲i的字符失配時的前綴函數 void getNext(char *str,int m){ memset(next,0,sizeof(next)); next[1]=0; int k=0; for(int i=2;i<=m;i++){ while(k>0 && str[k]!=str[i-1]) k=next[k]; if(str[k]==str[i-1]) k++; next[i]=k; } } //n爲target字符串的長度,m爲str字符串的長度,統計str在target中出現的個數 int match(char *target,int n,char * str,int m){ int k=0,cnt=0; for(int i=0;i<n;i++){ while(k>0 && str[k]!=target[i]) k=next[k]; if(str[k]==target[i]) k++; if(k==m){ cnt++; k=next[k]; } } return cnt; } //n爲target字符串的長度,m爲str字符串的長度 //若存在str字符串,返回匹配成功的第一個字符的位置 int match(char *target,int n,char * str,int m){ int k=0,cnt=0; for(int i=0;i<n;i++){ while(k>0 && str[k]!=target[i]) k=next[k]; if(str[k]==target[i]) k++; if(k==m){ return i-m+1; } } return -1; }
某大神的模板(其實和算法導論同樣):
#define KMP_GO(X) while(k>0 && P[k]!=X[i]) k=next[k];if(P[k]==X[i])k++ //求字符串P在T中出現的次數 int kmp_match(char*T,char*P){ int n,m,next[10010],i,k,c; n=strlen(T);m=strlen(P); next[1]=k=0; for(i=1;i<m;i++){ KMP_GO(P); next[i+1]=k;//這裏i表示的是字符的索引,對應的長度i+1 } k=c=0; for(i=0;i<n;i++){ KMP_GO(T); if(k==m){ c++; k=next[k]; } } return c; }
二:KMP最小循環節、循環週期:
定理:假設S的長度爲len,則S存在最小循環節,循環節的長度L爲len-next[len],子串爲S[0…len-next[len]-1]。
(1)若是len能夠被len - next[len]整除,則代表字符串S能夠徹底由循環節循環組成,循環週期T=len/L。
(2)若是不能,說明還須要再添加幾個字母才能補全。須要補的個數是循環個數L-len%L=L-(len-L)%L=L-next[len]%L,L=len-next[len]。
理解該定理,首先要理解next數組的含義:next[i]表示前面長度爲i的子串中,前綴和後綴相等的最大長度。
如:abcdabc
index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
char |
a |
b |
c |
d |
a |
b |
C |
|
next |
-1 |
0 |
0 |
0 |
0 |
1 |
2 |
3 |
如對於a,ab,abc,abcd,很明顯,前綴和後綴相同的長度爲0
對於長度爲5的子串abcda,前綴的a和後綴的a相同,長度爲1
對於長度爲6的子串abcdab,前綴的ab和後綴的ab相同,長度爲2
接下來舉幾個例子來講明最小循環節和循環週期:
爲方便說明,先設字符串的長度爲len,循環子串的長度爲L
1.
s0s1s2s3s4s5 ,next[6]=3
即s0s1s2=s3s4s5
很明顯可知:循環子串爲s0s1s2,L=len-next[6]=3,且能被len整除。
2.
s0s1s2s3s4s5s6s7 ,next[8]=6
此時len-next[8]=2 ,即L=2
由s0s1s2s3s4s5=s2s3s4s5s6s7
可知s0s1=s2s3,s2s3=s4s5,s4s5=s6s7
顯然s0s1爲循環子串
3.
s0s1s2s3s4s5s6 ,next[7]=4
此時len-next[7]=3,即L=3
由s0s1s2s3=s3s4s5s6
可知s0s1=s3s4,s2s3=s5s6
從而可知s0s1s2=s3s4s5,s0=s3=s6
即若是再添加3-4%3=2個字母(s1s2),那麼獲得的字符串就能夠由s0s1s2循環3次組成
這個定理能夠這麼理解:
http://www.cnblogs.com/oyking/p/3536817.html
對於一個字符串,如abcd abcd abcd,由長度爲4的字符串abcd重複3次獲得,那麼必然有原字符串的前八位等於後八位。
也就是說,對於某個字符串S,長度爲len,由長度爲L的字符串s重複R次獲得,當R≥2時必然有S[0..len-L-1]=S[L..len-1],字符串下標從0開始
那麼對於KMP算法來講,就有next[len]=len-L。此時L確定已是最小的了(由於next的值是前綴和後綴相等的最大長度,即len-L是最大的,那麼在len已經肯定的狀況下,L是最小的)。
若是必定仔細證實的話,請看下面:
(參考來自:http://www.cnblogs.com/wuyiqi/archive/2012/01/06/2314078.html,有所改動)
k m x j i
由上,next【i】=j,兩段紅色的字符串相等(兩個字符串徹底相等),s[k....j]==s[m....i]
設s[x...j]=s[j....i](xj=ji)
則可得,如下簡寫字符串表達方式
kj=kx+xj;
mi=mj+ji;
由於xj=ji,因此kx=mj,以下圖所示
k m a x j i
設s[a…x]=s[x..j](ax=xj)
又由xj=ji,可知ax=xj=ji
即s[a…i]是由s[a…x]循環3次得來的。
並且看到沒,此時又重複上述的模型,s[k…x]=s[m…j],能夠一直遞推下去
最後能夠就能夠遞推出文章開頭所說的定理了。
最後再舉兩個相關例子
abdabdab len:8 next[8]:5
最小循環節長度:3(即abd) 須要補的個數是1 d
ababa len:5 next[5]:3
最小循環節長度:2(即ab) 須要補的個數是1 b