本篇文章直接跳过蛮力算法以及一些简单背景,着重讨论Next数组的意义以及其是如何工作的,并对如何求Next数组做详细记录。
1.背景
1.1 KMP算法的应用:KMP算法用来解决模式串匹配问题。
1.2 为什么要用KMP算法:普通的蛮力算法时间复杂度为O(n*m),而KMP为O(n+m)。
2.KMP算法思想
2.1 KMP算法的思想:(称T为目标串,P为待查找字串)
- 目标串T的 i 指针不必回溯!
- 通过对待查找字串P进行分析得出当每次字符不匹配时P串应如何移动
2.2 Next数组介绍:Next数组中存的是如果P[ j ] != T[ i ] 时,j 应该等于多少。即P串要向右移动多少位,此时 i 不变。Next[ j]代表前 j – 1 个字符的最大前缀和最大后缀相同的字符数,这样当P[ j ] != T[ i ]时,将j = Next[ j ] ,由于前缀与后缀相等,故此时前 j 个字符仍是匹配的。
2.3 代码实例:
int KMP(string a,string b){
BuildNext(b);//用来求Next数组
int n = a.length();
int m = b.length();
int i = 0,j = 0;
while(j < m && i < n){
if(j < 0 || a[i] == b[j])
i++,j++;
else j = Next[j];
}
return i-j;
}
3.Next数组的求法
3.1 与KMP算法本身类似的思想:将P串自己与自己匹配。
3.2 代码实例:
void BuildNext(string P){
int m = P.length();
int t = Next[0] = -1;
int j = 0;
while(j < m-1){
if(t < 0 || P[j] == P[t]){
j++;
t++;
Next[j] = t;//待优化
}else t = Next[t];
}
}
3.3 解析:这里用到一个小技巧,令Next[0] = -1,叫做字符通配符,即可以和所有字符匹配,这样就可以在P没有前缀和T[i]匹配时P从头开始比较。
4.优化
4.1 对Next数组进行优化:当待匹配子串中有较多相同字符时,以上方法还是会进行很多次无谓的比较,比如T : 00100010与P :00010进行匹配,当知道T中的第三位1与P中的第三位0不匹配时,上述方法会将P串向右移动一位,结果仍是不匹配,但是我们已经知道了0和1不等,这样还有必要每次都向后移一位吗?为什么不直接移动3位?
4.2 实现方法:我们在求前 i – 1 个元素前缀与后缀相等个数的时候,是不考虑第P[ i ]的,但是如果要是最大相等前缀的后一个字符和P[ i ]相等的话,不就表明即使你移动了之后,当前字符依然没变,也就依然不能匹配,所以要再接着移吗?
4.3 代码实例:
void BuildNext(string P){
int m = P.length();
int t = Next[0] = -1;
int j = 0;
while(j < m-1){
if(t < 0 || P[j] == P[t]){
j++;
t++;
Next[j] = P[j] != P[t]?t:Next[t];
}else t = Next[t];
}
}
5.完整代码
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 1e5;
int Next[maxn];
void BuildNext(string P){
int m = P.length();
int t = Next[0] = -1;
int j = 0;
while(j < m-1){
if(t < 0 || P[j] == P[t]){
j++;
t++;
Next[j] = P[j] != P[t]?t:Next[t];
}else t = Next[t];
}
}
int KMP(string a,string b){
BuildNext(b);
int n = a.length();
int m = b.length();
int i = 0,j = 0;
while(j < m && i < n){
if(j < 0 || a[i] == b[j])
i++,j++;
else j = Next[j];
}
return i-j;
}
int main()
{
string a,b;
getline(cin,a);
getline(cin,b);
int pos;
if(b.length() > a.length()) pos = -1;
else pos = KMP(a,b);
if(pos >= 0) printf("%d\n",pos);
else puts("NO");
}