数据结构/ 串的模式匹配法 / kmp算法与next数组的构造

  模式匹配的基本思想:存在主串S和模式串T,从S的第pos个字符起和T的第一个字符相比较,若相等,逐个比较后续字符;若不相等,从S的pos+1个字符旗重新依次匹配, 直到T中的每个字符和S中的一个连续 字符序列相等,即为匹配成功。

可以据此情景,有这么一道题:

题目描述

如题,给出两个字符串s1s2,其中s2s1的子串,求出s2s1中所有出现的位置。

输入格式:

第一行为一个字符串,即为s1(仅包含大写字母)

第二行为一个字符串,即为s2(仅包含大写字母)

输出格式:

若干行,每行包含一个整数,表示s2s1中出现的位置

输入输出样例

输入样例#1

2222bokeyuan111

bokeyuan

输出样例#1

5

输入样例#2

acabaabaabcacaabc

aabcac

输出样例#2

6

通常的做法是:

 

#include<bits/stdc++.h>
using namespace std;
int main()
{
    string str1,str2;
    cin>>str1;              //主串
    cin>>str2;              //模式串
    int len1=str1.size();   //取长
    int len2=str2.size();

    int j=0;
    for(int i=0; i<len1; i++)
    {
        if(str1[i]==str2[j]) //相等,往后匹配
        {
            j++;
            if(j==len2)     //全部匹配,输出出现的初始位置
            {
                cout<<i-j+2<<endl;
                j=0;
            }
        }
        else               //不相等,换新的起点
        {
            j=0;
        }

    }
}

 

运行正常:

 

 

 

 这个算法的时间复杂度是O(M*N),效率较低,只是简单地不断重复循环和判断。

 

然后引入一种优化算法,这个算法是D.EKnuth、V.R.Pratt、J.H.Morris同时发现的,因此称为克努特——莫里斯——普拉特算法,KMP算法。此算法可以在O(M+N)的时间数量级上完成串的模式匹配操作。这种算法不太容易理解,最关键的是构造next数组

eg.我们引入模式串[T]={abaabcac},next有如下结果:

j 1 2 3 4 5 6 7 8
模式串T a b a a b c a c
next 0 1 1 2 2 3 1 2

 

 

计算某字符的next时,看该字符的前一个字符T[j-1],和以这个前字符的next为下标的字符T[netx[j-1]]是否相等,若相等,则该字符的next=这两字符的next之和。

如:在计算next[6]时,T[j-1]=T[6-1]=T[5]=b,  T[next[j-1]]=T[next[5]]=T[2]=b,  b==b,  所以next[6]=next[5]+next[2]=1+2=3

j 1 2 3 4 5 6 7 8
模式串T a b a a b c a c
next 0 1 1 2 2 3 1 2

 

若不相等,则按这个方法继续向前寻找,直到找到相同的

若找到起始字符也不相同,则该字符的next=1

如:在计算next[7]时,c!=a

j 1 2 3 4 5 6 7 8
模式串T a b a a b c a c
next 0 1 1 2 2 3 1 2

 

上述一种理解貌似比较麻烦,而且我好像在某个地方出错了,下面换一个简单一点的理解方法:前缀和后缀

next[i](i从1开始算)代表着,除去第i个数,在一个字符串里面从第一个数到第(i-1)字符串前缀与后缀最长重复的个数+1。

 

前缀:除去最后一个字符的剩余字符串。

后缀:除去第一个字符的后面全部的字符串。

如,在串"abcd"中,"abc"是前缀,"bcd"是后缀

关于"最长重复子串":首先保证重复,然后是最长,与重复的次数无关。

j 1 2 3 4 5 6 7 8
模式串T a b a a b c a c
next 0 1 1 2 2 3 1 2

还是以这串数组为例,并且默认next[1]=0,next[2]=1。下面开始分析:

next[1] = 0 ,默认值

next[2] = 1 ,默认值

next[3] = 1,即"ab",前缀是"a",后缀是"b",没有最长重复子串,next[3]=0+1;

next[4] = 2 ,即"aba",前缀是"ab",后缀是"ba",最长重复子串“a",next[4]=1+1;

next[5] = 2 ,即"abaa",前缀是"aba",后缀是"baa",最长重复子串”a",next[3]=1+1;

next[6] = 3 ,即"abaab",前缀是"abaa",后缀是"baab",最长重复子串“ab",next[4]=2+1;

next[7] = 1,即"abaabc",前缀是"abaab",最长重复子串“ab";后缀是"baabc",最长重复子串“a",ab!=a,故没有最长重复子串,netx[7]=0+1;

……

这个方法好理解,但不好根据这个思路写代码


 

代码:

#include<bits/stdc++.h>
using namespace std;
void oldnext(char p[],int next[],int len)
{
    next[1]=0;
    int i=1;
    int j=0;
    while(i<p[0])
    {
        if(j==0||p[j]==p[i])
        {
            ++i;
            ++j;
            next[i]=j;
        }
        else
            j=next[j];
    }//while

    //输出
    int k=1;
    for(k=1; k<len+1; k++)
        cout<<next[k]<<" ";
    cout<<" "<<endl;
}
void kmp(char s[],char p[],int next[],int mlen)//主/模式/next/主长
{
    for(int i=1,j=0; i<=mlen; i++)
    {
        while(s[i]!=p[j+1]&&j>0)
            j=next[j];
        if(s[i]==p[j+1])
            ++j;
        if(j==next[0])
        {
            cout<<i-next[0]+1<<endl;
            j=next[0];
            break;
        }
        if(i==mlen)
        {
            cout<<"-1"<<endl;
        }
    }
}

int main()
{

    char m[100];
    cin>>m+1;
    int mlen=strlen(m+1);

    char p[100];
    cin>>p+1;
    int len=strlen(p+1);



    p[0]=len;
    m[0]=mlen;

    int next[len+1];
    int nnext[len+1];
    next[0]=len;

    oldnext(p,next,len);
    kmp(m,p,next,mlen);
}
//主串m[0]=mlen;
//模式串p[0]=len;
//n函数 next[0]=len;

 

void getnext(string T,int next[])
{
    i=1;
    next[1]=0;
    j=0;
    while(i<T[0])
    {
        if(j==0||T[J]==T[I])
        {
            ++i;
            ++j;
            next[i]=j;
        }
        else
            j=next[j];
    }
}

 采纳:https://blog.csdn.net/you_will_know_me/article/details/77102567       https://blog.csdn.net/suguoliang/article/details/77460455       https://oi-wiki.org/string/kmp/

posted @ 2020-10-12 20:27  EleclouD  阅读(518)  评论(0编辑  收藏  举报