算法简介
KMP算法即字符串匹配算法,主要解决在一个主串S中找到子串T的起始位置。
该算法是三位大牛:D.E.Knuth、J.H.Morris和V.R.Pratt同时发现的,以其名字首字母命名。
算法引入
在学习KMP算法前,我们应该先了解前缀函数。
简单来说,前缀函数是一个长度为n+1的列表next[i],存储的是子串(1~i)的最长真前缀长度。
最长真前缀:该前缀既是字符串的前缀,同时也是字符串的后缀。
例:ABCBA 最长真前缀为1,即 $A-BCB-A$
ABCAB 最长真前缀为2,即 $AB-C-AB$
注意,不存在第0个字母,所以next[0]是无意义的,为了方便遍历,我们规定next[0]=-1
例:ABCABCD 的前缀函数为 [-1,0,0,0,1,2,3,0]
AABAAAB 的前缀函数为 [-1,0,1,0,1,2,2,3]
理解了前缀函数,我们先上它的实现代码。
def get_next(s):
next = [[] for i in range(len(s)+1)]
j = 0
k = -1
n = len(s)
next[0] = -1
while j < n:
if k == -1 or s[j] == s[k]:
j += 1
k += 1
next[j] = k
else:
k = next[k]
return next
流程介绍:
· 以 j = 1 到 j = n 的顺序计算前缀函数next[j]的值。
· 为了计算 next[i] 的值,我们令 k 表示右端点位于 j 的最长真前缀长度,即 k 表示字串(1~i)的最长真前缀长度。
· 通过比较 s[j] 和 s[k] 来检查 j 的后缀是否同时也是前缀。如果两者相等,我们让 next[j]=k;不相等,我们让 k=next[k],并重复此过程。
· 初始时k=-1,我们让next[j]=0,并移至j+1。
因为next[k]记录的是子串(1~k)的最长真前缀长度,当不匹配时,我们可以直接将k回溯到 next[k] ,即回溯到该点最长真前缀长度,减少不必要的遍历。
KMP算法
学习完前缀函数,我们首先计算出配对串t的前缀函数,然后进行配对。
其中 i-m+1 为字串 t 在字串 s 中第一次出现的位置
def KMP(s,t):
i = 0
j = 0
n = len(s)
m = len(t)
while i < n and j < m:
if j == -1 or s[i] == t[j]:
i+=1
j+=1
else:
j = next[j]
if j == m:
return i-m+1
else:
return -1
当计算字串 t 在字串 s 中所有出现的位置,可以将代码修改为:
def KMP(s,t):
i = 0
j = 0
n = len(s)
m = len(t)
while i < n and j < m:
if j == -1 or s[i] == t[j]:
i+=1
j+=1
else:
j = next1[j]
if j == m:
ans.append(i-m+1)
if i + m <= n:
i -= next1[j] # i应回溯最大真前缀长度
j = 0
最后,KMP算法其实很简单,就是有点绕,实在不行就背一背板子叭。