KMP匹配算法

KMP算法(Knuth–Morris–Pratt Algorithm),Knuth就是高德纳。
KMP算法是从朴素匹配算法改进而来:就是穷搜匹配。

朴素匹配算法:

原串:ABCDE 模式串:ACD
1. 将原串和模式串左对齐,然后一位一位比较,直到有一个字符不匹配
这里写图片描述
2. 发现第二位的B和C不匹配,模式串右移一位
这里写图片描述
3. 重复这个流程,直到找到完全匹配的子串或者匹配失败。

朴素匹配算法的不足:

就是不能够很直观的进行有移动的大跨步,也就是明明是不匹配的,但是不能跳过去,不够聪明。
例子:ABCDEABCDF 和 ABCDF。遇到E和F时候冲突,
这里写图片描述
此时明显人能直到应该直接调到E后面的A开始啊。
这里写图片描述

那是不是跳过的长度就是前面相同部分的长度呢?

其实不是这样的。
原串为ABCDABCDABF,模式串为ABCDABF,直接跳过相同部分就会遗漏匹配的串,所以跳过的长度并不是前面完全匹配的部分
KMP5.png

那是不是跳过的长度如何定下来?

其实就是人之所以强大是可以马上发现下一个匹配开始的位置,比如模式串是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消除逆序对。

原文地址:https://www.cnblogs.com/itrena/p/7434099.html