zoukankan      html  css  js  c++  java
  • 【KMP】【字符串】KMP字符串匹配算法 学习笔记

    一、简介

        KMP是由Knuth、Morris和Prat发明的字符串匹配算法,它的时间复杂度是均摊(O(n+m))。其实用Hash也可以做到线性,只不过Hash存在极其微小的难以避免的冲突。于是就有了KMP。

        KMP算法用作模式串匹配,可以找到一个长为(m)的模式串在一个长为(n)的主串中出现的次数和位置。

    二、朴素算法((O(nm)))

        实际上是枚举模式串在主串中出现的位置,然后一一比对,出现错误就停止,移动到下一位。连续匹配成功(m)次就说明模式串在主串中出现了。

        其实(O(nm))是一个比较宽的上界,随机数据远没有这么慢。不过给出主串S="aaaaaaaaaaa",模式串T="aaab",就得每次匹配到最后才知道挂掉了。那么我们既然已经知道这样会挂掉,为什么还要重复匹配呢?这就是KMP要解决的问题。

    三、KMP算法

        0.注意事项

        KMP算法中,字符数组下标是从1开始而不是0,这样会比较方便,在匹配的时候直接调用nxt数组就不用-1了,而且很直观。

        1.nxt数组(next)

        nxt数组是KMP算法的核心。

        nxt数组nxt[i]中存的是后缀字符与前缀字符相同的个数,且后缀字符不是前缀字符,也就是不能完全匹配自己。

        对于ababcab而言,它的nxt数组是

    nxt[1] nxt[2] nxt[3] nxt[4] nxt[5] nxt[6] nxt[7]
    a ab aba abab ababc ababca ababcab
    0 0 1 2 0 1 2
    没有后缀与前缀相同 没有后缀与前缀相同 前缀"a"=后缀"a"

    前缀"ab"=后缀"ab"

    注意不能完全匹配自己

    没有后缀与前缀相同(失配直接=0) 前缀"a"=后缀"a" 前缀"ab"=后缀"ab"

        看上去很简单。但是它怎么求呢?当然,如果递推掌握的比较好,可以知道,如果T[i+1]==T[nxt[i]+1],那nxt[i+1]=nxt[i]+1。而失配了怎么办,是我们接下来要解决的问题。

        2.nxt数组的作用

        在上面我们提到了,KMP算法是减少了朴素算法的重复匹配,均摊下来每个字符只匹配了一次。在上面的表格中,我们看到了nxt数组处理出了模式串后缀与前缀匹配数。当我们用模式串匹配主串,在后缀失配时,在模式串里与后缀相同的就是我们预处理出的前缀了,因此我们只需要把模式串前缀挪到现在的位置来即可。这时我们会发现,中间可能跳过很多没有用,也就是安排匹配不上的状态,这就是KMP的优化。

        3.nxt失配的求法

        可以看出,求nxt数组,就是在用模式串匹配自己,且不匹配第一位。当我们发现T[i+1]与T[nxt[i]+1]失配时,比nxt[i]要次一点但是仍然与T[i]所在后缀相同的前缀就是nxt[nxt[i]]了。就是我们相当于退而求其次,“贪心地”按长度从大到小找一个最能匹配上后缀的前缀。

    匹配不到的情况

        比如说

    ababababc
            ↑

    如果匹配到第8+1位时失配了,这时就去找第nxt[8]+1位,(nxt[8]=6)也就是第6+1位。此时发现还是不行,继续找第nxt[6]+1位……直到找到nxt[1]=0,退出循环,再看看第一位是否匹配,如果匹配,nxt[9]=1,否则nxt[9]=0。

    重新匹配到的情况

    abcaba
         ↑

    这里匹配到第5+1位发现失配,去找nxt[5]+1=2+1,仍然不行,但是nxt[2]=0了,此时退出循环,发现T[1]=T[6],所以nxt[6]=1。

        其实情况还有很多很多种,这里只是列举了几种,想让nxt数组的求法变得更加形象一些。

    四、KMP代码

    #include<cstdio>
    #include<cstring>
    char s[1000100];
    char t[1000100];
    int nxt[1000100];
    int f[1000100];
    int main()
    {
        scanf("%s",s+1);
        scanf("%s",t+1);
        int n=strlen(s+1);
        int m=strlen(t+1);
        for(int i=2,j=0;i<=m;++i)
        {
            while(j&&t[i]!=t[j+1])
                j=nxt[j];
            if(t[j+1]==t[i])
                ++j;
            nxt[i]=j;
        }
        for(int i=1,j=0;i<=n;++i)
        {
            while(j&&(j==m||s[i]!=t[j+1]))
                j=nxt[j];
            if(t[j+1]==s[i])
                ++j;
            f[i]=j;
            if(f[i]==m)
                printf("%d
    ",i-m+1);
        }
        for(int i=1;i<=m;++i)
            printf("%d ",nxt[i]);
        return 0;
    }
  • 相关阅读:
    Android 图片圆角、图片圆形【转载:https://github.com/SheHuan/NiceImageView】
    fragment中嵌套listview,切换时数据出现重复加载
    fragment中嵌套listview,切换时数据出现重复加载
    Android让View的显示超出父容器
    ZooKeeper
    Redis
    kafka
    性能优化一
    RK Android7.1 禁用 USB触摸
    RK Android7.1 使用POWER按键才能开机
  • 原文地址:https://www.cnblogs.com/wjyyy/p/kmp.html
Copyright © 2011-2022 走看看