【KMP】【字符串】KMP字符串匹配算法 學習筆記
一、簡介
KMP是由Knuth、Morris和Prat發明的字符串匹配算法,它的時間復雜度是均攤\(O(n+m)\)。其實用Hash也可以做到線性,只不過Hash存在極其微小的難以避免的沖突。於是就有了KMP。
KMP算法用作模式串匹配,可以找到一個長為\(m\)的模式串在一個長為\(n\)的主串中出現的次數和位置。
二、樸素算法(\(O(nm)\))
實際上是枚舉模式串在主串中出現的位置,然後一一比對,出現錯誤就停止,移動到下一位。連續匹配成功\(m\)次就說明模式串在主串中出現了。
其實\(O(nm)\)是一個比較寬的上界,隨機數據遠沒有這麽慢。不過給出主串S="aaaaaaaaaaa"
"aaab"
,就得每次匹配到最後才知道掛掉了。那麽我們既然已經知道這樣會掛掉,為什麽還要重復匹配呢?這就是KMP要解決的問題。
三、KMP算法
0.註意事項
KMP算法中,字符數組下標是從1開始而不是0,這樣會比較方便,在匹配的時候直接調用nxt數組就不用-1了,而且很直觀。
1.nxt數組(next)
nxt數組是KMP算法的核心。
nxt數組nxt[i]中存的是後綴字符與前綴字符相同的個數,且後綴字符不是前綴字符,也就是不能完全匹配自己。
對於ababcab
而言,它的nxt數組是
nxt[1] | nxt[2] | nxt[3] | nxt[4] | nxt[5] | nxt[6] | nxt[7] |
a | ab | aba | abab | ababc | ababca | ababcab |
0 | 0 | 1 | 2 | 0 | 1 | 2 |
沒有後綴與前綴相同 | 沒有後綴與前綴相同 | 前綴"a"=後綴"a" |
前綴"ab"=後綴"ab" 註意不能完全匹配自己 |
沒有後綴與前綴相同(失配直接=0) | 前綴"a"=後綴"a" | 前綴"ab"=後綴"ab" |
看上去很簡單。但是它怎麽求呢?當然,如果遞推掌握的比較好,可以知道,如果T[i+1]==T[nxt[i]+1]
,那nxt[i+1]=nxt[i]+1
。而失配了怎麽辦,是我們接下來要解決的問題。
2.nxt數組的作用
在上面我們提到了,KMP算法是減少了樸素算法的重復匹配,均攤下來每個字符只匹配了一次。在上面的表格中,我們看到了nxt數組處理出了模式串後綴與前綴匹配數。當我們用模式串匹配主串,在後綴失配時,在模式串裏與後綴相同
3.nxt失配的求法
可以看出,求nxt數組,就是在用模式串匹配自己,且不匹配第一位。當我們發現T[i+1]與T[nxt[i]+1]
失配時,比nxt[i]要次一點但是仍然與T[i]所在後綴相同的前綴就是nxt[nxt[i]]了。就是我們相當於退而求其次,“貪心地”按長度從大到小找一個最能匹配上後綴的前綴。
匹配不到的情況
比如說
ababababc ↑
如果匹配到第8+1位時失配了,這時就去找第nxt[8]+1位,(nxt[8]=6)也就是第6+1位。此時發現還是不行,繼續找第nxt[6]+1位……直到找到nxt[1]=0,退出循環,再看看第一位是否匹配,如果匹配,nxt[9]=1,否則nxt[9]=0。
重新匹配到的情況
abcaba ↑
這裏匹配到第5+1位發現失配,去找nxt[5]+1=2+1,仍然不行,但是nxt[2]=0了,此時退出循環,發現T[1]=T[6],所以nxt[6]=1。
其實情況還有很多很多種,這裏只是列舉了幾種,想讓nxt數組的求法變得更加形象一些。
四、KMP代碼
#include<cstdio> #include<cstring> char s[1000100]; char t[1000100]; int nxt[1000100]; int f[1000100]; int main() { scanf("%s",s+1); scanf("%s",t+1); int n=strlen(s+1); int m=strlen(t+1); for(int i=2,j=0;i<=m;++i) { while(j&&t[i]!=t[j+1]) j=nxt[j]; if(t[j+1]==t[i]) ++j; nxt[i]=j; } for(int i=1,j=0;i<=n;++i) { while(j&&(j==m||s[i]!=t[j+1])) j=nxt[j]; if(t[j+1]==s[i]) ++j; f[i]=j; if(f[i]==m) printf("%d\n",i-m+1); } for(int i=1;i<=m;++i) printf("%d ",nxt[i]); return 0; }
【KMP】【字符串】KMP字符串匹配算法 學習筆記