KMP算法(Knuth–Morris–Pratt Algorithm),Knuth就是高德纳。
KMP算法是从朴素匹配算法改进而来:就是穷搜匹配。
朴素匹配算法:
原串:ABCDE 模式串:ACD
1. 将原串和模式串左对齐,然后一位一位比较,直到有一个字符不匹配
2. 发现第二位的B和C不匹配,模式串右移一位
3. 重复这个流程,直到找到完全匹配的子串或者匹配失败。
朴素匹配算法的不足:
就是不能够很直观的进行有移动的大跨步,也就是明明是不匹配的,但是不能跳过去,不够聪明。
例子:ABCDEABCDF 和 ABCDF。遇到E和F时候冲突,
此时明显人能直到应该直接调到E后面的A开始啊。
那是不是跳过的长度就是前面相同部分的长度呢?
其实不是这样的。
原串为ABCDABCDABF,模式串为ABCDABF,直接跳过相同部分就会遗漏匹配的串,所以跳过的长度并不是前面完全匹配的部分
那是不是跳过的长度如何定下来?
其实就是人之所以强大是可以马上发现下一个匹配开始的位置,比如模式串是A开头,那么匹配串肯定每次都是找到原串下一个A开始的继续匹配。机器不知道下一个究竟在哪,怎么跳过去合适。
也就是机器怎么靠近人的眼睛呢。直接发现下一个合理的开始位置呢,也就是如何跳到那个合理位置呢?这就是KMP算法解决的.
KMP算法
KMP算法需要对模式串进行预处理。
把可以跳过的长度一般存储在模式串的partial match table中。
例如:ABCDABF的partial match table如下:
可以跳过的长度 = 当前模式串已匹配长度 - 模式串最后一个字母在partial match table中的值。例如:
当发现C和F不匹配时,根据公式,当前已匹配串ABCDAB长度为6, 最后一个字母B在partial match table中的对应值为2,所以可以跳过的长度 = 6 - 2 = 4,即:
【partial match table是如何构建的?】
每个位置i记录的其实就是从0到i的子串中,同时出现在子串前缀和后缀中的最大长度。还是以上面那个例子为例:
- A没有前缀或后缀,所以长度为0
- AB前缀为A,后缀为B,没有相同部分,长度为0
- ABC前缀为A、AB,后缀为BC、C,没有相同部分,长度为0
- ABCD前缀为A、AB、ABC,后缀为BCD、CD、D,没有相同部分,长度为0
- ABCDA前缀为A、AB、ABC、ABCD,后缀为BCDA、CDA、DA、A,相同部分为A,长度为1
- ABCDAB前缀为A、AB、ABC、ABCD、ABCDA,后缀为BCDAB、CDAB、DAB、AB、B,相同部分为AB,长度为2
- ABCDABF前缀为A、AB、ABC、ABCD、ABCDA、ABCDAB,后缀为BCDABF、BCDAB、CDAB、DAB、AB、B,没有相同部分,长度为0
demo
'''
Knuth-Morris-Pratt algorithm
'''
def getPrefix(pattern):
'''
@param pattern: pattern string.
@return: prefix array.
'''
length = len(pattern)
prefix = [0] * length
for i in range(1, length):
k = prefix[i-1]
while k > 0 and pattern[i] != pattern[k]:
k = prefix[k-1]
if pattern[i] == pattern[k]:
prefix[i] = k + 1
return prefix
def KMP(src, pattern):
prefix = getPrefix(pattern)
print(prefix)
i = j = 0
src_len = len(src)
pattern_len = len(pattern)
if src_len < pattern_len:
return -1
while i < src_len:
while src[i] == pattern[j]:
if j == pattern_len - 1:
return i - j
else:
i += 1
j += 1
if j > 0:
j = prefix[j-1]
else:
i += 1
return -1
src = 'bbc abcdab abcdabcdabde'
pattern = input('pattern: ')#'abcdabd'
#print(KMP(src, pattern))
print(getPrefix(pattern))
【延展】
这类问题的思路是解决重复,有点像qucik sort消除逆序对。