• 算法导论————KMP


    【例题传送门:caioj1177


    KMP模版:子串是否出现

    【题意】
    有两个字符串SA和SB,SA是母串,SB是子串,问子串SB是否在母串SA中出现过。
    如果出现过输出第一次出现的起始位置和结束位置,否则输出"NO"
    【输入文件】
    第一行SA(1<= 长度<=1000000)
    第二行SB(1<= 长度<=1000)
    【输出文件】
    如果SB在SA中出现过输出第一次出现的起始位置和结束位置,否则输出"NO"
    【样例1输入】
    aaaaabaa
    aab
    【样例1输出】
    4 6
    【样例2输入】
    aaaaabaa
    aax
    【样例2输出】
    NO


    算法分析:

      KMP其实就是一种偷懒的方式,通过访问前面已经遍历过的位置来首先给定一个可继承的值,然后暴力(汗......)

      首先定义一个p数组,p[i]=j时,代表SA字符串[1...j]与SA字符串[i-j+1...i]所形成的子串完全相同

      那么我们怎么得出这个p数组呢?

      就是暴力+偷懒!!!(其实前面已经讲了,尴尬)

      当我们访问到i这个点时,我们先访问一下i-1,因为我们访问到i的时候已经把1~i-1的p数组求出来了,那么请看下面的图:

      我们设j=p[i-1],i-1-j+1=i-j,那么我们可以知道SA[1...j]=SA[i-j...i-1],也就是上图的红色区间,那假如SA[j+1]=SA[i]的话,我们就可以直接p[i]=j+1对吧

      但是现实是残酷的(AC没有那么容易~),那么当SA[j+1]!=SA[i]的时候怎么办呢?那么我们就找p[j]!!!如图:

      我们再设一个变量k=p[j],因为p[j]的定义,所以我们可以得到SA[1...k]=SA[j-k+1...j],也就是前两个棕色区间,这时因为两个红色区间相同,所以我们可以得到SA[1...k]=SA[i-j...i-j+k-1],也就是第一个和第三个棕色区间相等,接着又可以得到SA[j-k+1...j]=SA[i-k(i-1-k+1)...i-1],也就是第二个和第四个的棕色区间相等,然后!!!我们得到四个棕色区间相等!!!我们就可以判断SA[k+1]和SA[i]是否相等来求p[i],如果相等,p[i]=k+1,否则继续找p[k]。一直找啊找,直到找到没得找也就是当前的p[p[p[p[p[p[.......的值为0时,那么我们就判断SA[1]和SA[i]是否相同,相同p[i]=1,否则p[i]=0

      其实也不算暴力,而是利用p数组的定义来寻找前面可以用来继承的点!!

      但是到了这里,还没有完成KMP的学习

      我们的p数组只对SA字符串,也就是题目中的子串进行了处理

      那么p数组对于我们求答案有什么帮助呢?

      

      我们设j为SB字符串中的i-1位置开始所组成的后缀,与SA字符串中的1位置开始的前缀的最长公共长度,也就是SA[1...j]=SB[i-j(i-1-j+1)...j-1],那么我们可以判断SA[j+1]和SB[i]是否相等,相等就记录j的值,不相等就找p[j]!!!是不是似曾相识的步骤,没错!就是把SB中的i转移到SA中进行求解,这样就是KMP的全部了!


    参考代码:

    #include<cstdio>
    #include<cstring>
    using namespace std;
    char sa[1110000],sb[1100];//sa是母串,sb是子串
    int p[1100];//p数组是为子串准备的,p的含义和母串一点关系都没有。
    //p[i]表示sb中 以第i个字符为结尾,往前最多拉多少个字符(sb[i]结尾的后缀)可以完全匹配sb的前缀
    //比如  sb="abcdabc"中 p[7]=3, 就这样,我当你理解p[i]的含义了
    int main()
    {
        int lena,lenb,i,j;
        scanf("%s",sa+1);lena=strlen(sa+1);
        scanf("%s",sb+1);lenb=strlen(sb+1);
        //制造P数组
        p[1]=0;
        for(i=2;i<=lenb;i++)
        {
            j=p[i-1];//先记录p[i-1]
            while(j>0&&sb[i]!=sb[j+1]) j=p[j];
            /*
            这里有两种情况
            如果j+1的位置的字符等于i位置的字符的话,那就直接把p[i]=j+1
            否则就找j的p值,因为1~sb[p[j]]一定是1~sb[j]的后缀
            */
            if(sb[i]==sb[j+1]) p[i]=j+1;else p[i]=0;
        }
        int st,ed;
        j=0;
        for(i=1;i<=lena;i++)//紧接着用p数组来匹配母串
        {
            while(j>0&&sa[i]!=sb[j+1]) j=p[j];
            if(sa[i]==sb[j+1]) j++;
            if(j==lenb){ed=i;st=i-lenb+1;break;}//得到答案后就记录开头与结尾
        }
        if(j==lenb) printf("%d %d
    ",st,ed);
        else printf("NO
    ");
        return 0;
    }

     

  • 相关阅读:
    ubuntu切换到root
    ubuntu vim退出时出错
    easy_install和pip的安装及使用
    Swap file "/etc/.hosts.swp" already exists! [O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it,
    ERROR 2003 (HY000): Can't connect to MySQL server on 'ip地址' (110)
    Android-PullToRefresh(一)
    Android Exception 6 (adapter is not modified from a background thread)
    Android Exception 5(startActivityForResult & singleTask)
    【协议篇】UDP
    【协议篇】TCP
  • 原文地址:https://www.cnblogs.com/Never-mind/p/7598880.html
Copyright © 2020-2023  润新知