zoukankan      html  css  js  c++  java
  • KMP

    KMP理解起来有些困难,所以说,要理解,最重要的地方就是(largeoxed{画图})!!

    参照:KMP算法详解

    名字来源

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

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

    看看这个题(From:Luogu)

    【模板】KMP字符串匹配

    题目描述:

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

    定义一个字符串(s)的 border 为(s)的一个非(s)本身的子串(t)满足(t既是)s(的前缀,又是)s的后缀
    对于(s_2),你还需要求出对于其每个前缀(s')的最长 border (t')的长度


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

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

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

    KMP闪亮登场

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    所以,(P)(next)数组如下((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
    ",i-m+2);j=nxt[j];}    //找到了,输出位置,然后继续
        }
    }
    

    然后就是计算(next)数组

    就比如说(P)(ababax),求(next_5和next_6)

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

    然后,我们又知道了(next_3=next_5),所以,(next[1,3]==next[3,5])(next_5=next_4+1)

    那么(next_6)呢?

    显然它不能由(next_5)转移过来,因为(next_3)不等于(next_6)

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

    然后是(next[next[next_5]])……套娃

    最后推到(next_1)也不行,所以(next_6=0)

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

    最后给出代码:

    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.(next_0)(next_1)都是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
    ",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

  • 相关阅读:
    [算法] 带权图
    哥德巴赫猜想 ——— 极限算法(你要是能写出比我用时还短的代码算我输)
    详解 位运算
    内存对齐模式 —— 原理讲解
    C语言 文件操作
    指针与数组
    队列的实现
    堆栈的实现
    线性表 详讲及使用
    树莓派
  • 原文地址:https://www.cnblogs.com/RadestionAdtinium/p/13179559.html
Copyright © 2011-2022 走看看