加载笔记内容...
加载笔记内容...
在计算机科学中,字符串匹配是一个基础且重要的问题。传统的暴力匹配算法(Brute-Force)时间复杂度为O(mn),当处理大规模文本时效率堪忧。Knuth-Morris-Pratt算法(简称KMP)通过引入前缀函数(prefix function)这一核心概念,将时间复杂度优化到O(n+m),实现了质的飞跃。
考虑在文本"ABABABABC"中查找模式"ABABC":
1文本指针i: A B A B A B A B C
2模式指针j: A B A B C
当匹配到第5个字符时发现不匹配,暴力算法会将i回退到起始位置+1,j重置为0。这种回溯机制造成了大量重复比较。
KMP算法的革命性在于发现:已匹配的字符包含足够信息来确定下一个可能的匹配位置。通过预处理模式串生成next数组(失败函数),算法可以在不回溯文本指针的情况下继续匹配。
对于模式串P[0..m-1],其前缀函数π[q]定义为:
P[0..q]的最长真前缀(proper prefix)同时是该子串的后缀的最大长度
示例:模式串"ababaca"的next数组:
1索引: 0 1 2 3 4 5 6
2字符: a b a b a c a
3next: 0 0 1 2 3 0 1
next数组的构建过程本质上是模式串的自匹配,通过双指针技术实现动态规划:
1function buildNext(pattern: string): number[] {
2 const next = new Array(pattern.length).fill(0);
3 let k = 0;
4 for (let q = 1; q < pattern.length; q++) {
5 while (k > 0 && pattern[k] !== pattern[q]) {
6 k = next[k-1]; // 关键回退操作
7 }
8 if (pattern[k] === pattern[q]) k++;
9 next[q] = k;
10 }
11 return next;
12}
关键点解析:
不同实现中next数组可能有偏移量差异,常见三种形式:
工程选择建议:
1function kmpSearch(text: string, pattern: string): number {
2 if (pattern.length === 0) return 0;
3 const next = buildNext(pattern);
4 let q = 0; // 模式串指针
5
6 for (let i = 0; i < text.length; i++) {
7 while (q > 0 && text[i] !== pattern[q]) {
8 q = next[q-1]; // 失配时回退
9 }
10 if (text[i] === pattern[q]) {
11 if (++q === pattern.length) {
12 return i - q + 1; // 完全匹配
13 }
14 }
15 }
16 return -1;
17}
案例1:next数组构建错误
1// 错误实现:缺少回退循环
2function wrongNext(pattern) {
3 let next = [0], j = 0;
4 for (let i = 1; i < pattern.length; i++) {
5 if (pattern[i] === pattern[j]) {
6 j++;
7 } else {
8 j = 0; // 应回退到next[j-1]
9 }
10 next[i] = j;
11 }
12 return next;
13}
解决方案:严格遵循动态规划的回退逻辑
案例2:处理Unicode字符 当模式包含多字节字符(如emoji)时,需要先转换为码点数组处理
1function safeKmp(text: string, pattern: string): number {
2 if (typeof text !== 'string' || typeof pattern !== 'string') {
3 throw new TypeError('Inputs must be strings');
4 }
5 // ...原有逻辑...
6}
Q:KMP算法是否总是优于暴力算法? A:在小字符集(如DNA序列)或短模式串场景下,Boyer-Moore或Sunday算法可能更优。建议通过基准测试选择算法。
Q:如何处理包含通配符的模式? A:需要扩展next数组的定义,例如:
1function wildcardNext(pattern: string) {
2 // 处理包含'?'通配符的情况
3 // 修改比较逻辑:pattern[k] === pattern[q] || pattern[k] === '?'
4}
KMP算法的精妙之处在于将模式分析转化为确定有限自动机(DFA),这种"以空间换时间"的思想深刻影响了后续算法设计。理解其核心——前缀函数,不仅有助于掌握字符串匹配的本质,更能培养解决复杂问题的系统思维。
推荐阅读:
- 《算法导论》第32章 - 字符串匹配
- Knuth D E, Morris J H, Pratt V R. Fast pattern matching in strings[J]. 1977 (原始论文)
- Crochemore, M., & Rytter, W. (2002). Jewels of stringology