KMP 算法是一种高效的字符串匹配算法,由 Knuth、Morris 和 Pratt 共同提出,其核心思想是通过预处理模式串,减少匹配过程中的回溯次数,时间复杂度可达 O (n+m)(n 为主串长度,m 为模式串长度)。
一、核心原理
- 问题背景:传统暴力匹配在失配时,主串和模式串都要回溯,效率低下。
- 优化思路:失配时,主串指针不回溯,仅调整模式串指针,利用已匹配的前缀信息快速定位下次匹配位置。
- 关键工具:前缀函数(Prefix Function,又称部分匹配表 PMT),用于记录模式串中每个位置的最长前缀(不含最后字符)与后缀(不含首字符)的公共长度。
二、前缀函数(Prefix Function)
前缀函数π[i]定义:模式串pattern[0..i]中,最长的相等前缀子串与后缀子串的长度。
计算规则:
π[0] = 0(单个字符无前缀 / 后缀)- 对
i > 0:- 初始化
j = π[i-1] - 若
pattern[i] == pattern[j],则π[i] = j + 1 - 否则,令
j = π[j-1],重复步骤 2,直到j = 0;若仍不匹配,则π[i] = 0
三、KMP 匹配流程
- 预处理模式串,计算前缀函数
π - 初始化主串指针
i = 0,模式串指针j = 0 - 循环匹配:
- 若
text[i] == pattern[j],则i++,j++ - 若
j == m(模式串匹配完成),记录匹配位置i - j,令j = π[j-1]继续查找 - 若失配:
- 若
j > 0,令j = π[j-1](模式串回退到最长前缀位置) - 若
j = 0,则i++(主串后移)
四、示例演示
主串:text = "ABABABC"
模式串:pattern = "ABABC"
计算前缀函数 π:
π[0] = 0("A")π[1] = 0("AB":前缀 "A"≠后缀 "B")π[2] = 1("ABA":前缀 "A"= 后缀 "A")π[3] = 2("ABAB":前缀 "AB"= 后缀 "AB")π[4] = 0("ABABC":前缀与后缀无匹配)- 结果:
π = [0,0,1,2,0]
匹配过程:
- 初始:
i=0, j=0,匹配 "A"→i=1, j=1 - 匹配 "B"→
i=2, j=2 - 匹配 "A"→
i=3, j=3 - 匹配 "B"→
i=4, j=4 - 失配(text [4]="A" vs pattern [4]="C"),
j = π[3] = 2 - 匹配 text [4]="A" vs pattern [2]="A"→
i=5, j=3 - 匹配 text [5]="B" vs pattern [3]="B"→
i=6, j=4 - 匹配 text [6]="C" vs pattern [4]="C"→
j=5 == m=5,找到匹配位置6-5=1
五、代码实现
def compute_prefix(pattern):
m = len(pattern)
pi = [0] * m
for i in range(1, m):
j = pi[i-1]
while j > 0 and pattern[i] != pattern[j]:
j = pi[j-1]
if pattern[i] == pattern[j]:
j += 1
pi[i] = j
return pi
def kmp_search(text, pattern):
n = len(text)
m = len(pattern)
if m == 0:
return []
pi = compute_prefix(pattern)
matches = []
j = 0 # 模式串指针
for i in range(n): # 主串指针
while j > 0 and text[i] != pattern[j]:
j = pi[j-1]
if text[i] == pattern[j]:
j += 1
if j == m:
matches.append(i - m + 1) # 记录匹配起始位置
j = pi[j-1] # 继续查找下一个匹配
return matches
# 示例
text = "ABABABC"
pattern = "ABABC"
print(kmp_search(text, pattern)) # 输出:[1]