KMP算法是一種改進的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同時發現,因此人們稱它為克努特——莫里斯——普拉特操作(簡稱KMP算法)。KMP算法的關鍵是利用匹配失敗后的信息,盡量減少模式串與主串的匹配次數以達到快速匹配的目的。具體實現就是實現一個next()函數,函數本身包含了模式串的局部匹配信息。時間復雜度O(m+n)。
以上是百度百科對KMP算法的介紹,如何把實現把朴素算法變成O(m+n)的時間復雜度呢,從上面介紹可以看出來,KMP算法利用了一個next數組,所以需要預處理,下面我們就來講解KMP算法。
因為懶得畫圖還怕畫不好,所以我錄制成了視頻的格式。
Bilibili視頻:https://www.bilibili.com/video/av40137935
這是一道KMP裸題,請自行嘗試AC:傳送門
看完上面,你大致就應該清楚如何利用KMP進行線性匹配了,但是KMP算法的精髓其實不是進行簡單的串匹配,精髓應該在於next數組的應用,以及擴展的next_val數組的運用,可以快速的尋找循環節,前綴匹配等等一些復雜的字符串問題。
下面將以一道例題說明next數組的強大
HDU 1358(Period)
3 aaa 12 aabaabaabaab 0
Test case #1 2 2 3 3 Test case #2 2 2 6 2 9 3 12 4
題意:給一個長為n的字符串,問字符串的前綴是不是周期串,如果是周期串,輸出前綴的最后一個字母的位置和最短周期
應該如何思考呢,已經說明了這是一道KMP的題,用KMP進行串匹配嗎?顯然不是,那么肯定就是利用next數組的性質了,對於前i個字符,如果next[i]不等於零,那么說明在此字符串的前綴中,有一部分[0,next[i]]和本字符串[i-next[i],i]的這一部分是相同的。如果這i個字符組成一個周期串,那么錯開的一部分[next[i],i]恰好是一個循環節。(換句話說,如果滿足next[i]不等於零 && [next[i],i]是循環節這兩點,就可以說明前i個字符組成一個周期串),那么我們只需要跑一遍next數組即可,代碼:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 int n; 5 string str; 6 int nxt[1000005]; 7 8 void getnext(){ 9 int i = 0, j = -1, len = str.size(); 10 nxt[0] = -1; 11 while(i < len){ 12 if(j == -1 || str[i] == str[j]) 13 nxt[++i] = ++j; 14 else 15 j = nxt[j]; 16 } 17 } 18 19 int main(){ 20 ios_base::sync_with_stdio(false),cout.tie(0),cin.tie(0); 21 int tot = 1; 22 while(cin>>n && n){ 23 cin>>str; 24 getnext(); 25 cout << "Test case #" << tot++ << endl; 26 for(int i = 2; i <= n; i++){ 27 if(nxt[i] != 0 && i % (i - nxt[i]) == 0) 28 cout << i << " " << i/(i - nxt[i]) << endl; 29 } 30 cout << endl; 31 } 32 33 return 0; 34 }
關於KMP算法就講到這里了,這是一個很簡單的串匹配算法,但能否掌握其思想以及運用其next數組,就得靠自己不斷的磨練了。