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

    问题:已知两个字符串分别为S、P如下图,判断s串中存在几个p串,并输出其起始位置?

    目录

             两种算法的区别 

             暴力解决

             KMP算法

           


    两种算法的区别:

    黄线为已经匹配的部分,两段绿线为c之前相同的前后缀,当c失配时,分别从箭头位置开始匹配的效果是完全相同的。

    暴力是从第一个箭头开始,KMP是从第二个开始,这就是两中方法的差别。(不知道能不能看懂)

    一、暴力解决:

    复杂度:O(N*M

    1、abab匹配成功,但当 i=4 (j=4) 时d与c失配。

    2、令j=0,i=1重新开始匹配,当i=1(j=0)时失配,继续移动。

    3、重复上面的步骤,知道最后匹配成功,输出起始位置。

    代码模板:

    void Search(string a,string p)
    {
        int la=s.size(),lp=p.size();
        for(int i=0;i<la;i++){
            for(int j=0;j<lp;j++){
                if(a[i]!=p[j]){
                    break;
                }
                if(j==lp-1){
                    cout<<i<<endl;
                }
            }
        }
    }
    

    上面的复杂度确实很高,KMP算法的优点就是用线形的时间复杂度解决这个问题。

    二、KMP算法

    复杂度:O(N+M) 

    1、同样abab匹配成功,但当 i=4 (j=4) 时d与c失配。

    2、此时令j=Next[j],即j=Next[4]=2,i不变,继续比较,此时d与a失配。(先不要关心Next[4]=2什么意思,求Next数组传送门

    3、重复上面的步骤,直到最后完全匹配为止。

    比较上面的两种方法应该能看出复杂度的曲别。

    Next数组含义:

    以p串为例子,它的每个坐标对应的Next数组的值如下:

    Next数组的含义:Next[4]=2的含义就是 [0,3] 这个子串的前缀与后缀最大相同的字符数目为2。

    求解Next数组:

    先上代码(找了很多天,这个算是最好用的了)      感谢:Venishel

    ///p[k]为前缀,p[i]为后缀
    void getNext(string p,LL lp,LL Next[]){
        LL k=0;
        for(LL i=1;i<lp;i++){
            while(k&&p[i]!=p[k]) k=Next[k];
            if(p[i]==p[k]) k++;
            Next[i+1]=k;
        }
    }

    KMP算法匹配:

    感谢: v_JULY_v

    该字符对应的Next 值会告诉你下一步匹配中,j应该跳到哪个位置(跳到Next [j] 的位置)。如果Next [j] 等于0,则跳到p串的开头,若Next [j] 不为0,代表下次匹配跳到Next[j]的位置,而不是跳到开头,且具体跳过了j-Next[j] 个字符。

    像下面的这种情况:

    1、现在d与c失配:

    2、Next[j]=2,即j跳到2的位置。

    代码: 

    void kmp(string a,string p,LL la,LL lp){
        LL j=0;
        for(LL i=0;i<la;i++){
            while(j&&a[i]!=p[j]) j=Next[j];
            if(a[i]==p[j]) j++;
            if(j==lp) cnt++,j=0;
        }
    }

    KMP完整代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const LL MAX=1e6;
    LL Next[MAX+5],cnt;
    void getNext(string p,LL lp,LL Next[]){
        LL k=0;
        for(LL i=1;i<lp;i++){
            while(k&&p[i]!=p[k]) k=Next[k];
            if(p[i]==p[k]) k++;
            Next[i+1]=k;
        }
    }
    void kmp(string a,string p,LL la,LL lp){
        LL j=0;
        for(LL i=0;i<la;i++){
            while(j&&a[i]!=p[j]) j=Next[j];
            if(a[i]==p[j]) j++;
            if(j==lp) cnt++,j=0;
        }
    }
    int main()
    {
        string a,p;
        cin>>a>>p;
        LL la=a.size(),lp=p.size();
        getNext(p,lp,Next);
        kmp(a,p,la,lp);
        cout<<cnt<<endl;
        return 0;
    }
    

     

  • 相关阅读:
    u-boot 内核 启动参数
    模块移除 命令rmmod 的实现
    led 的 platform驱动实现
    kconfig语法
    通过编程语言操作数据库
    linux 下 postgres 的使用总结
    maven 项目遇到的问题(不断更新中)
    从svn中check out maven项目 所遇到的一系列问题:
    java多线程学习
    内连接,外连接,交叉连接--数据库查询语句学习
  • 原文地址:https://www.cnblogs.com/ldu-xingjiahui/p/12407411.html
Copyright © 2011-2022 走看看