算法简介

该算法由KnuthMorris以及Pratt三人共同提出,故又称Knuth-Morris-Pratt算法(简称KMP算法)。与暴力算法相比其优点主要是通过取消了主串的回溯来提高算法效率。

代码内容

# 人生苦短,我用python
# 这里T表示副串,j,k为下标
# 由于next(0)=next(1)=0 (但为了方便设next(0)=-1) 
# next(2)=1
# 故这里仅考虑T.Length ≥ 3时
def getNext(T):
	j,k,Next = 0,-1,[-1]
	while j < len(Next)-1 : 
		if k == -1 or T[j] == T[K]:
			j+=1
			k+=1
			Next.append(k)
		else: k = Next[K] #这里最难理解
	return Next

运用递归思想:对于Next中某个元素Next[ j+1 ],我们可以通过Next[ j ]来求出。所以不妨设Next[ j ] = k,此时求Next[ j+1 ]。此时考虑两种情况:

#1° T[ k ] == T[ j ]:

由于 Next[ j ] == k,也就是说T[ t0 ~ tk-1 ]==T[ tj-k ~ tj-1 ],又有了条件T[ k ]==T[ j ],那么很显然有T[ t0 ~ tk ]==T[ tj-k ~ tj ](直接将两个元素分别加在两个子串末端) 根据Next数组的判定条件可以得到,Next[ j+1 ] = k+1
#如下图:
在这里插入图片描述
这种情况比较易懂,不过多说明。

#2° T [ k ] != T[ j ]:

在这里插入图片描述
如图,T[ k ] = C,T[ j ] = B,显然不相同。那么说明应当缩短前后子串的长度,来确保在更小范围内有可能T[ t0 ~ td ]==T[ tj-d ~ tj ] (其中d<k),也就是“牺牲” k-d 长度的子串来保证能使剩下部分子串重复。接下来求d:

不如假设Next[ k ] = m,也就是说T[ t0 ~ tm-1 ]==T[ tk-m ~ tk-1 ] ——记该式为“ imp ”式,考虑到Next[ j ] = k,故 imp 式左边=T[ tj-k ~ tj-k+m-1 ],右边=T[ tj-m+1 ~ tj ],而 imp 式左边= imp 式右边,故 imp 式左边,也就是T[ t0 ~ tm-1 ] = T[ tj-m+1 ~ tj ], 这样就保证了子串的头与尾在m长度时是相等的,也就可以考虑下一步:T[ m ]与T[ j ]是否相等,若相等,则重复情况1°,否则,再次重复情况2° ,因此,只要让k回溯到m,也就是k=next[ k ],再继续往后考虑即可。

注:不必考虑Next[ k ]是多少,是否存在,因为我们每一个Next[ t ]都是根据其前一个元素求出来的,因此只要Next[ t ] != -1,那么Next[ t-1 ]就存在,继续循环即可。

因此可以总结:T[ k ] = T[ j ]时,Next[ j+1 ] = Next [ j ] + 1 = k + 1
T[ k ] != T[ j ]时,k退回至k = Next [ k ],再继续考虑

数组优化

在这之前,首先说明Next数组的含义:当主串Si与副串Tj匹配失败时,j退回至Next[ j ]处继续与主串配对。

但当:
在这里插入图片描述
容易求出Next数组为[-1,0,0,1]
我们可以看到,此时Si与Tj并不匹配,此时j=3所以下一步应该把j移到Next[3] = 1处,也就变成了:在这里插入图片描述
发现此时还是不匹配,这也是肯定的,刚刚的B与主串的C不匹配,现在又来一个B,怎么可能匹配呢。正是在这种思想下,Next数组的优化——Nextval数组诞生了:
若Next[ j ] = k,此时如果j要回溯,那么应退至j=Next[ j ]处,亦即k处,而如果Tk=Tj,不用比较也知道Tk != Si,因为导致j回退原因就是Tj != Si 此时 j 又要回退至Next[ k ]处,也就是j=Next[Next[ j ]]处,如果又是一样,那还是要继续回退,那其实一开始就没有回退这么多次的必要,于是Nextval数组诞生了:

如果Tj = TNext[j] =Tk (Next[ j ] = k),那么Nextval[ j ] = Nextval[ k ]
如果Tj != Tk ,那么 Nextval[ j ] = Next[ j ]

当Tj 和Tk 相等的时候,假设Nextval[ k ] = m。当j回退时,本来应该先回退到k处,再退至m处,但是由于Nextval[ j ] =Nextval[ k ] = m,那么j就直接回退至m处,也就是 跳过了回退至k处这一步, 这便是优化。
而当Tj 和 Tk 不相等的时候,由于Next[ j ] = k,此时不能跳过回退至k这一步, 所以j应该回退至k,也就是Nextval[ j ] = k = Next[ j ]

需要说明的是: Nextval[ j ]的意义也是当Si 与 Tj 无法配对时,j回退至Nextval[ j ]处继续配对。


当初学kmp算法看了一整天,看了十几篇博客才看懂,这篇博客里的图也来自于让我看懂的那篇博客:点这里
在此特别鸣谢

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐