我对KMP算法的理解

KMP算法的核心在于失配回溯表——pnext,相比于通过逐个比较来匹配字符串的朴素算法,KMP通过对模式串的分析,可以做到比较指针在主串上不回溯,一直向前。

 

1. KMP如何实现不回溯?

  对于主串 t0 t1....tj,模式串 p在 pi处与 t失配,假设 p0~pi-1 存在最长相等前后缀,可以证明将模式串移动至该前缀的后一位,再将其与 tj进行比较,不会漏掉可能的匹配,并且可以大大加速匹配过程。并且这种移动与具体主串无关,仅仅与模式串的失配位置 i 有关,可以通过提前分析模式串,得到失配后的串移动表pnext。

2. 如何得到pnext?

  pnext表实质上记录的是模式串中前 i 个元素形成的子串,其最长相等前后缀的大小。即pnext [ i ] 记录着p0到pi-1的子串最长相等前后缀的大小。

  具体实现方法就是用模式串自身去逐个比对自身,由前后缀的定义可知,头元素没有相等前后缀,pnext [1]=0。用第0个元素去比对第1个元素,当p0=p1时,pnext [2]=1,接下来比较p1是否等p2否则pnext [2]=0,接下来继续比较p0是否等p2.......

  更一般的,当已知模式串中pnext [ i-1]=k-1,即 p0~pk-2与 pi-k~pi-2求pnext [ i ]。分两种情况:

    1)当pi-1=pk-1时,对于i-1的最长相等前后缀,比i-2要多1,那么可以得到pnext [ i ]=k,指针后移,开始求pnext [ i+1];

    2)当pi-1!=pk-1时,这时我们需要在 p0~pk-1中继续向前找,在 pi-k~pi-1中继续向后找,寻找新的最长相等前后缀。假设我们寻找到了该串,即前缀为 p0~px-1,后缀为 pi-x~pi-1,那么由于pi-1!=pk-1,而pi-1=px-1。表明px-1!=pk-1,这说明串p0~px-2一定是pk-1的最长相等前后缀串,即pnext [ k ]=x-1。因此当pi-1!=pk-1时,可以直接比对pi-1是否等于ppnext[ k ]!!!此时问题变为:已知模式串中pnext [ i-1]=k-1,求pnext [ i ],其中i-1实际为k,k-1实际值为x-1。若比对成功,则pnext [ i ]=x,指针后移,否则直接比对pi-1是否等于ppnext[ pnext[ k ] ]。问题形成了递归。

  将这种递归求值关系与边界条件相结合,我们注意到当p1 !=p0时,pnext [2]=0= -1+1,可以设pnext [0]=-1,则整个求pnext表值过程可以用一个函数统一起来。求值过程从1号位值开始。

 

def my_pnext(p):
    '''模式串的pnext表生成函数'''
    m=len(p)
    pnext=[-1]*m
    i,k=0,-1
    while i<m-1:
'''当k=-1时,说明pi != p0 ,没有最大相等前后缀,此时pnext[i]应为0
同样可以将i,k都加1,进行赋值'''
if k==-1 or p[i]==p[k]: i,k=i+1,k+1 pnext[i]=k else: k=pnext[k] return pnext

 

相应的KMP函数为:

def my_kmp(t,p,pnext):
    '''t是主串,p是模式串,pnext是模式串kmp算法的回溯指针表'''
    j,i=0,0
    n,m=len(t),len(p)
    while j<n and i<m:
        if i==-1 or t[j]==p[i]:
            j,i=j+1,i+1
        else:
            i=pnext[i]
    if i==m:
        return j-i
    return -1

 

posted on 2020-04-05 22:51  ZhangCheng2020  阅读(247)  评论(0)    收藏  举报