【菜鸟福音】KMP算法简单理解(从严蔚敏老师的《数据结构》出发)

导言:本文有如下特色:算法

(1)主要讨论的是严蔚敏老师的《数据结构》中第四章所提到的KMP算法,即带NEXT[]辅助数组的KMP算法;数组

(2)主要针对初学者,对算法不熟悉的同窗,主要目的是但愿经过本文能让初学者快速理解KMP算法的NEXT的计算规则。数据结构


最近复习到KMP算法,发现以前虽然好不容易弄会了手动KMP计算NEXT并且自觉得“理解”了KMP算法,结果复习起来发现基本要重看。痛定思痛,决定完全弄懂这个玩意儿。spa


相信即便是初学者也能理解KMP算法的最大优点在于不须要将模式串回退匹配来提升速度。若是给出了NEXT数组,我估计绝大部分人都可以手动模拟搜索过程。递归


但问题就在于,怎么计算next数组呢?数学


严老师的书上给出了一种对next的计算算法。算法当然是很简单的,重点在于解释的过程。基础


咱们先回顾一下书上的作法。搜索

书上从一种匹配状况出发。im

这种匹配状况中,已知在一次匹配的过程当中有(每一个字符写做p[])next

‘p[1]p[2]..p[k]’=‘..p[j]’(即某段字符相等匹配),而且已知p[j]的next[p[j]]的值为p[k]。

而后再考虑p(k+1)和p(j+1)的匹配状况。

书上的讲解虽然易懂,可是将其直接推演到求解一个模式串的next值得状况时则有些让人摸不着头脑:一个串和本身相比,又是错位相比,仍是在知道某段值得状况下"向前看"去求下一个字符的next[],这是在是很差想象。

即便拿一个具体的例子来看,好比abbab。想要模拟一下这种假设,都让人手忙脚乱。

固然,若是算法基础比较好的同窗也许分分钟搞定了这个问题,可是对于我这种来讲...就须要另外一个想法了!


那么来看看个人办法。

首先,让咱们厘清KMP算法中next[]的意义吧。

next数组中的某个值next[k]的意义在于指出匹配失败时下一次匹配的模式串字符的位置.

它其实是,当模式串中的p[k]与文本串(目标串)t[j]对比时,若是不能匹配,在将模式串右移的过程当中,第一个遇到"合理的"的再一次与t[y]比较的模式串上的字符的位置,即p[next[k]]。

这个地方的合理性在于,从p[next[k]]以前的全部模式串字符,与当前位置下的目标串前面的一部分是彻底相同的,即

'p[1]p[2]...p[next[k]-1]'  =  '...t[j-1]'  =  '...p[k-1]'

厘清合理性后,让咱们来看看怎么求next[]。


对于一个模式串p[],咱们很容易经过定义对其头两个字符的next进行赋值(*:若是第一个字符都不匹配,那么只能讲模式串右移;若是第二个字符不能匹配,那么只能再与第一个字符比较),在此处仍是写做p[1]=0,p[2]=1;。

如今,假设咱们已经对p[k]及其以前的每一个字符都求得了next[]的值(这种假设是合理的,由于咱们显然已经求得了p[1]、p[2]的next[]),那么咱们如今来考虑p[k+1]对应的next值,即next[k+1]=x

回顾定义,x应当是这样一个值:它能使得,当模式串与目标串已经匹配了k个字符时,即

'p[1]p[2]...p[k-1]p[k]'='...t[j-1]t[j]'

若是p[k+1]!=t[j+1],那么p[x]就是下一个与t[j+1]比较的字符。

根据前面提到的合理性,此时,即移动后的模式串,在p[x]的那些字符,是与目标串上相应位置上的字符相匹配的,即有:

'p[1]p[2]...p[x-1]'  =  '...t[j-1]'  =  '...p[k]'

注意,此时也就是有

'p[1]p[2]...p[x-1]'='...p[k]'

此时相等意义正是理解算法的关键。

由于已经说过此时求得了p[k]的next[k]=y为已知,而这个y的意义由前面的合理性知道,有

'p[1]p[2]..p[y-1]'='...p[k-1]'

若是此时再知足:p[y]=p[k],那么此时有:

'p[1]p[2]..p[y-1]p[y]'  =  '...p[k-1]p[k]'  =  'p[1]p[2]...p[x-1]'

也就是说此时知足了以下条件y=x-1!(由KMP的“最长部分匹配”条件能够得此时的推导是充要的,本文重在理解,此处的推理也就不证实啦)。那么咱们就求得了

next[k+1]=x=y+1=next[k]+1

这就是严老师教材所给出的公式,不过严老师教材中的推理比如从后向前看,这里更多的是从前向后看。

不过这个过程还没结束,若是p[y]!=p[k]怎么办?,注意,此时咱们有

'p[1]p[2]..p[y-1]'='...p[k-1]'

因此p[y]的next值即next[y]一样知足前面说到的合理性,所以能够有y2=next[y],继续递归...直到,最后一个yn=1。


整个过程就比如一个解方程的过程,有方程特征(即next[]的性质和KMP的性质),有初值和边界条件(即p[k]及以前的字符的next[]值已知,且当第一个字符不匹配时模式串右移),那么就能够求得P[K+1]的next[]值。


这个理解就写到这里啦,有许多地方数学的表达不严谨,重在理解嘛。

若有勘误,请指正。欢迎讨论!


谢谢阅读!