我对KMP算法的理解
KMP算法的核心在于失配回溯表——pnext,相比于通过逐个比较来匹配字符串的朴素算法,KMP通过对模式串的分析,可以做到比较指针在主串上不回溯,一直向前。
1. KMP如何实现不回溯?
对于主串 t0 t1....tj,模式串 p在 pi处与 tj 失配,假设 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
浙公网安备 33010602011771号