#include<iostream>
using namespace std;
#include<string>
class KMP{
public:
KMP(int n,string s0,string p0):s(s0),p(p0),length(n){
next=new int[n];
CalNext();
}
~KMP() {delete[] next;}
void TargetPosition(){//直接输出位置
int j=0;
int k=s.size();
for(int i=0;i<k;){
while(p[j]==s[i]&&i<k&&j<length){
++i;++j;
}
if(j==0) ++i;//如果发现p[0]!=s[i],头一个就匹配不了了,往后看++i;
if(j==length){//如果j==length 说明匹配成功
cout<<i-j+1<<endl;
}
if(j>=1)//如果j!=0,不管匹配成功 还是匹配失败,都要继续跟s[i]匹配,那就看
//next[j-1] 前后缀最长下表是多少 +1继续匹配
j=next[j-1]+1;
}
OutputNext();
return;
}
void OutputNext(){
for(int i=0;i<length;++i)
cout<<next[i]+1<<' ';
return ;
}
private:
void CalNext(){//求出前缀最长下标 存入next;
next[0]=-1;
for(int i=1;i<length;++i){
int j=next[i-1];
while(j>=0&&p[j+1]!=p[i])
j=next[j];
if(p[j+1]==p[i])
next[i]=j+1;
else
next[i]=-1;
}
return;
}
int *next;
int length;
string s;//目标串
string p;//模式串
};
int main(void){
string s;
string p;
cin>>s;
cin>>p;
KMP kmp(p.size(),s,p);
kmp.TargetPosition();
}
①求string的长度用 size()。
string p;
p.size() 是p的长度.
②第一次错误点:当p[0]!=s[i]的时候 没有进行++i,导致死循环。
②差点就错误:i的最终取值是i<s.size(),而不是i<=s.size()-p.size();
因为最终匹配,仍然需要匹配到最后一位,所以i别越界就可以了。
一、next数组的求解
我们这里的next数组 存的是最长前后缀匹配的最大下标。
前后缀的长度必然要小于字符串的长度,
所以next[0]必然<0,所以next[0]=-1;相当于没有前后缀相等,最大下标为数组的前一个位置。
然后对i=1到i=length-1的求解用到递归求法。
如果不用递归求法当然也可以,不过费时。
通过代码来看求解方法:
void CalNext(){//求出前缀最长下标 存入next;
next[0]=-1;//第一位没有前后缀赋值为-1;
for(int i=1;i<length;++i){//从第二位求到最后一位,利用递推关系
int j=next[i-1];//j表示第i位前面最长前后缀的下标
while(j>=0&&p[j+1]!=p[i])//如果最长前后缀的后一位刚好等于第i位,说明此时第i位前后缀
//就是j+1;
j=next[j]; //不然的话,继续往下看能不能找到满足前后缀又有后一位刚好等于i的
//实在没有 那么j==-1时停止, if(p[-1+1]==p[i]) next[i]=0;
if(p[j+1]==p[i])
next[i]=j+1;
else
next[i]=-1;
}
return;
}注意next数组的值最小为-1,所以不用怕它超出。
二、用前后缀可以跳转的原理
123456789
abcdefdaX······(目标串)
abcdefdaaetgs;
假设从第2号位置,从头比是有效的,
则必有,bcdefda==abcdefd
也就是说 abcdefda 的前后缀最长长度应当是7,如果是7 从头开始比才能保证,这些比较一直到X是有效的,然而如果不是7,必然无效。
怎么知道最长是不是7? 我们在求next函数的时候就已经保证了,我们已经保证了next数组是最长的长度,所以用next数组从前后缀最长的地方比较才是有意义的,否则必然是没有意义的。