感谢kuangbin巨巨的讲解
KMP解决的是一个字符串匹配的问题
什么是字符串的匹配问题呢?
比如,字符串S “ABCDABCDABDE”,字符串P “ABCDABD”
现在问P在S中出现了几次
暴力计算 时间复杂度n*m
这个方法的时间复杂度太过于大了,有没有好一点的方法呢,可不可以每一次不要再从开头开始匹配呢
我们发现在P中一些字符是没有必要再比较的比如,我们可以下一次比较的时候直接从S[4]开始比较,这样就快了很多
那我们现在的问题就是如何求出这一次S数组应该从哪一位开始比较,我们引入数组 next[]
假如现在s[i]!=p[j],则s[i-j,i-1]==p[0,j-1],我们现在需要移动字符串P k位,使s[i-k,i-1]==p[0,k-1],这个式子等价于p[0,k-1]==p[j-k,j-1]
所以k的取值只和字符串P有关,而我们数组next中存的就是k,所以数组next只和p有关
next数组:
含义:next[k]为满足p[0,k-1]==p[j-k,j-1]的最大k值,即当前字符以前的字符串中有多长的相同前缀和后缀。
设置数组next代码如下:
对于next数组还有一些深入的理解,可能在做一些题的时候可以用到。
1.沿着next数组向前或者向后,得到的字符串是此处的前缀和后缀字符串,比如
比如next[11]=8就代表着字母B之前的前缀字符串就是P[0-8]
2.周期性字符串:if(n%(n-next[n])==0) 则循环节长度为n-next[n]
专题练习
1.POJ - 3461
ac代码
#include <iostream>
#include <cstdio>
using namespace std;
char T[1000005];
char W[10005];
int Next[10005];
void kmp_pre(int a)
{
int i,j;
j=Next[0]=-1;
i=0;
while (i<a) {
while (j!=-1&&W[i]!=W[j]) {
j=Next[j];
}
Next[++i]=++j;
}
}
int main()
{
int t;
scanf("%d",&t);
while (t--) {
scanf("%s%s",W,T);
int a=strlen(W);
int b=strlen(T);
kmp_pre(a);
int i,j,Ans;
i=j=Ans=0;
while (i<b&&j<a) {
if (j==-1||W[j]==T[i]) {
i++;
j++;
}
else
j=Next[j];
if (j==a) {
Ans++;
j=Next[j];
}
}
printf("%d
",Ans);
}
}
这个题比较基础,就是按着模版打就好了,第一次交没有过因为,将所有的strlen没有保存下来,每次都在while里重新计算导致原本O(n)变成了O(n^2)
2.HDU-1711
AC代码
#include <iostream>
#include <cstdio>
using namespace std;
int M[10004];
int N[1000004];
int Next[10004];
void kmp_pre(int n)
{
int i,j;
j=Next[0]=-1;
i=0;
while (i<n) {
while (j!=-1&&M[i]!=M[j]) {
j=Next[j];
}
Next[++i]=++j;
}
}
int main()
{
int t;
scanf("%d",&t);
while (t--) {
int len_m,len_n;
scanf("%d%d",&len_n,&len_m);
for (int i=0; i<len_n; i++) {
scanf("%d",&N[i]);
}
for (int i=0; i<len_m; i++) {
scanf("%d",&M[i]);
}
kmp_pre(len_m);
int Ans=-1;
int i=0,j=0;
while (i<len_n&&j<len_m) {
if (j==-1||N[i]==M[j]) {
i++;
j++;
}
else
j=Next[j];
if (j==len_m) {
Ans=i;
break;
}
}
if (Ans!=-1) {
printf("%d
",Ans-len_m+1);
}
else
printf("%d
",Ans);
}
}