第一部分:KMP 字符串匹配算法
KMP 算法是一种高效的字符串匹配算法,它的核心在于避免了在文本串中不必要的回溯,从而将时间复杂度从暴力匹配的 O(m×n)O(m \times n)O(m×n) 优化到 O(m+n)O(m+n)O(m+n)。
1. 核心问题:模式匹配
- 目标串 (Target String):一个较长的字符串,我们通常用
S表示,长度为n。 - 模式串 (Pattern String):一个较短的,我们希望在目标串中查找的字符串,通常用
P表示,长度为m。 - 目标:在
S中找到P首次出现的位置。
2. 朴素(暴力)匹配算法及其弊端
朴素算法的思路很简单:
- 将
S的第一个字符与P的第一个字符对齐,然后逐一比较。 - 如果所有字符都匹配成功,则返回起始位置。
- 如果中途遇到不匹配的字符,则将
P向后移动一位,再次从P的第一个字符开始与S的新位置对齐比较。 - 重复此过程,直到找到匹配或
S遍历完毕。
弊端:效率低下。在最坏情况下(例如 S=“aaaaaaaaab”, P=“aaab”),每次失配后,S 的指针 i 都要回溯,P 的指针 j 都要归零,导致大量重复比较。
3. KMP 算法的核心思想
KMP算法的巧妙之处在于,当 S[i] 与 P[j] 发生不匹配时,它不会简单地将 i 回溯,j 归零。而是利用已经匹配过的信息,让模式串 P 尽可能地向右滑动多位,并让 j 跳转到一个新的位置继续比较。
这个“跳转到哪里”的信息,就存储在一个预先计算好的辅助数组中,这个数组通常被称为 next 数组 (在你的课件中也被称为“失败函数” f(j) 的计算结果)。
4. 关键:next 数组 (前缀函数)
next 数组是 KMP 算法的精髓,它完全根据模式串 P 本身的结构来计算。
-
定义:
next[j]的值表示P的子串P[0...j-1]中,最长相等前后缀的长度。- 前缀:指除最后一个字符以外,字符串的所有头部子串。
- 后缀:指除第一个字符以外,字符串的所有尾部子串。
- 最长相等前后缀:例如,对于字符串 “ababa”,
- 前缀有: “a”, “ab”, “aba”, “abab”
- 后缀有: “b”, “ba”, “aba”, “baba”
- 相等的前后缀有 “a” 和 “aba”。最长的那个是 “aba”,长度为 3。
-
作用:当
S[i]和P[j]不匹配时,我们不必从头再来。我们知道S[i-j ... i-1]已经和P[0 ... j-1]匹配成功了。根据next数组,我们知道P[0 ... j-1]的一个长度为k = next[j]的前缀P[0 ... k-1]和它的一个后缀P[j-k ... j-1]是相等的。因此,我们可以直接将模式串向右滑动,让这个前缀P[0 ... k-1]对齐到刚刚匹配上的后缀位置,然后从P[k]继续和S[i]进行比较。这个过程指针i完全不需要回溯。 -
计算示例:以模式串
P = "ababc"为例计算next数组。j=0:P[0...-1](空串),没有前后缀,next[0] = 0。(在某些实现中,为了方便设为-1,我们这里统一设为0)j=1:P[0...0]= “a”,最长相等前后缀长度为 0,next[1] = 0。j=2:P[0...1]= “ab”,最长相等前后缀长度为 0,next[2] = 0。j=3:P[0...2]= “aba”,最长相等前后缀为 “a”,长度为 1,next[3] = 1。j=4:P[0...3]= “abab”,最长相等前后缀为 “ab”,长度为 2,next[4] = 2。j=5:P[0...4]= “ababc”,最长相等前后缀长度为 0,next[5] = 0。- 所以,
P = "ababc"的next数组为[0, 0, 0, 1, 2]。
5. KMP 算法实现 (机试必备)
a. next 数组的计算
// C++ 实现
#include <vector>
#include <string>
void computeNext(const std::string& p, std::vector<int>& next) {
int m = p.length();
next.resize(m, 0);
for (int i = 1, len = 0; i < m; ) {
if (p[i] == p[len]) {
len++;
next[i] = len;
i++;
} else {
if (len != 0) {
len = next[len - 1];
} else {
<

最低0.47元/天 解锁文章
859

被折叠的 条评论
为什么被折叠?



