1.KMP算法定义
KMP算法是由D.E.Knuth、J.H.Morris和V.R.Pratt发明的一种字符串匹配算法,因此命名为KMP算法。
2.KMP算法求解的问题
KMP算法主要是为了找到子串P在主串T中的位置,即查找主串是否包含子串,如果包含,则返回包含的起始位置。
3.KMP算法原理
上图可以看到,当已经匹配了部分信息后,发现不能完全匹配,此时保持i指针不回溯,只移动j指针,让子串尽量移动到有效的位置。
所以KMP算法的重点在于j指针下一步移动到哪里?
上图,可以看到C和D不匹配了,要把j移动到B的位置,即下标为1的位置,因为前面的A可以继续匹配。
当匹配失败时,j要移动的下一个位置如何计算?
假设j要移动的下一个位置为k,则下标为[0~k-1]和下标为[j-k,j-1]之间的字符是相同的。
怎么求k呢?因为在子串P的每一个位置都可能发生不匹配,也就是说我们要计算每一个位置j对应的k,所以用一个数组next来保存,next[j] = k,表示当T[i] != P[j]时,j指针的下一个位置。
4.KMP算法实现
#求解next数组
def cal_next(ptr):
next = [-1]
# ptr第一位的没有最长前后缀,直接赋值为-1
k = -1
# k代表最长前后缀长度,赋值为-1
for p in range(1, len(ptr)):
# 从第二位开始遍历ptr
while k>-1 and ptr[p]!=ptr[k+1]:
# 假设已有最长前缀为A,最长后缀为B,B的下一位ptr[p] != A的下一位ptr[k+1]
# 说明最长前后缀不能持续下去
k = next[k]
# 往前回溯,尝试部分前缀,而非从第一位开始重新寻找最长前缀
if ptr[p] == ptr[k+1]:
# 如果A B的下一位相同
k = k + 1
# 最长前后缀长度 + 1
next.append(k)
# 第p位的最长前后缀赋值为k
print('next: ', next)
return next
#KMP算法
def kmp(str, ptr):
next = cal_next(ptr) # 求解next
k = -1 # 此处k相当于ptr中已匹配的长度,类似一个指针指向ptr中已匹配的最后一位
num = 0 # str中ptr的数量
for p in range(len(str)):
# 遍历str
while k>-1 and str[p] != ptr[k+1]:
# 假设str中的A片段和ptr中的前A位已匹配,但A的下一位和ptr中A+1位不匹配
k = next[k]
# 放弃A片段中的前若干位,因为它们不可能再匹配了
# 用A片段的后若干位去匹配ptr中某一个最大前缀,像上面矩形图所示
if str[p] == ptr[k+1]:
# 如果A的下一位和ptr中A+1位相匹配
k = k+1
if k == len(ptr)-1: # 如果ptr走到尽头
num = num + 1 # 匹配到了一个
k = next[k]
return num