#include<bits/stdc++.h>
using namespace std;
/*
abcabcde中开始len指向a,j指向b
a[j]!=a[len],且len无路可退
让Next[j] = len = 0,同时j++指向c
其他的同理,当j指向3(也就是第二个a时)
a[j] == a[len](此时j=3,len=0)
让Next[j] = ++len后j++(此时j=4,len=1,Next[j]==1)
j再指向4,也就是第二个b,此时len=1;
a[j] = a[len]
同理Next[j] == 2
...
当j == 6时,a[j] = d,a[len] = a(len==3)
两者不相等
len = Next[len-1] == Next[2] ==0
再进行循环判断
更新完成后Next数组为
a b c a b c d e
0 0 0 1 2 3 0 0
*/
void KMP(string a,int n,int Next[]){ //a是待求子串,n是待求子串的长度,Next数组
int len=0;
int j=1;
Next[0]=0;
while(j<n){
if(a[j]==a[len]){
/*当两者相等时,说明j的位置和前面对应前缀的位置相等,
所以先让len的长度加1,再让j的长度加1*/
Next[j++]=++len;
}
else{
/*当对应位置不相等时,当len>0(说明len还有路可退)
让len返回他上一个位置的next数组的值
当对应位置不相等,且len==0(len无路可退),直接让Next[j]=len再让j++*/
if(len>0){
len=Next[len-1];
}
else{
Next[j]=len;
j++;
}
}
}
/*上面求得是每一位的最大前缀和,Next数组是代表前一位的,
所以用for循环遍历,将所有位置后移,再让Next[0]=-1*/
for(int i=n-1;i>0;i--){
Next[i]=Next[i-1];
}
Next[0]=-1;
}
int main(){
int Next[100]={0};
string b="abcdefabcdeabcabcdefg";
string a="abcabcde";
int n=a.length();
KMP(a,n,Next); //求Next数组
int m=b.length();
int i=0;
int j=0;
while(i<n&&j<m){ //当两个字符串任意一个走到头了,就跳出循环
if(i==-1||a[i]==b[j]){ //注意不要忘了i==-1的条件
i++;
j++;
}
else{
i=Next[i];
}
}
if(i==n){
cout<<j-i; //输出比对到的第一个数的下标,有些题下标是从1开始,这里就需要加1
}
else{
cout<<"-1"; //没找到输出-1
}
}
注意事项:
1.Next数组首字母要大写,否则在Devc++里会有定义冲突
2.KMP函数里最后遍历的范围是下标为字符串长度减一的位置到下标为1的位置,不是下标到零的位置,实际循环长度是n-1不是n
3.遍历是i--不是i++,注意不要笔误
4.主函数查找过程中不要忘记i==-1的情况,如果等于负一直接让i和j同时++,避免陷入死循环
5.有些题目的数组是从下标为1的位置开始,最后输出i-j+1(匹配成功后第一个下标的位置),从零开始就是i-j
6.判断语句不要直接用字符串长度直接判断(如n<a.length())这样可能会造成n小于0时循环无法正常进行,应该先让一个变量等于a.length(),再让这个变量和n比较
if(n<a.length())
n++; //有可能当n小于0时发生错误,跳出循环
//应写成
int m=a.length();
if(n<m)
n++;
当时间限制比较紧的题目中也可以直接删除KMP函数中的遍历后移部分
for(int i=n-1;i>0;i--){
Next[i]=Next[i-1]
}
Next[0]=-1; //这一部分
然后再后面的查找过程中加上几句判断语句
if(i>0)
i=Next[i-1];
else
i=-1;
也可以时间目的,并且用时更短