zoukankan      html  css  js  c++  java
  • KMP算法

    KMP算法

    简介

    Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP算法”,常用于在一个文本串S内查找一个模式串P 的出现位置,这个算法由Donald Knuth、Vaughan Pratt、James H. Morris三人于1977年联合发表,故取这3人的姓氏命名此算法。

    一、暴力做法的损失

    • 定义:称待匹配串(长串)为S1,匹配串(短串)为S2。n为S1长度,m为S2长度。

    暴力做法:

    1. 枚举S1
    2. S1已枚举到第(i)位,枚举S2,看以第(i)位为开头的S1与S2能否配对

    最坏复杂度:O(N*M)

    这样做,事实上我们损失了十分多的信息

    比如说

    S1="abababc"

    S2="ababc"

    在S1的第1位的时候,在第5号失配了,显然,这时候我们把S2的左边直接挪到第3位是最优的。

    我们如何把损失的信息利用起来呢


    二、定义串的模式值NEXT

    • 定义:(next[i])代表S2在第(i)位(与S1的某一位)失配时,将自身跳转到(next[i])位置继续进行匹配。
    • 特别的,(next[i])==(-1)代表S2在S1的某一位已经无法匹配,将匹配S1的下一位

    三、求串的模式值NEXT

    • 求法:

    (一). 若(k==-1)或者(s[j-1]==s[k])

    (1) 当前位(j)满足条件①且不满足条件②,(next[j]=k+1)

    (2) 否则,(next[j]=next[k+1])

    (二). 令(k=next[k])继续寻找边界

    • 约定:

    (1) (next[0]=-1)

    (2) 约束条件①:
    在当前位(j)前面的字串(不包括(j))中,存在(s[0)~(k]=s[(j-k-1))~((j-1)])(k∈(0,j-1)),且(k)取最大值

    (3) 约束条件②(是依托于①的): (S2[j]=S2[k+1])

    • 看到这肯定一脸萌币(并没有暴露什么),没事下面有解释,对着上面看就知道为什么了(注意(一)(二)在最后面,所以哪怕觉得奇怪也要按顺序看哦)。

    • 解释:

    (1) 约束条件①:

    感性理解:即为在j前面的子串中前缀与后缀相同两个串最长的那个

    举例:

    ABABAC

    对C前面的ABABA,前缀ABA等于后缀ABA,k=2

    (2) 约束条件②:

    假设已满足约束条件①(否则无意义)

    感想理解:在(j)加入(j)前面的子串后,前后缀相等的两个串延长了1位

    举例:

    ABABAB

    对B(第三个)前面的ABABA,有(S2[5]=S2[3]),即第二个B等于了第三个B

    (3) 约束条件的存在意义

    假设已经(S1)匹配到第(i)位,(S2)匹配到第(j)

    (S1)只是一个片段,(S2)的蓝色部位为相等的前后缀。)

    假设此时(i!=j)(失配),我们应该跳到哪里比呢?

    按照之前的做法我们要先比较一下(j)(k+1)是否相等

    ㈠ 如果不相等

    显然,(i)(k+1)是否相等是不确定的,我们把(S2)挪到(k+1)

    继续配对,即为(next[j]=k+1)

    ㈡ 如果相等呢?

    这时候,我们将(S2)前面一个蓝色的块块放大,如(S2^{'})

    显然,(i)(在值上(i=j=k+1))与(k^{'}+1)是否相等是不确定的,我们把(S2)挪到(k^{'}+1)继续配对,即为(next[j]=next[k+1])

    (4) 求法中(一)(二)大点的存在意义

    为什么放在最后说,因为要确保已有的信息基础。

    我们要求解(next),要确保在(j)前面已经形成了前后缀相等的串,而(s[j-1])==(s[k])不就是前提条件吗?


    四、参考代码

    #include <cstdio>
    using namespace std;
    const int N=1000010;
    char s1[N],s2[N];
    int next[N],t[N];
    int main()
    {
        freopen("KMP.in","r",stdin);
        freopen("KMP.out","w",stdout);
        char c=getchar();
        int len1=-1,len2=-1;
        while(c!='
    ')
        {
            s1[++len1]=c;
            c=getchar();
        }
        c=getchar();
        while(c!='
    ')
        {
            s2[++len2]=c;
            c=getchar();
        }
        int k=-1,j=0;
        next[0]=-1;
        while(j<=len2)
        {
            if(k==-1||s2[k]==s2[j])
            {
                k++,j++;
                if(s2[k]!=s2[j])
                    next[j]=k;
                else
                    next[j]=next[k];
            }
            else
                k=next[k];
        }
        k=0,j=0;
        int cnt=0;
        while(j<=len1)
        {
            while(k!=-1&&s1[j]!=s2[k])
                k=next[k];
            if(k==len2)
                t[++cnt]=j,k=next[k]==-1?0:next[k];
            else
                j++,k++;
        }
        for(int i=1;i<=cnt;i++)
            printf("%d ",t[i]+1-len2);
        return 0;
    }
    

    2018.4.27

  • 相关阅读:
    引用数据类型:字符串和数组
    java流程控制
    java基本类型-八大基本数据类型(4类8种)
    Java标识符
    Java注释
    虚拟环境管理virtualenv
    pipenv管理模块和包
    有关线程的说法?
    TCP三次握手的序列号和确认号的计算
    jenkins的安装
  • 原文地址:https://www.cnblogs.com/butterflydew/p/8999847.html
Copyright © 2011-2022 走看看