本文大部分摘自szy學長的ppt《string》中的KMP部分。
%%%膜拜szy大神orz
1.概述
KMP 算法是用來解決單模匹配問題的一種算法。
如果暴力的進行單模匹配,那么時間復雜度為O(nm)。
KMP 算法通過對模式串的預處理優化了復雜度。
2.求next數組
為了敘述方便,設模式串長度為n,主串長度為m。
將模式串稱為s1,主串稱為s2,下標從1 開始。
我們首先對模式串預處理出一個next 數組。
next[i] 表示最大的x,滿足s1[1 : x - 1] 是s1[1 : i - 1] 的后綴。
這個數組記錄了失配時,模式串指針移動的目標位置。
求next[i] 時,考慮維護一個位置j,初始時為next[i - 1]。
如果s1[j] = s1[i -1],那么next[i] 顯然等於j + 1。
如果s1[j] != s1[i - 1],那么此時需要將j 向前移動到next[j] 的位置。
一直將j 移動到next[j] 的位置,直到j = 0 或s1[j] = s1[i - 1]。
此時next[i] 等於j + 1。
由於next 是最長公共前后綴,因此在j 的移動過程中一定會經過next[i] - 1 的位置。
1 void getnx() 2 { 3 nx[1]=0; 4 for(int i=2,j=1;i<=n;) 5 { 6 nx[i]=j; 7 while(j&&s1[j]!=s1[i])j=nx[j]; 8 j++,i++; 9 } 10 }
3.匹配
在匹配過程中,設在主串中匹配到位置i,模式串中匹配到位置j。
首先如果s2[i] = s1[j],當前位置匹配成功,此時可以把i 和j 同時移動到下一個位置。
否則發生失配,需要進行調整,我們將j 置為next[j],然后繼續匹配。
同樣由於next 是最長公共前后綴,因此在j 的移動過程中不會跳過可能匹配的位置。
並且模式串中j 之前的部分一定可以匹配。
void kmp() { for(int i=1,j=1;i<=m;) { while(j&&s1[j]!=s2[i])j=nx[j]; if(j==n) { // 此時找到了一個能夠匹配的位置 j=nx[j]; } else j++,i++; } }
可以發現兩部分代碼有很大相似之處。
其實可以把求next 數組過程看做用模式串與自身匹配的過程。
4.時間復雜度
在求next 的過程中,j 指針每向后移動一步,i 指針就會向后移動一步。
而j 指針每延next 移動一次,就會向前移動大於等於一步。
由於i 指針會向后移動O(n) 次,因此j 指針也只會向后移動O(n) 次,因此向前同樣最多移動O(n) 次。
因此求next 數組部分復雜度為O(n)。
與之類似,可以得出匹配過程的復雜度為O(m)。
因此KMP 算法的總復雜度為O(n + m)。
尾聲:
總之,KMP算法是處理字符串匹配問題的一大利器。
搭配字符串上的DP可以說是......咳咳......很有趣......
(下篇高能預告)