关于KMP算法,相信大家都有所了解。它也是我们在数据结构书上遇到的第一个
比较繁琐的问题。希望我能通过这篇文章对大家有所启发。
KMP算法主要找到子串在模式串中第一次出现的位置,也是我们在实际情况中
经常会遇见的一个问题。闲话少谈,我们来看以下例子。
现假设有以下两个字符串s1, s2。
s1 | a | b | a | b | c | a | c | c |
---|---|---|---|---|---|---|---|---|
s2 | a | b | c | a |
对于暴力求解方式来说,我们只需要对s1和s2这两个字符串逐个进行比较。
如果两个字符相同,则比较下一个字符。如果不相同,
则将s2子串向后移位,再重新进行比较。直到将s2遍历完或s2的尾端对齐s1的尾端,则算法结束。
其大致的执行过程如下图所示。
我们可以很清楚的看到再进行第二次判断时,指针会有回溯
(即s1指针从指向a反向跳转为指向b)。那么我们能不能找到一个方法,让指向s1值的指针不回溯呢?
KMP算法就因此而产生。
要想了解kmp算法,我们得先知道相同最长前缀和最长后缀的概念。
对应下表s1字符串中的来说,其相同的最长前缀和后缀的值如下表所示。
s1 | a | b | a | b | c |
---|---|---|---|---|---|
-1 | 0 | 0 | 1 | 2 |
我们以s1串中的c字符为例:
c字符前缀: c字符后缀:
a b
ab ab
aba bab
abab abab
去除一定相同的abab,它相同的最大的前缀串和后缀串为ab,长度为2。
各个值的相同最长前缀和后缀构成了一个next数组。
KMP基本的思想是利用next数组,使得字符串直接从不匹配的地方开始接着进行匹配。
如图示例:
我们已经知道了next数组的基本概念和如何计算,那计算机该如何实现呢?
我们可以用动态规划的方式加以考虑。
对于一个字符串p,假设我们已经知道:
|
如果此时Pk = Pj 则next[j+1] = next[j]+1 又得知next[j] = k-1,故next[j+1] = k
如果此时Pk != Pj,又会怎样呢?
我们可令k = next[k] ;如果p[k] = p[j] 则next[j+1] = k+1否则重复此过程。此处可能不太好理解。
我们直接来看图
|
关于不相等的时候递归确实比较难以理解,大家可以多画画图。加深自己的理解。
def gen_next(s2):
k = -1
n = len(s2)
j = 0
next_list = [0 for i in range(n)]
next_list[0] = -1 #next数组初始值为-1
while j < n-1:
if k == -1 or s2[k] == s2[j]:
k += 1
j += 1
next_list[j] = k #如果相等 则next[j+1] = k
else:
k = next_list[k] #如果不等,则将next[k]的值给k
return next_list
def match(s1, s2, next_list):
ans = -1
i = 0
j = 0
while i < len(s1):
if s1[i] == s2[j] or j == -1:
i += 1
j += 1
else:
j = next_list[j]
if j == len(s2):
ans = i - len(s2)
break
return ans
if __name__ == '__main__':
s1 = 'ababaacc'
s2 = 'abaa'
next_list = gen_next(s2)
print(next_list)
print(match(s1, s2, next_list))
关于kmp还有一个改进的next数组,即倘若p[k] = p[j]的时候直接将next[k]的值赋给next[j+1],
大家不妨思考一下这么做会有什么好处。