KMP字符串查找算法

发表于 · 归类于 代码 · 阅读完需 7 分钟 · 报告错误 · 阅读:

KMP(Knuth-Morris-Pratt)字符串查找算法

KMP算法可在一个字符串s中查找一个词W的出现位置。一个词在不匹配时本身就包含足够的信息来确定下一个匹配可能的开始位置,此算法利用这一特征以避免重新检查先前配对的字符。

这种算法不太容易理解,我也是翻阅了很多资料,才理解。本篇通过一个例子解释什么是KMP。

 

KMP原理

举例来说,有一个字符串”BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包含另一个字符串”ABCDABD”?

kmp

kmp

kmp

kmp

kmp

kmp

kmp

kmp

kmp

查表可知,最后一个匹配字符B对应的”部分匹配值”为2,因此按照下面的公式算出向后移动的位数:

移动位数 = 已匹配的字符数 - 对应的部分匹配值

因为 6 - 2 等于4,所以将搜索词向后移动4位。

kmp

kmp

kmp

kmp

首先,要了解两个概念:”前缀”和”后缀”。 “前缀”指除了最后一个字符以外,一个字符串的全部头部组合;”后缀”指除了第一个字符以外,一个字符串的全部尾部组合。

kmp

kmp

kmp

 

具体代码

#!/usr/bin/env python
#-*- coding:utf-8 -*-

def kmp_match(s, p):
    match_idx = []
    m = len(s)      # 完整字符串
    n = len(p)      # 需要查询字串
    cur = 0         # 搜索起始指针
    table = partial_table(p)    # 字串匹配表
    while cur <= m-n:           # 只去匹配前m-n个
        for i in range(n):      # 按字串长度进行匹配,如果字串没有匹配上,向后位移
            if s[i+cur] != p[i]:
                cur += max(i - table[i-1], 1)  # 移动位数 = 已匹配的字符数 - 对应的部分匹配值
                break
        else:
            # 匹配上!!!
            match_idx.append(cur)
            cur+=1
    return match_idx


# 部分匹配表
def partial_table(p):
    prefix = set()
    postfix = set()
    ret = [0]   # 第一位无须匹配,从第二位开始
    for i in range(1, len(p)):
        prefix.add(p[:i])                                   # 根据匹配子串长度,依次更新前缀集合
        postfix = {p[j:i+1] for j in range(1, i+1)}         # 每次循环子串,更新后缀集合
        ret.append(len((prefix & postfix or {''}).pop()))   # 每循环一次更新字串同时出现在前缀和后缀的字串,返回其长度   
    return ret


def main():
    s = "ABCDEABC"
    p = "ABC"
    kmp_match(s, p)


if __name__ == "__main__":
    main()