zoukankan      html  css  js  c++  java
  • 数据结构/ 串的模式匹配法 / 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/

  • 相关阅读:
    集群、分布式、负载均衡区别与联系
    Android--加载大分辨率图片到内存
    Android--MP3播放器MediaPlayer
    Android--SoundPool
    Android--MediaPlayer高级
    Android--调用系统照相机拍照与摄像
    Android--SurfaceView播放视频
    Android--使用VideoView播放视频
    Android--使用Camera拍照
    Vue组件选项props
  • 原文地址:https://www.cnblogs.com/elegantcloud/p/13803423.html
Copyright © 2011-2022 走看看