KMP算法初步理解
概述
KMP算法能夠解決字符串匹配問題。即S串在P串中出現了多少次的問題,時間復雜度為\(O(n+m)\)
設S處的指針為j,P處的指針為i,我們的目的是讓P[i-j+1..i]與S[1..j]完全相等。
那麽如果使用傳統的方法,一旦匹配失敗,就需要把i往後移一位,再重新匹配,時間復雜度是\(O(n*m)\)的很不劃算。
嘗試優化
很顯然,每一次匹配失敗獲得的信息在上面的樸素算法中沒有得到很好的應用。
舉個例子:
S="ababacb "
T="abababaabab"
當匹配到第六個字母時,我們發現不匹配了,但同時,這也說明[0,4]這個區間內是匹配的。按照常規的思路,我們應該將i往後移一位再重新匹配。
那麽能不能最大限度的利用此時已經得到的j?(此時的j代表S[0..j]與P[i-j+1,i]完全一致了)
KMP的策略是:在i移動的同時減少j,讓j盡量大
那麽新的j‘就要滿足在S中前j‘個字母與後j‘個字母是一樣的。
於是就可以這麽移動:
ababacb
ababacb
之後再繼續按照上述流程匹配。
此時就需要一個數組nxt[x],記錄S的一個前綴,表示在[0,x-1]這個範圍內,j->j‘的映射。
整體的代碼雛形也不難寫出了:
bool KMP(char *S,char *P,int m){ int j=-1,n=strlen(P); for(int i=0;i<n;i++){ while(~j&&S[j+1]!=P[i])j=nxt[j]; if(S[j+1]==P[i])j++; if(j==m-1)return 1; } return 0; }
考慮預處理
接下來便是nxt數組的預處理問題。
實際上與上面的代碼非常類似(相當於自己跑自己的KMP)
同樣是對於上面的S,假設我們已知nxt[0],nxt[1],nxt[2],nxt[3],那麽要怎麽求出nxt[4]和nxt[5]呢?
先看nxt[4],由nxt[3]=2,又S[2]=S[4]可知,nxt[4]=nxt[3]+1。
但是對於nxt[5]來說,因為S[nxt[4]+1]!=S[5],所以這裏要回退一步,照這樣回退下去,我們最終得到nxt[5]=0
代碼:
void get_next(char *S,int *next){ int j=-1,m=strlen(S); next[0]=0; for(int i=1;i<m;i++){ while(~j&&S[j+1]!=S[i])j=next[j];//一直回退的過程 if(S[j+1]==S[i])j++; next[i]=j; } }
大概就是這樣。
參考資料:
KMP算法詳解 by Matrix67
KMP算法初步理解