引言:什么是KMP算法?
在字符串处理问题中,最经典的场景之一是在一段文本中查找某个子串的位置。朴素的匹配方法(逐个字符比对)虽然简单,但在面对大规模数据时效率极低。这时,KMP算法(Knuth-Morris-Pratt)登场了。
KMP算法的核心优势在于:
- 时间复杂度为 O(n + m) ,其中 n 是文本长度,m 是模式串长度。
- 通过预处理模式串,避免了不必要的字符比较,达到高效匹配。
本文将从以下几个方面带你深入了解KMP算法:
- KMP的工作原理与实现
- 经典KMP相关算法题及解法
- KMP算法的实际应用场景
一、KMP算法的原理与实现
1.1 前缀函数(Partial Match Table)
KMP算法的关键在于部分匹配表(前缀函数)的构建。这张表记录了模式串中最长相等的前后缀长度,用于在字符不匹配时跳过已匹配的部分,避免回退到开头。
假设模式串为 P = "ABABCABAA"
,其部分匹配表如下:
索引 | 字符 | 前缀函数值 |
---|---|---|
0 | A | 0 |
1 | B | 0 |
2 | A | 1 |
3 | B | 2 |
4 | C | 0 |
5 | A | 1 |
6 | B | 2 |
7 | A | 3 |
8 | A | 1 |
1.2 KMP算法实现
完整实现(JavaScript)
function computePrefixFunction(pattern) {
const m = pattern.length;
const prefix = new Array(m).fill(0);
let j = 0; // 当前前缀长度
for (let i = 1; i < m; i++) {
while (j > 0 && pattern[i] !== pattern[j]) {
j = prefix[j - 1]; // 回退
}
if (pattern[i] === pattern[j]) {
j++;
}
prefix[i] = j;
}
return prefix;
}
function kmpSearch(text, pattern) {
const n = text.length;
const m = pattern.length;
const prefix = computePrefixFunction(pattern);
const result = [];
let j = 0;
for (let i = 0; i < n; i++) {
while (j > 0 && text[i] !== pattern[j]) {
j = prefix[j - 1]; // 回退
}
if (text[i] === pattern[j]) {
j++;
}
if (j === m) {
result.push(i - m + 1); // 记录匹配位置
j = prefix[j - 1]; // 继续匹配
}
}
return result;
}
// 示例
console.log(kmpSearch("ABABDABACDABABCABAA", "ABABCABAA")); // [10]
二、KMP相关的经典算法题
2.1 字符串匹配
- 题目:给定文本
text
和模式串pattern
,找出所有匹配的位置。 - 解法:直接使用KMP算法匹配。
代码示例
console.log(kmpSearch("AABAACAADAABAAABAA", "AABA")); // [0, 9, 13]
2.2 判断字符串是否为循环移位
- 题目:给定两个字符串,判断一个字符串是否可以通过循环移位得到另一个字符串。
- 解法:将
s1 + s1
拼接,检查s2
是否为子串。
代码示例
function isRotation(s1, s2) {
if (s1.length !== s2.length) return false;
return kmpSearch(s1 + s1, s2).length > 0;
}
console.log(isRotation("waterbottle", "erbottlewat")); // true
2.3 最长前缀后缀匹配
- 题目:求字符串中最长的前缀和后缀相等的长度。
- 解法:直接返回前缀函数数组的最后一个值。
代码示例
function longestPrefixSuffix(s) {
const prefix = computePrefixFunction(s);
return prefix[s.length - 1];
}
console.log(longestPrefixSuffix("ababab")); // 4
2.4 字符串重复子串检测
- 题目:判断一个字符串是否可以通过重复子串构成。
- 解法:将字符串拼接两次,去头尾后检查原字符串是否为子串。
代码示例
function repeatedSubstringPattern(s) {
const combined = s + s;
return kmpSearch(combined.substring(1, combined.length - 1), s).length > 0;
}
console.log(repeatedSubstringPattern("abab")); // true
三、KMP算法的实际应用场景
3.1 文本编辑器中的查找与替换
在大型文档中实现快速查找和替换操作,是文本编辑器(如Word、VSCode)的核心功能之一。
3.2 网络搜索引擎中的关键词匹配
搜索引擎需要高效匹配用户输入的关键词,KMP算法可在大规模网页数据中快速查找模式串。
3.3 DNA序列比对与生物信息学
在基因序列中查找特定基因片段,通过KMP算法实现高效比对,常用于生物信息分析。
3.4 压缩算法
在数据压缩中,KMP帮助识别重复子串,优化压缩率,例如LZ77、LZW等算法。
3.5 网络安全与敏感词过滤
- 防火墙检测:匹配数据包中的恶意字符串模式。
- 敏感词过滤:在聊天平台中快速定位并过滤不合规内容。
3.6 代码查重与抄袭检测
在代码仓库中快速查找重复代码片段,辅助学术抄袭检测与代码质量检查。
总结
KMP算法不仅是一种经典的字符串匹配算法,更是高效处理文本与数据的利器。从原理上理解前缀函数的作用,到实际应用于文本搜索、基因匹配、网络安全等领域,KMP展示了其强大的实用性。