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数组,就得靠自己不断的磨练了。