字符串匹配算法

    KMP算法,以为一个简简单单的算法,看了我一天时间竟然没有看懂…果然图样图撕破了,三位大师提出的算法岂是我等屌丝能够迅速的理解的?不过话说看了这次算法才知自己的智力有多么的吃紧,还是要努力学习呀~智力不行就要加把劲了。(接下来字符串匹配算法均参考算法导论)

    字符串匹配,算法的模型不用提出大家都知道,仅仅是在文本T字符串中精确匹配模式串P,简简单单,轻轻松松的就知道这么一个模型,自然而然的能够想到一个最笨最实用的算法,朴素算法,朴素算法就是逐个比对,然后在文本串中下移一位在进行逐个比对,算法复杂度O((n-m+1)m),

    不过这种简单的方法的时间复杂度不是我们能够容忍的,提高一点有一个Rabin-Karp的算法,此算法的最坏时间复杂度没有提高,但是平均情况比较好,而且它这个思想可以借鉴,就是把匹配当做算术运算,后来取模比对,针对剩余的少部分精确比对~不过这不是重点,

    接下来的就是重量级的算法,KMP算法,它在预处理时间和匹配时间均达到了线性时间~O(m)的预处理时间,O(n)的匹配时间,非常完美的理论,不是么?网上很多资料都是关于next函数的,这里我参考算法导论里面的KMP算法,其思想虽然一致,但是不同于next函数的原理,其原理如下:(匹配过程)

 

 i  1 2 3 4 5 6

    a b a b a b a a b a b 

    a b a b a c b

          a b a b a c b  

        j 1 2 3 4 5 6 7  

  j 1 2 3 4 5 6

 

    这里比对过程是i和j的变化,如果i和j对应的地方相同,则同时+1,继续走下去,当出现T[i]和P[j]出现不同时,如上图中的P[6]!=T[6],此时KMP考虑的是i不进行回溯,而是考虑挪动j,使得j变化为一个更小的值,小于j,这样的变化效果是P整体向后移,但是此时不能够导致T[6]前面的i的匹配性质,及挪动后T[6]前面的元素和P对齐之后还是要相同的,所以这里j=5改为j=3,挪动后效果如红色标注的P所示,这是T[6]=P[4],两个串的表示i和j可以挪动下去;

 i  1 2 3 4 5 6 7 8 9 10 11

    a b a b a b a a b a b 

     

          a b a b a c b  

                a b a b a c b

                      3

                      a b a b a c b

                      1

                         a b a b a c b

                      0

        j 1 2 3 4 5 6 7  

    但是接下来挪动的时候又产生了问题,T[8]!=P[6],这时候如同上面的方式,挪动j=5的位置,首先挪动至最近的保持T[8]前保持性质的部分,其实这里保持性质及P[5]的后缀的最长前缀…说到概念可能就不知道了,还是理解保持性质吧,挪动至红色后发现T[8]!=P[4],所以这里还要挪动j=3的位置,挪动成j=1,还是不能满足,所以还需要挪动,这时j=0了,及这时候T[8]和P[1]比较了,这里刚好一样,所以i可以继续向后挪动了;

    以上的移动均是参考了一个数字,该数字是保证某个位置的时候P和T能够保持性质,但是这个性质仅仅和P有关,及上面说的P的后缀的最长前缀,因为以上匹配过程用到了这个数据,所以接下来就是这个数据的计算的算法了,其实这个算法才是让我纠结的地方,纠结了好久,这里发现一个可以很容易理解的地方,分享给大家;

    上面算法的线性时间需要用到均摊分析的知识~可以仔细思考一下,线性的时间,

    这里图解一下计算后缀的最长前缀吧,这里这个记为PI[j],这里PI[j]可以根据PI[1],PI[2]…PI[j-1]计算得出,

    

《字符串匹配算法》

    如图所示,PI[j-1]为标记的凸包的长度,这里如果P[lengh[PI[j-1]]+1]=P[j],这里PI[j]及为PI[j-1]长度加1,如果P[lengh[PI[j-1]]+1]!=P[j],则这个需要在更加前面找,这里及递归的找,画图如下,

    

《字符串匹配算法》

    及黑色线标注的位置是否与P[j]相同选择是否继续递归,黑色标注的位置的选择为PI[ PI[j-1] ] + 1,PI[j-1]的位置为红色的那段,因为不可能为红色,所以要缩小一下,这里示意为绿色的圈,绿色的圈挪动到前边的红色部位,所以绿色的圈在红色的部位找到前缀为黑色的部分,所以PI[ PI[j-1] ]是这样得出的,这样递归下去就能够得出PI[j]的值了,这样就可以根据计算得出的PI[j]值运用匹配算法了,

    不知道上面的解释是否能够解释的清楚,一个递归的过程,分析算法复杂度也是一个均摊分析的方法,同样是线性的,

 

    模式串的预处理的伪代码如下:

COMPUTE-PREFIX-FUNCTION(P)
    m=length(P) 
    PI[1]=0               //初始化
    k=0                    //初始化
    for q=2 to m
        while k>0 and P[k+1]!=P[q] //结束条件至0或者相等
            k=PI[k]                             //递归的过程
        if P[k+1]=P[q]      //               增加的过程
            k=k+1
        PI[q]=k                         //赋值为k,下一个计算已k为起点向下递归
    return PI

  KMP匹配过程执行的伪代码如下:

KMP-MATCH(T,P)
    n = length[T]
    m = length[P]
    PI=COMPUTE-PREFIX-FUNCTION(P)
    j=0
    for i=1 to n
        while j>0 and P[j+1]!=T[i]  //如果不等,挪动P,根据计算好的PI
            j=PI[j]
        if P[j+1]=T[i] //如果相等,则向后继续匹配
            j=j+1
        if j=m            //匹配m个后则成功
            printf(匹配成功)
            j = PI[j]
       

  以上是伪代码描述,上面说描述的参考算法导论的KMP思想,网上还有一种next方式的,有空研读一下比较差别;

     除了上述算法还有一些优秀的字符串匹配算法,基于自动机的,BM算法等等,

 如果想要了解一下有限自动机方法可以继续看下去,这种方法可KMP算法可以看做等价,

 有限自动机字符串匹配算法简单介绍如下:

给定模式P[1…m],其对应的字符串匹配自动机定义如下:

1、状态集Q为{0,1,…,m},初始状态q0为0状态,状态m是唯一的接受状态。

2、对于任意的状态q和字符a,变迁函数δ由如下等式定义:δ(q,a)=σ(Pq a)

这里的解释一下σ(x)的定义,其为相应P的后缀函数。σ(x)为x的后缀 P的最长前缀的长度:σ(x)=max{k:Pk是x的后缀}

简单的举例如下:

i         1 2 3 4 5 6 7 8 9 10 11 

T[i]    a  b a b a b a c  a  b   a 

状态   1 2  3 4 5  4 5 6  7 2  3  

上述是匹配的过程,下面是转移表:

      a    b     c              P

0    1     0     0            a    

1    1     2     0            b

2    3     0     0            a

3    1     4     0            b

4    5     0     0            a

5    1     4     6            c

6    7     0     0            a

7    1     2     0 

自动机的过程与KMP可以转换,只不过自动机考虑了T[i]不匹配过程中的具体字母表的转移,然后KMP只是不匹配的情况下先进行挪动,挪动后发现不匹配之后再进行挪动?直至挪动为0的时候,抑或是一直不匹配i++,此时忽略j;自动机只是具体考虑了此时如果T[i]如果不等于P[j],并且此时T[i]的具体取值已经指定,在此值指定的情况下应该怎样的转移~

a  b  a  b  a  c  a

0  0  1  2  3  0  1

上面是KMP计算出的模式函数,首先a值是T[i]和P[1]如果相等,则j++,向后移动,但是如果不匹配,此时如同自动机转移表第一行,为b or c,此时就应该i,j同时++了,此时KMP如果发现T[i]和P[1]不相等,则上面算法i++,j取值仍为0,等价于i和j同时++了;

这时考虑b,如果a匹配了,P[2]和T[i]匹配,此时j++=2了,这时候移动两位了,自动机表现匹配两个字母,所以为状态2,但是如果不相等呢?此时考虑了b,必定T[i]和P[1]已经匹配了,KMP中不匹配则考虑移动P,根据值j重新改为0,及向右移动两位,然后自动机此时读入不等的可能为a或者c,如果为a,则KMP移动两位之后匹配上了一位,j++=1,如果为c,移动之后仍然不能匹配,所以i和j同时仍然移动,KMP表现i++,j重新赋值为0;

这次考虑一个中间的值,比如c的位置,如果知道T[i]值为多少,则可以知道具体的移动多少,然而KMP中是根据P计算的模式值,所以这里不知道T[i]的值,它有可能是最坏的值,所以这里取得的是最坏的值;所以这里计算自动机转移函数可以考虑KMP中已经计算好的结果,这样提升时间复杂度,

所以自动机匹配和KMP算法有一定的等价性,上面的描述比较凌乱,希望能够对你有所启发,

    原文作者:weixliu
    原文地址: https://www.cnblogs.com/weixliu/archive/2012/12/14/2817331.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞