KMP:n+1次探
如你所见,这是我不知道第几次学KMP了。
推荐B站上电子科大的字符串专题。
引入:KMP是干什么的
KMP解决的是模式串P在源串T中出现次数的问题,比如模式串P为aba,源串为abababa,我们可以求出计算重叠的出现次数3,还可以求出不计算重叠的出现次数2。
next数组
- 最好不要用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\n",i-pl+2); } } /*for(int i=0;i<pl;i++){ //if(i!=0) printf(" "); //printf("%d",nex[i]); }*/ printf("%d\n",ans); } return 0; }