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!='\n')
    {
        s1[++len1]=c;
        c=getchar();
    }
    c=getchar();
    while(c!='\n')
    {
        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

posted @ 2018-05-06 21:47  露迭月  阅读(229)  评论(0编辑  收藏  举报