【Python】KMP算法
KMP算法详解:高效字符串匹配的终极指南
本文通过大量图表和清晰概念,深入解析KMP算法的核心原理与实现细节
算法定义
KMP算法(Knuth-Morris-Pratt算法)是一种高效的字符串匹配算法,由D.E.Knuth、J.H.Morris和V.R.Pratt于1977年联合发表。该算法通过消除主串指针的回溯,将字符串匹配的时间复杂度优化到O(n+m)。
核心思想
传统暴力匹配的问题
主串: A B A B A B C
模式: A B A B C暴力匹配过程:
1. A B A B A B CA B A B C ✗ (第5个字符不匹配)2. A B A B A B C A B A B C ✗ (完全重新开始)3. A B A B A B CA B A B C ✗4. A B A B A B CA B A B C ✓
问题:每次失配都从模式串头部重新开始,效率低下
KMP的智能跳跃
主串: A B A B A B C
模式: A B A B CKMP匹配过程:
1. A B A B A B CA B A B C ✗ (第5个字符不匹配)2. A B A B A B C A B A B C ✓ (利用已匹配信息直接跳跃)
核心:利用已匹配部分的信息,避免主串指针回溯
关键概念解析
1. 部分匹配表(next数组)
定义:对于模式串P,next[i]
表示子串P[0:i]
的最长相等真前缀和真后缀的长度。
构建过程图解(模式串:“ABABC”)
模式串: A B A B C
索引: 0 1 2 3 4next数组构建:
i=0: "A"前缀: [] 后缀: [] → 长度: 0 → next[0]=0i=1: "AB" 前缀: ["A"] 后缀: ["B"] → 无公共 → next[1]=0i=2: "ABA"前缀: ["A","AB"] 后缀: ["A","BA"] 公共: "A" → 长度:1 → next[2]=1i=3: "ABAB"前缀: ["A","AB","ABA"]后缀: ["B","AB","BAB"] 公共: "AB" → 长度:2 → next[3]=2i=4: "ABABC"前缀: ["A","AB","ABA","ABAB"]后缀: ["C","BC","ABC","BABC"]无公共 → next[4]=0
最终next数组: [0, 0, 1, 2, 0]
2. 真前缀与真后缀
字符串: "ABAB"真前缀: "A", "AB", "ABA" (不包含自身)
真后缀: "B", "AB", "BAB" (不包含自身)最长公共: "AB" (长度=2)
算法执行流程
完整匹配过程演示
主串 S: “ABABABC”
模式串 P: “ABABC”
next数组: [0, 0, 1, 2, 0]
步骤1: 初始状态
S: A B A B A B C↑i=0
P: A B A B C↑j=0
状态: S[0]=P[0] ✓ → i=1, j=1步骤2:
S: A B A B A B C↑i=1
P: A B A B C↑j=1
状态: S[1]=P[1] ✓ → i=2, j=2步骤3:
S: A B A B A B C↑i=2
P: A B A B C↑j=2
状态: S[2]=P[2] ✓ → i=3, j=3步骤4:
S: A B A B A B C↑i=3
P: A B A B C↑j=3
状态: S[3]=P[3] ✓ → i=4, j=4步骤5: 失配处理 ✗
S: A B A B A B C↑i=4
P: A B A B C↑j=4
状态: S[4]≠P[4] ✗ → j=next[j-1]=next[3]=2步骤6: 跳跃后继续
S: A B A B A B C↑i=4
P: A B A B C↑j=2
状态: S[4]=P[2] ✓ → i=5, j=3步骤7:
S: A B A B A B C↑i=5
P: A B A B C↑j=3
状态: S[5]=P[3] ✓ → i=6, j=4步骤8:
S: A B A B A B C↑i=6
P: A B A B C↑j=4
状态: S[6]=P[4] ✓ → 匹配成功!
代码实现
Python实现
class Solution:def getNext(self, next_arr, s):j = -1next_arr[0] = jfor i in range(1, len(s)):while j >= 0 and s[i] != s[j+1]:j = next_arr[j]if s[i] == s[j+1]:j += 1next_arr[i] = jdef strStr(self, haystack: str, needle: str) -> int:if not needle:return 0next_arr = [0] * len(needle)self.getNext(next_arr, needle)j = -1for i in range(len(haystack)):while j >= 0 and haystack[i] != needle[j+1]:j = next_arr[j]if haystack[i] == needle[j+1]:j += 1if j == len(needle) - 1:return i - len(needle) + 1return -1# 测试代码
if __name__ == "__main__":solution = Solution()S = 'ABABABC'W = 'ABABC'print(solution.strStr(S, W)) # 应该输出 2# 更多测试用例print(solution.strStr("hello", "ll")) # 应该输出 2print(solution.strStr("aaaaa", "bba")) # 应该输出 -1print(solution.strStr("", "")) # 应该输出 0
算法复杂度分析
操作 | 时间复杂度 | 空间复杂度 |
---|---|---|
构建next数组 | O(m) | O(m) |
匹配过程 | O(n) | O(1) |
总计 | O(n+m) | O(m) |
对比传统算法:
- 暴力匹配:O(n×m)
- KMP算法:O(n+m)