KMP

KMP理解起来有些困难,所以说,要理解,最重要的地方就是!!

参照:KMP算法详解

名字来源#

Knuth(D.E.Knuth)&Morris(J.H.Morris)&Pratt(V.R.Pratt)发明KMP算法,因此称作KMP

没错,就是这样,我都不认识

看看这个题(From:Luogu)#

【模板】KMP字符串匹配

题目描述:#

给出两个字符串s1s2,若s1的区间[l,r]子串与s2完全相同,则称s2s1中出现了,其出现位置为l
现在请你求出s2s1中所有出现的位置。

定义一个字符串s的 border 为s的一个非s本身的子串t满足tss的后缀
对于s2,你还需要求出对于其每个前缀s的最长 border t的长度


这题,朴素暴力?
期望复杂度O(n+m)

但是有的毒瘤良心出题人会给你卡成O(nm)
TLE必不可少

有些地方失配了,暴力的算法会到下一位继续从头开始匹配,而有些是明显不用匹配的
怎么处理呢?

KMP闪亮登场

KMP算法:可以实现复杂度为O(m+n)#

如何简化了时间复杂度呢?

这个算法充分利用了目标字符串s_2的性质(比如里面部分字符串的重复性,即使不存在重复字段,在比较时,实现最大的移动量)#

放一张图来展示KMP算法的精髓(来自某本书里):

假定在匹配的过程中正在比较文本串T(Tababaabbaabbaabab)*位置的字符和模板串P(Pabbaaba)的最后一个字符,发现二者不同(称为失配)

这时,朴素算法会把模板串右移一位,重新比较abbaaba的第一个字符和文本串!!位置的字符。

我们已经知道灰色部分就是abbaab,可以直接利用模板串本身的特性判断出右移一位一定不是匹配

同理,右移两位或者三位也不行,但是右移四位是有可能的

这个时候,需要比较*处的字符和abbaaba的第三个字符

我们创建一个next数组,其中nexti表示的是前i的字符组成的这个子串最长的相同前缀后缀的长度

例如字符串ababa的相同前缀后缀有abaa,那么其中最长的就是aba

至于为什么要选最长的相同前缀后缀,其实是一个贪心的思想

我们要跳next数组,必须要保证不能错过任何一个可以匹配的位置,并且跳跃的距离要尽量大

假若我们随便选取一个相同前缀后缀,那么会错过一些地方

--------------------------------------------------------------------分割线--------------------------------------------------------------------------

所以,Pnext数组如下(i表示当前的位置):

那么如何用next数组去匹配呢?

我们再用i表示当前A串要匹配的位置(即还未匹配),j表示当前B串匹配的位置(同样也是还未匹配)

首先从头开始匹配,直到失配

然后再直接将j跳转到next[j]的位置

相信都能写出代码了qwq

然后给出一份代码:

inline void find(char P[],char T[]){
    getnext();
    j=0;                                            //当前结点编号,初始为0号结点
    for(int i=0;i<n;++i){                           //文本串当前指针
        while(j>0&&P[j]!=T[i]) j=nxt[j];            //如果不匹配,则将利用kmp数组往回跳直到匹配
        if(P[j]==T[i]) ++j;                         //匹配成功就把对应位置++
        if(j==m){printf("%d\n",i-m+2);j=nxt[j];}    //找到了,输出位置,然后继续
    }
}

然后就是计算next数组

就比如说Pababax,求next5next6

此时我们已经求出了next4=2,所以很明显next[1,2]=next[3,4]

然后,我们又知道了next3=next5,所以,next[1,3]==next[3,5]next5=next4+1

那么next6呢?

显然它不能由next5转移过来,因为next3不等于next6

接着便看能不能由next[next5]转移过来,但是也不行

然后是next[next[next5]]……套娃

最后推到next1也不行,所以next6=0

神似find()函数,其实就是PP自我匹配的过程

最后给出代码:

inline void getnext(){
    nxt[0]=nxt[1]=0;                        //边界初始化
    j=0;
    for(int i=1;i<m;++i){                   //自己匹配自己
        while(j>0&&P[i]!=P[j]) j=nxt[j];    //找到最长的前后缀重叠长度
        nxt[i+1]=P[i]==P[j]?++j:0;          //不相等的情况,即无前缀能与后缀重叠,直接赋值位0(注意是给下一位,因为匹配的是下一位失配的情况)
    }
}

注意:#

1.next0next1都是1
2.这是一个从前往后的推导(类似于dp)
3.若以某一位结尾的子串不存在相同的前缀和后缀,这个位的F置为0

应该解释的很清楚了吧qwq

下面放上完整代码:

#include "bits/stdc++.h"
using namespace std;
const int N=1000005;
char T[N],P[N];
int nxt[N];
int m,n,j;
inline void getnext(){
    nxt[0]=nxt[1]=0;                       //边界初始化
    j=0;
    for(int i=1;i<m;++i){                   //自己匹配自己
        while(j>0&&P[i]!=P[j]) j=nxt[j];    //找到最长的前后缀重叠长度
        nxt[i+1]=P[i]==P[j]?++j:0;          //不相等的情况,即无前缀能与后缀重叠,直接赋值位0(注意是给下一位,因为匹配的是下一位失配的情况)
    }
}
inline void find(){
    getnext();
    j=0;                                            //当前结点编号,初始为0号结点
    for(int i=0;i<n;++i){                           //文本串当前指针
        while(j>0&&P[j]!=T[i]) j=nxt[j];            //如果不匹配,则将利用kmp数组往回跳直到匹配
        if(P[j]==T[i]) ++j;                         //匹配成功就把对应位置++
        if(j==m){printf("%d\n",i-m+2);j=nxt[j];}    //找到了,输出位置,然后继续
    }
}
int main(){
    scanf("%s %s",T,P);
    m=strlen(P),n=strlen(T);
    find();
    for(int i=1;i<=m;++i) cout<<nxt[i]<<' ';
    return 0;
}

习题:Loj103

作者:Into_qwq

出处:https://www.cnblogs.com/into-qwq/p/13179559.html

版权:本作品采用「qwq」许可协议进行许可。

posted @   Into_qwq  阅读(191)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示