KMP模板

先导概念

s[ ] 模式串
p[ ] 匹配串
非平凡前缀 指除了最后一个字符以外,一个字符串的全部头部组合(前面连续的部分)
非平凡后缀 指除了第一个字符以外,一个字符串的全部尾部组合
部分匹配值 非平凡前缀和非平凡后缀的最大相似长度
next[ ] 第i个元素的,前i个元素的字符串,的部分匹配值。
与模式串匹配 若在下标i处匹配失败,若模式串与匹配串有一个以s[i]结尾的相似子串,那个就可以跳过这段匹配,把匹配串直接移动到下一个需要匹配的位置(匹配串的当前下标移动到len+1处,len为相似子串的长度)。

求next数组
for(int i=2,j=0;i<=n;i++){//更新匹配串的next[]数组
    while(j&&p[i]!=p[j+1]) j=ne[j];//当前不匹配前面匹配上的作废,要退回去,所以不断next.
    if(p[i]==p[j+1]) j++;//若匹配接着记录,j表示相同区间长度
    ne[i]=j;//存一下
}

j 作为非平凡前缀的下标
i 作为非平凡后缀的下标
如果非平凡前缀非平凡后缀相同,那就在next[]数组中存下当前的缀的长度,因为下标从1开始,正好j作为前缀下标,就是部分匹配值
如果在访问新的元素时发现,此时非平凡前缀非平凡后缀不再是相同的字符串,那么就要有j=next[j],让前缀退回到上一个位置(详细解释如下):
对于部分匹配值,即前后缀相似的长度,有 p [ 1 , n e x t [ i ] ] = p [ i − n e x t [ i ] + 1 , i ] p[1,next[i]]=p[i-next[i]+1,i] p[1,next[i]]=p[inext[i]+1,i]由此可以得知,j=next[j] 就是将后缀退回到前缀的一个操作。在求next数组阶段,可以抽象成是退回到前缀的位置;在模式串匹配的阶段,可以抽象成是把前缀移动到现在后缀所在的位置(针对匹配串操作)。

模式串匹配
for(int i=1,j=0;i<=m;i++){
while(j&&s[i]!=p[j+1]) j=ne[j];//同上
	if(s[i]==p[j+1]) j++;
	if(j==n){
	    cout<<i-j<<" ";//子串的起始点下标
	    j=ne[j];//直接把前面的匹配成功的点跳过,kmp精髓
}
//n 匹配串长度
//m 模式串长度

匹配操作在上一部分有所提及,就是不断匹配,在匹配失败的当前下标,直接将满足非平凡后缀非平凡前缀的下一个元素的下标拖到当前位置,i控制模式串,j控制匹配串,也就是将新的j的下标拖到当前i的位置,从匹配串的j位置开始匹配,而不是从头重新开始。

代码模板
#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=1000010;
int n,m;
int ne[N];
char s[M],p[N];//模式串 匹配串
int main(){
    cin>>n>>p+1>>m>>s+1;//下标从1开始
    for(int i=2,j=0;i<=n;i++){//更新匹配串的next[]数组
        while(j&&p[i]!=p[j+1]) j=ne[j];//当前不匹配前面匹配上的作废,要退回去,所以不断next.
        if(p[i]==p[j+1]) j++;//若匹配接着记录,j表示相同区间长度
        ne[i]=j;//存一下
    }
    for(int i=1,j=0;i<=m;i++){
        while(j&&s[i]!=p[j+1]) j=ne[j];//同上
        if(s[i]==p[j+1]) j++;
        if(j==n){
            cout<<i-j<<" ";//子串的起始点下标
            j=ne[j];//直接把前面的匹配成功的点跳过,kmp精髓
        }
    }
    return 0;
}

string版:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
string p,s;
int n,m;
int ne[N];
int main(){
    cin>>n>>p>>m>>s;
    p="&"+p;
    s="&"+s;
    for(int i=2,j=0;i<=n;i++){
        while(j&&p[i]!=p[j+1]) j=ne[j];
        if(p[i]==p[j+1]) j++;
        ne[i]=j;
    }
    for(int i=1,j=0;i<=m;i++){
        while(j&&s[i]!=p[j+1]) j=ne[j];
        if(s[i]==p[j+1]) j++;
        if(j==n){
            cout<<i-j<<" ";
            j=ne[j];
        }
    }
    return 0;
}

题解里带过的前缀后缀均指非平凡前缀后缀,有的时候没说全。

参考
补充说明

根据评论区大哥,王道考研规定是 n e x t [ 1 ] ≡ 0 , n e x t [ 2 ] ≡ 1 next[1]≡0,next[2]≡1 next[1]0,next[2]1如果是期末考试一定要根据教材来,根据定义写next数组,虽然本质思想一样,但是还是要得分。408没考过,架不住自命题可能考,还有期末。。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值