KMP算法入门

一.KMP算法概念


来看上图,当比较串与模板串比较到AB处不相同时,原始做法是模板串前进一个单位,比较串回溯到开始重新比较,这样做效率低下,很多重复部分。我们要明确一点:模板串与比较串此时的1-2和3-4部分是相同的!所以如果此时串中开头与结尾的一部分是对称相同的,我们可以直接把比较串移动到C处与A比较(而模板串不回溯),这样就能提高效率!

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

以下关系递推式转自:点击打开链接

假设主串:S: S[1] S[2] S[3] ……S[n]

模式串:T: T[1] T[2] T[3]…..T[m]

现在我们假设主串第i 个字符与模式串的第j(j<=m)个字符‘失配’后,主串第i 个字符与模式串的第k(k<j)个字符继续比较,此时就有S[i] != T[j]

主串:   S[1]...S[i-j+1]...S[i-1]S[i]...

                    ||(匹配)   ||    ≠

模式串:            T[1]...  T[j-1] T[j]

由此,可以得到关系式如下

  T[1]T[2]T[3]...T[j-1] = S[i-j+1]...S[i-1]

由于S[i] != T[j],接下来S[i]将与T[k]继续比较,则模式串中的前k-1咯字符串必须满足下列关系式,并且不可能存在k'>k满足下列关系式:

T[1]T[2]T[3]...T[k-1] = S[j-k+1]S[j-k+2]...S[i-1] (k<j)

也就是说:

主串:  S[1]...S[i-k+1]S[i-k+2]...S[i-1]S[i]...

                    ||        ||         ||    ?(待比较)

模式串:           T[1]      T[2]...  T[k-1] T[k]

现在可以把前面的关系综合总结如下:

S[1]...S[i-j+1]...S[i-k+1]S[i-k+2]...S[i-1]S[i]...

            ||          ||       ||           ||   ≠

           T[1]...    T[j-k+1] T[j-k+2]...   T[j-1] T[j]

                         ||       ||            ||    ?

                        T[1]     T[2] ...     T[k-1] T[k]

现在唯一的任务就是如何求k了,通过一个next函数求。

二.KMP算法前期准备

1.最长前缀概念: 最长前缀是说以第一个字符开始,但是不包含最后一个字符。

2.最长后缀概念: 最长前缀是说以最后一个字符开始,但是不包含第一个字符。

3.next[]数组:保存每一个字符处的最大最长前缀与最长后缀相等长度,即图中的len(3=4), 用于比较时移动和回溯,目的是实现最大移动量!(其实next[i]表示的是1~i位置中最长的前后缀相同长度,他的值一方面表示长度,另一方面表示这一最大的前缀末坐标。)

4.KMP算法:利用next数组实现比较串的移动与回溯,模板串不回溯。

三.如何求next数组

1.已知:next数组的值是最大的相同前缀与后缀长度,也可以表示这个最大长度表示的前缀末座标。若现在已知next[j],如果我们比较S[next[j]+1]==S[j+1](前缀后缀同时后移一个单位比较),则就表示在原来基础上最大相同前后缀又多了一:next[j+1]=next[j]+1;

2.若S[next[j]+1]!=S[j+1]:


即A!=B,此时应该在A之前继续找BF段的相同最大前缀,我们可以直接k--寻找,但是我们可以直接回溯i=next[i],找他之前的最大前缀末坐标,如果此时S[i]==S[j+1];则next[j+1]=next[i]+1;证明:因为此时B=C末坐标,而之前有CD=EF,是对称的!所以F=C-1,而B=C末即C=BF!出现最大前后缀。

综上:

  • M[k] == M[i] 此时 next(i+1)=k+1=next(i)+1

  • M[k] ≠ M[i] 此时只能在M位置k之前的字符串中匹配,假设j=next(k),如果此时M[j]==M[i],则next(i+1)=j+1,否则重复此步骤,直到字符串长度为0为止

代码:

int next[1000];
void getNext(string s,int l)//这里next是从0开始的,与字符串坐标一一对应,表示该0~下标处的最大长度。-1表示没有,0表示1,1表示2,以此类推!
{
    next[0]=-1;            //所以回溯的时候,回溯到的是最大前缀的末坐标k,而我们要比较的是k+1与j+1,所以坐标要后移一位于j+1比较,出现了s[k+1]
    int k=-1;               //但是next等于的还是最大后缀的末下标,与字符串下标对应。
    for(int i=1;i<=l-1;i++)
    {
        while(k>-1&&s[k+1]!=s[i])
            k=next[k];//回溯
        if(s[k+1]==s[i])
            k=k+1;//next[j+1]=next[j]+1;
        next[i]=k;
    }
}
int next[1000];
int getNext(char *s,int l)//这个版本,next数组是从1开始的,字符串是从0开始的,这里next 0就表示没有,数字表示长度
{
    int j=0;             //所以回溯的时候,回溯到的比如长度是2,在字符串里就是第三个字符,自动跳到了最大前缀末坐标的下一个,不用k+1了
    int k=-1;
    next[0]=-1;
    while(j<l)
    {
        if(k==-1||s[j]==s[k])
            next[++j]=++k;//next[j+1]=next[j]+1;
        else
            k=next[k];//回溯
    }
}

4.KMP算法模板(求第一次出现位置+出现次数)


1.
void getNext(string s,int l)
{
    next[0]=-1;
    int k=-1;
    for(int i=1;i<=l-1;i++)
    {
        while(k>-1&&s[k+1]!=s[i])
            k=next[k];
        if(s[k+1]==s[i])
            k=k+1;
        next[i]=k;
    }
}
int KMP(string s1,int l1,string s2,int l2)
{
    int k=-1;
    for(int i=0;i<l1;i++)
    {
        while(k>-1&&s2[k+1]!=s1[i])
            k=next[k];
        if(s2[k+1]==s1[i])
            k=k+1;
        if(k==l2-1)
            return i-l2+1;
    }
    return -1;
}
void KMP(char *str,int l1,char *s,int l2)
{
    int k=-1;
    for(int i=0;i<l1;i++)
    {
        while(k>-1&&s[k+1]!=str[i])
            k=next[k];
        if(s[k+1]==str[i])
            k=k+1;
        if(k==l2-1)
        {
            sum++;
            k=next[k];
        }
    }
}
2. 点击打开链接


猜你喜欢

转载自blog.csdn.net/qq_40772692/article/details/80036959