先给出模式匹配问题:给出两个字符穿,一个为S(主串)另一个为T(字串),模式匹配就是求T在S中的位置。我们先介绍简单的模式匹配算法,KMP算法是基于这种算法的改进算法。
简单的模式匹配算法:从S的第一个字符开始和T的第一个字符进行比较,若相等,则继续逐个的比较后续的字符,直到T中的每个字符依次和S中的一个连续的字符序列相等,则匹配成功,返回这个S中的连续字符序列的第一个字符的下标,如果在比较过程中有某个字符不相等,则从S 的下一个字符开始重新和T的第一个字符比较,依此类推,直到S中的字符都比较完了仍然没有匹配成功的话,则匹配不成功。Python实现代码如下:
def StrMatching(S,T): i,j=0,0 m,n=len(S),len(T) while i<m and j<n: if S[i]==T[j]: i+=1 j+=1 else: i=i-j+1 j=0 if j==n: return i-n
else: return 0
KMP算法:
接下来进入我们的正餐KMP算法,上述的简单的模式匹配算法的最坏的时间复杂度为O(n*m),KMP算法可以在(m+n)的时间数量级上完成模式匹配操作。KMP算法的改进在于:每当一趟匹配过程中出现字符不相等时,不需要回溯i指针,而是利用已经得到的"部分匹配"的结果将模式串T向右"滑动"尽可能远的一段距离后,再继续进行比较。这样的表述比较的抽象,我的理解是:在使用KMP算法进行模式匹配时,使用了两个指针i和j,其中i为指向主串S的指针,j为指向子串T的指针。在没有遇到不相等的字符之前,i指针和j指针同时向右移动,当碰到不相等的字符的时候,保持i指针不动,把子串T向后滑动一个合适的位置(向后滑动的过程即为将j指针更新的过程),让这个位置的字符和i指针指向的字符进行比较,这个合适位置与子串本身的结构有关。根据子串和一个求取next函数值的算法可以得到一个next整数数组,这个数组的长度和子串T的长度一样,next数组中的数值对应于相同下标的子串T中的字符如果遇到不匹配的情况时j指针更新的数(在匹配过程中,如果遇到不相等的字符,则根据这个字符对应数组中的值,将j指针的值变更为这个值),这个next数组的求解实际是对子串T的每个位置找到最长的公共前缀。在得到next数组后,利用next数组就能完成模式匹配的过程了。下面来谈谈next数组的求法以及个人的理解。
求next数组的python代码如下:
def get_next(t,next): i=0 next[0]=-1 j=-1 while i<len(t)-1: if j==-1 or t[i]==t[j]: i+=1 j+=1 next[i]=j else: j=next[j]
上述算法用文字描述如下:
(1):next[0]=-1
(2)后面求解每一位的next[j]的值时,根据j的前一位进行比较,令k=next[j-1]
(3)比较t[j-1]与t[k]:
a.如果相等,则next[j]=k+1
b.如果不相等,令k=next[k],若k不等于-1,跳到第三步;如果next[k]==-1,则next[j]=1
个人理解:(1)next[0]取-1的原因:next[0]对应的是子串T中的首个字符,在进行匹配时,如果主串中当前字符与子串中的首个字符不匹配,则直接将主串的指针往后移动一位,然后再与子串的首个字符进行比较,而这一过程体现在代码中为指针i和指针j都加1,因此next[0]取负一的原因就是在经过j+1操作之后要使得j的值又变为0即为子串的首字符。
(2)在求解next[j]的值时,都是根据前一位进行比较的原因:首先,我们看代码,代码中next[i]赋值的步骤是发生在i和j都自增之后,而if语句中比较的是t[i]和t[j]是否相等,因此 ,求next[i]的值就要先比较next[i-1]和next[j-1]的值,从代码中可以看出来在求解每一位next[j]的值时,要根据j 的前一位进行比较.这是从代码上分析得到的原因,而实际的原因是:因为next数组求解的实际就是对每个位置找到最长公共前缀,这个前缀指的是当前字符前面的那些字符,就比如abc这里面c字符的前缀就是ab,因此求当前的next[j]的值需要根据j的前一位进行比较。
(3)这个第三步就是一个迭代求每个位置最长公共前缀的过程了,迭代的终止条件为next[k]为负一或者t[j-1]与t[k]相等。
求得next数组之后,KMP算法就与简单的模式匹配算法很相似了。不同之处仅在于当匹配过程产生失败时,指针i不变,指针j退回到next[j]的位置,并重新进行比较,并且当j为0时,指针j和i同时加1.在得到next数组之后的KMP算法的python代码如下:
def KMP(S,T,next): i=0 j=0 while i<len(S) and j<len(T): if j==-1 or S[i]==T[j]: i+=1 j+=1 else: j=next[j] return i-j
下面给出一个在python中的KMP算法的完整例子:
def get_next(t,next): i=0 next[0]=-1 j=-1 while i<len(t)-1: if j==-1 or t[i]==t[j]: i+=1 j+=1 next[i]=j else: j=next[j] def KMP(S,T,next): i=0 j=0 while i<len(S) and j<len(T): if j==-1 or S[i]==T[j]: i+=1 j+=1 else: j=next[j] return i-j S='abcabaaabaabcac' T='abaabcac' next=[0 for i in range(len(T))] get_next(T,next) pos=KMP(S,T,next) print(pos)
程序运行结果为7