• KMP算法详解


    详解KMP算法

    KMP算法(也叫做KMP模式匹配算法、模式匹配算法),是一种常用的字符串基本算法。其用途是:在线性时间内判断A串是否为B的子串,并求出A串在B串中各自出现的位置

    暴力求解字符串匹配

    在我们还不知道这个世界上有KMP这种东西的时候,我们需要考虑如何暴力匹配两个字符串的包含和被包含关系。

    暴力的做法的时间复杂度是(O(NM))的((N,M)表示两个字符串的长度),就是把(B)串从(A)串的第一个字符开始往后推,每推一位尝试一下匹配。以此类推之后看一看什么时候能匹配到,记录答案。

    这个算法的流程可以看下图理解:

    (图片引自(CSDN)博客)

    KMP思路

    (O(NM))的复杂度显然太慢了(废话,要不然为什么要学KMP),不能满足我们的需求,那么我们如何来对这种字符串匹配进行优化呢?

    我们来回过头对这个问题来进行思考:对于两个字符串的匹配,既然不能一个一个匹配,那就一群一群匹配,即:暴力思路对“字符串匹配”的扩展是一个一个字符地扩,但是我们可以通过一些手段变成一堆一堆的扩。这是我们优化的第一步。

    有了这个思路,我们继续往下想:如何能实现由“一个一个扩”到“一堆一堆扩”呢?这里介绍两个概念:(其实算作字符串的一种基础概念,但是因为这里实在用的很多,怕有些读者不清楚,所以拿来重述一遍)

    • 前缀:字符串前缀的定义是:从原串开头处开始的连续子串。如:abcd的前缀就分别是a/ab/abc。

    • 后缀:类比一下,后缀的定义就是:从原串结尾处向开头处延伸的连续子串。如:abcd的后缀就是:d/cd/bcd。

    有了这两个概念,我们就可以发现,其实“一堆一堆匹配”就是对字符串前缀、后缀的匹配,我们可以在匹配之前预先找出来这些字符串有哪些位置出现“成堆”的相等字符,然后对其进行匹配。这样就可以把效率提升很多。

    KMP的原理及流程

    首先介绍两个数组:(next[i])(f[i])(next[i])表示(A)串(需要和另一个串匹配的小串)前(i)个字符构成的子串最大的相同前缀后缀的长度

    例子:

    abababaac

    在这个长度为9的字符串中,(next[7])表示前7个字符所构成的子串(abababa)中最长的相同前缀后缀,根据前缀和后缀的定义,我们可以发现,前5个字符和后5个字符是相等的,但前6个、前7个字符和后6个,后7个字符都不等了,所以(next[7]=5)

    我们把(A)串的(next)数组的求解过程叫做KMP算法的自我匹配过程

    (f[i])数组表示(B)串(进行匹配的长串)前(i)个字符构成的子串的后缀和(A)串的前缀相同的最大长度。

    我们把(B)串的(f)数组的求解过程叫做KMP算法的异串匹配过程

    用公式理解一下:

    (next[i])的意义就是:

    [next[i]=max{j},j<i,A[i-j+1\,\,to\,\,i]=A[1\,\,to\,\,j] ]

    (f[i])的意义就是:

    [f[i]=max{j},jle i,B[i-j+1\,\,to\,\,i]=A[1\,\,to\,\,j] ]

    next数组和f数组的求解

    根据以上对(next)数组和(f)数组的定义,我们可以发现,当(f[i]=n)(n)(A)串长度)的时候,就是(A)串在(B)串出现的时候,这个(i)就是(A,B)共同串的结尾。

    也就是说,我们只需要想办法求出(next)数组和(f)数组,就可以完成KMP算法的流程。

    (next)数组和(f)数组的求解是相似的过程,这是由它们定义上的相似性得出的。

    (next)数组的求法举例:

    (next)数组的求解是一个递推的过程。需要好好理解。

    在求解(next[i])的时候,我们实际上是借助(next[i-1])的可行解转移的。假设我们现在已经得出(next[i-1])的解,那么对于(next[i]),只需要匹配一下(A[i])和对应前缀的下一个字符是否相等即可,假如相等,就可以在(next[i-1])的基础上直接进行(+1)

    否则呢?(重点来了)

    注意!如果不等的话,并不是直接继承(next[i-1])的值。为什么呢?因为我们在(i-1)串的结尾加入了一个新字符(A[i]),这导致了当前子串的后缀发生了变化,所以不能再从之前的(next[i-1])推导。

    那么,理所应当地,我们应当从前面的那些“合法子串”中寻求最大的那个继续进行匹配。也就是说,既然(next[i-1])不能继承,那我们就尝试继承更小一点的合法串,那么这个更小一点的合法串怎么找呢?答案就是:(next[next[i-1]])

    很惊讶吧?用一个例子说明:

    假如(next[7]=5),这实际上说明了(A)的前(5)个字符和从7往前(5)个字符是相等的,那么,对于这个新插进来的字符(j),假如它和从5往前(j)个字符一起与(A[1\,\,to\,\,j])匹配的话,那么自然地,从7往前的(j)个字符和(A[1\,\,to\,\,j])也是相等的。那么这样的(j)最大是多少?就是(next[5])

    KMP算法模板

    注:在一些新型的编译器(请原谅笔者不知道具体是什么新型的编译器)下,(next)这个词成了(C++)的保留字,如果交上去会(CE)。(膜拜郭爷(GXZLegend)大佬)所以把(next)数组变成了(nxt)数组,想来不会有什么问题。

    模板:(求(next)数组)

    nxt[1]=0;
        for(int i=2,j=0;i<=n;i++)
        {
            while(j && a[i]!=a[j+1])
                j=nxt[j];
            if(a[i]==a[j+1])
                j++;
            nxt[i]=j;
        }
    

    模板:(求(f)数组)

    for(int i=1,j=0;i<=m;i++)
        {
            while(j && (j==n || b[i]!=a[j+1]))
                j=nxt[j];
            if(b[i]==a[j+1])
                j++;
            f[i]=j;
            //if(f[i]==n)
            //{
                //solve
            //}
        }
    
  • 相关阅读:
    二维数组实现01背包
    一维数组实现01背包
    js实现最长子串算法
    mysql__存储过程
    mysql 分页查询 limit
    转:严重: Exception loading sessions from persistent storage
    struts2---ValueStack对象
    struts2----ognl
    什么是JavaBean?
    EL表达式
  • 原文地址:https://www.cnblogs.com/fusiwei/p/11944975.html
Copyright © 2020-2023  润新知