數據結構—KMP算法
KMP算法用於解決兩個字符串匹配的問題,但更多的時候用到的是next數組的含義,用到next數組的時候,大可能是題目跟先後綴有關的 。數組
首先介紹KMP算法:(假定next數組已經學會,後邊next數組會在介紹)數據結構
上圖T爲主鏈,P爲模板鏈,要求P在T中是否出現,出現就返回位置。優化
樸素算法會順序遍歷,比較第一次的時候p[0]處失配,而後向後移動繼續匹配。數據量大的時候這麼作確定是不可行的。因此這裏就會有KMP算法!在一次失配以後,KMP算法認爲這裏已經失配了,就不能在比較一遍了,而是將字符串P向前移動(已匹配長度-最大公共長度)位,接着繼續比較下一個位置。這裏已匹配長度好理解,可是最大公共長度是什麼吶?這裏就出現了next數組,next數組:next[i]表示的是P[0-i]最大公共先後綴公共長度。這裏確定又有人要問了,next數組這麼奇葩的定義,爲何就能算出來字符串須要向後平移幾位纔不會重複比較吶?spa
上圖中紅星標記爲例,此時在p[4]處失配,已匹配長度爲4,而next[3]=2(也就是babaa中先後綴最大公共長度爲0),這時候向後平移已匹配長度-最大公共長度=2位,P[0]到達原來的P[2]的位置,若是隻平移一位,P[0]到達p[1]的位置這個位置沒有匹配此次操做就是無用功因此浪費掉了時間。已知前綴後綴中的最大公共長度,下次位移的時候直接把前綴位移到後綴上面直接產生匹配,這樣直接從後綴的後一位開始比較就能夠了。這樣將一下無心義的位移過濾掉剩去了很多的時間。指針
下面講解next數組經過語言進行實現:code
void makeNext(const char P[],int next[]) { int q,k; int m=strlen(P); next[0]=0; for (q=1,k=0;q<m;++q) { while(k>0&&P[q]!=P[k]) k = next[k-1]; /* 這裏的while循環很很差理解! 就是用一個循環來求出先後綴最大公共長度; 首先比較P[q]和P[K]是否相等若是相等的話說明已經K的數值就是已匹配到的長的; 若是不相等的話,那麼next[k-1]與P[q]的長度,爲何吶?由於當前長度不合適 了,不能增加模板鏈,就縮小看看next[k-1] 的長度可以不能和P[q]匹配,這麼一直遞歸下去直到找到 */ if(P[q]==P[k])//若是當前位置也能匹配上,那麼長度能夠+1 { k++; } next[q]=k; } }
上面KMP算法的理論部分已經講解完了,下面解釋語言實現:blog
int kmp(const char T[],const char P[],int next[]) { int n,m; int i,q; n = strlen(T); m = strlen(P); makeNext(P,next); for (i=0,q=0;i<n;++i) { while(q>0&&P[q]!= T[i]) q = next[q-1]; /* 這裏的循環就是位移以後P的前幾個字符能個T模板匹配 */ if(P[q]==T[i]) { q++; } if(q==m)//若是能匹配的長度恰好是T的長度那麼就是找到了一個能匹配成功的位置 { printf("Pattern occurs with shift:%d\n",(i-m+1)); } } }
另外KMP算法還能夠進一步的優化:遞歸
/*************************KMP模板****************************/ int next[101];//優化後的失配指針,記住這裏next要比P多一位,由於P到m-1便可,可是next還要計算出m的失配指針 int next2[101];//next2用來保存KM指針,是爲優化next的失配指針,next保存的是優化以後的失配指針 char T[1000];//待匹配串 char P[100];//模板串 void makeNext(char *P, int *next) { int m = strlen(P); next[0]=next[1]=0; next2[0]=next2[1]=0; for(int i=1;i<m;i++) { int j = next2[i]; //這裏直接找出當前位置上一步的next,和上一步不斷保存K值是一個道理 while(j && P[i]!=P[j]) j = next2[j]; next2[i+1]=next[i+1]=(P[i]==P[j])?j+1:0; //既然i+1的失配位置指向j+1,可是P[i+1]和P[j+1]的內容是相同的 //因此就算指針從i+1跳到j+1去,仍是不能匹配,因此next[i+1]直接=next[j+1] if(next[i+1]==j+1 && P[i+1]==P[j+1]) //這一步就是進行優化,若是下一個位置還能和當前位置匹配那麼直接更新next數組的值 next[i+1]=next[j+1]; } } void kmp(char *T, char *P, int *next) //找到全部匹配點 { int n = strlen(T); int m = strlen(P); int j = 0; for(int i = 0; i < n; i++) { while(j && T[i] != P[j]) j = next[j];//向前移動了多少 inext(T[i] == P[j]) j++; inext(j == m) printnext("%d\n", i - m + 1); } } /*************************KMP模板****************************/
擴展KMP算法字符串
這裏稍稍的提一點,時間倉促,我也尚未完全的理解……嘖嘖嘖
理論部分若是我講的很差別噴,求T與S[i,n-1]的最長公共前綴extend[i],要求出全部extend[i](0<=i<n)。下面從模板中講解:
const int maxn=100010; //字符串長度最大值 int next[maxn],ex[maxn]; //ex數組即爲extend數組 /* extend數組,extend[i]表示T與S[i,n-1]的最長公共前綴,要求出全部extend[i](0<=i<n)。 */ /* 設輔助數組next[i]表示T[i,m-1]和T的最長公共前綴長度 */ //預處理計算next數組 void GETNEXT(char *str) { int i=0,j,po,len=strlen(str); next[0]=len;//初始化next[0] /* 0到n-1組成的字符串和str的最長公共前綴長度固然是len了 */ while(str[i]==str[i+1]&&i+1<len)//計算next[1],也就是第一位的時候能匹配多少 i++; next[1]=i; po=1;//初始化po的位置 for(i=2;i<len;i++) { if(next[i-po]+i<next[po]+po)//第一種狀況,能夠直接獲得next[i]的值 /* 若是不如以前計算過的最長的長就直接賦值爲最長的那個 */ next[i]=next[i-po]; else//第二種狀況,要繼續匹配才能獲得next[i]的值 /* 比最長的還短,那麼後面的就不是到了,因此要繼續匹配 */ { j=next[po]+po-i; if(j<0)j=0;//若是i>po+next[po],則要從頭開始匹配 while(i+j<len&&str[j]==str[j+i])//計算next[i] j++; next[i]=j; po=i;//更新po的位置 } } } //計算extend數組 void EXKMP(char *s1,char *s2) { int i=0,j,po,len=strlen(s1),l2=strlen(s2); GETNEXT(s2);//計算子串的next數組 while(s1[i]==s2[i]&&i<l2&&i<len)//計算ex[0] i++; ex[0]=i; po=0;//初始化po的位置 for(i=1;i<len;i++) { if(next[i-po]+i<ex[po]+po)//第一種狀況,直接能夠獲得ex[i]的值 ex[i]=next[i-po]; else//第二種狀況,要繼續匹配才能獲得ex[i]的值 { j=ex[po]+po-i; if(j<0)j=0;//若是i>ex[po]+po則要從頭開始匹配 while(i+j<len&&j<l2&&s1[j+i]==s2[j])//計算ex[i] j++; ex[i]=j; po=i;//更新po的位置 } } }