如你所见,这是我不知道第几次学KMP了。
推荐B站上电子科大的字符串专题。
引入:KMP是干什么的
KMP解决的是模式串P在源串T中出现次数的问题,比如模式串P为aba,源串为abababa,我们可以求出计算重叠的出现次数3,还可以求出不计算重叠的出现次数2。
next数组
- [x] 最好不要用next命名next数组,某些OJ会报错
前(后)缀和真前(后)缀:字符串s前i个(i<=strlen(s))字符为其前缀,i!=strlen(s)时为真前缀,后缀和真后缀同理。
next[i]表示模式串P以i为尾的这个前缀,最长的公共真前缀和真后缀长度,例如abcabc,next[1:6]={0,0,0,1,2,3}
求next数组
假设我们已经知道next[1:i-1],求next[i]。
设last=next[i-1],则p[1:last]等于p[i-last:i-1],即模式串长度为i-1的前缀,前last个与后last个相同,且last最大,那么我们只需要检测p[i]与p[last+1]是否相等,相等就是last+1,否则要在这last个(p[i-last:i-1],即p[1:last])里面找,更新last=next[last],继续检测p[i]与p[last+1]是否相等,如此循环直到last=0,p[i]=p[1],即为1,否则为0。
那么只需要设置next[1]=0,循环求即可。
真正的KMP
通过求next数组,我们发现:next数组的作用是当前面的匹配好了,而下一个匹配不到时,更新一个更小的来匹配,取代了重新匹配,来加速匹配。
KMP的过程与求next的过程几乎完全相同。
我们设last为匹配到T[i-1]时当前已经匹配的个数,即当last=strlen( P )时,匹配成功,现在求匹配T[i]时的last。
我们已经匹配了last个,p[1:last]等于t[i-last:i-1](是不是很熟悉),如果p[i]与t[last+1]相同,last++,否则跳转到前面,last=next[last],尝试更小的匹配,直到完全不能匹配。
代码
作用:输出有多少次匹配,可重叠。
由于通常的输入从0开始,这个代码的next数组从0到n-1,每一位比正常的next数组少1。
回到开头的问题,如果想求的匹配不重叠,后面每当匹配到,last=0即可。
#include<bits/stdc++.h>
using namespace std;
int n,last;
char t[1000020],p[1002000];
int nex[1002000];
int main(){
scanf("%d",&n);
while(n--){
int ans=0;
scanf("%s%s",p,t);
int pl=strlen(p),tl=strlen(t);
nex[0]=last=-1;
for(int i=1;i<pl;i++){
while(last>-1&&p[i]!=p[last+1]){
last=nex[last];
}
if(p[i]==p[last+1]){
last++;
}
nex[i]=last;
}
last=-1;
for(int i=0;i<tl;i++){
while(last>-1&&t[i]!=p[last+1]){
last=nex[last];
}
if(t[i]==p[last+1]) last++;
if(last+1==pl){
ans++;
last=nex[last];
//printf("%d
",i-pl+2);
}
}
/*for(int i=0;i<pl;i++){
//if(i!=0) printf(" ");
//printf("%d",nex[i]);
}*/
printf("%d
",ans);
}
return 0;
}