KMP算法高效字符串搜索 | AI生成和翻译

Home 2025.10

理解 Knuth-Morris-Pratt (KMP) 算法

你的直觉很准确:KMP 的核心在于不浪费字符串搜索过程中的部分匹配结果。当你在长文本(如 “ababcabc”)中查找模式(如 “abc”)时,朴素方法每次遇到不匹配就从头开始重新比较,效率很低。KMP 巧妙地”记住”已经匹配了多少模式内容,然后跳过冗余比较直接前移。这使得算法非常快——达到线性时间复杂度 O(n + m),其中 n 是文本长度,m 是模式长度。

我将通过一个简单示例逐步解析。我们在文本 T = "ababababc" 中搜索模式 P = "abab"(该模式出现在位置 0 和 2)。

第一步:问题描述与朴素解法

在最坏情况下(例如在 “aaaaa…a” 中搜索 “aaa…ab”),这种方法可能达到 O(n*m) 的时间复杂度。

第二步:KMP 的核心思想——前缀表(或称”失效函数”)

KMP 预先为模式 P 计算一个表 π(pi)。该表告诉你,对于 P 中的每个位置 i,P[0..i] 的最长真前缀(同时是后缀)的长度。换句话说:”如果在此处不匹配,我们可以通过跳转到这个重叠的前缀来复用多少部分匹配内容?”

对于 P = "abab"

位置 i P[0..i] 最长真前缀 = 后缀 π[i]
0 “a” 无(单字符) 0
1 “ab” 0
2 “aba” “a”(前缀 “a” == 后缀 “a”) 1
3 “abab” “ab”(前缀 “ab” == 后缀 “ab”) 2

第三步:构建前缀表 (π)

这一步在 O(m) 时间内完成。它类似于用类似的逻辑让 P 与自身进行搜索。

伪代码:

def compute_prefix_function(P):
    m = len(P)
    pi = [0] * m
    k = 0  # 当前前缀-后缀匹配的长度
    for i in range(1, m):
        while k > 0 and P[k] != P[i]:
            k = pi[k-1]  # 跳转到前一个重叠(复用!)
        if P[k] == P[i]:
            k += 1
        pi[i] = k
    return pi

对于 “abab”:

第四步:使用前缀表进行搜索

现在使用 Pπ 搜索 T

伪代码:

def kmp_search(T, P):
    n, m = len(T), len(P)
    if m == 0: return []
    pi = compute_prefix_function(P)
    q = 0
    matches = []
    for i in range(n):
        while q > 0 and P[q] != T[i]:
            q = pi[q-1]
        if P[q] == T[i]:
            q += 1
        if q == m:
            matches.append(i - m + 1)  # 起始位置
            q = pi[q-1]  # 为下一次搜索处理重叠
    return matches

对于 T = "ababababc"P = "abab"

看到了吗?在不匹配时(比如如果有不匹配发生),它会跳转到 q=π[q-1] 而不是 q=0。

为什么它感觉如此神奇

动手尝试:简单 Python 代码

def compute_prefix_function(P):
    m = len(P)
    pi = [0] * m
    k = 0
    for i in range(1, m):
        while k > 0 and P[k] != P[i]:
            k = pi[k - 1]
        if P[k] == P[i]:
            k += 1
        pi[i] = k
    return pi

def kmp_search(T, P):
    n, m = len(T), len(P)
    if m == 0: return []
    pi = compute_prefix_function(P)
    q = 0
    matches = []
    for i in range(n):
        while q > 0 and P[q] != T[i]:
            q = pi[q - 1]
        if P[q] == T[i]:
            q += 1
        if q == m:
            matches.append(i - m + 1)
            q = pi[q - 1]
    return matches

# 测试
T = "ababababc"
P = "abab"
print(kmp_search(T, P))  # 输出: [0, 2, 4]

运行这段代码——调整 P/T 来观察跳转过程(可以添加打印语句输出 q 的值)。

常见陷阱与技巧

现在你应该明白了——它就是通过预计算的重叠来实现智能跳转。如果你有具体示例遇到困难,欢迎分享!

GeeksforGeeks: KMP 算法
维基百科: Knuth–Morris–Pratt 算法


Back

x-ai/grok-4-fast

Donate