字符串存储与 BF、KMP 匹配算法
字符串匹配的目标是在主串 S 中查找模式串 P 的首次出现位置。算法性能的关键在于:主串发生失配后,能否复用已经比较过的信息。
定长顺序字符串
一种教材式字符串结构用第 0 个位置保存长度,用 S[1..len] 保存字符:
text
S[0] = length
S[1..n] = characters
这种结构不等同于 C 字符串,不能直接套用 strlen 或 \0 终止规则。拼接时必须先检查容量,匹配函数也必须统一返回值约定。
BF 算法
BF 从每个可能起点重新比较模式串:
cpp
for (int start = 0; start + m <= n; ++start) {
int j = 0;
while (j < m && text[start + j] == pattern[j]) ++j;
if (j == m) return start;
}
return -1;
主串长度为 n、模式串长度为 m 时,最坏时间复杂度为 O(nm),额外空间为 O(1)。BF 代码简单,短字符串场景中常数开销较低。
KMP 的核心:前缀函数
若 P[0..j-1] 已经匹配,P[j] 失配,不必让主串指针回退。模式串的某个最长相等前缀和后缀可以继续参与匹配。
定义 next[j] 表示模式串失配时的回退位置。构造时维护当前候选前缀长度:
cpp
next[0] = -1;
int i = 0, j = -1;
while (i < m) {
if (j == -1 || pattern[i] == pattern[j]) {
++i;
++j;
next[i] = j;
} else {
j = next[j];
}
}
匹配过程为:
cpp
while (i < n && j < m) {
if (j == -1 || text[i] == pattern[j]) {
++i;
++j;
} else {
j = next[j];
}
}
return j == m ? i - m : -1;
主串指针只向前移动,模式串指针通过 next 回退。预处理为 O(m),匹配为 O(n),总复杂度为 O(n+m),额外空间为 O(m)。
nextval 优化
若回退后比较的字符与刚才失配字符相同,则该比较必然再次失败,可以继续跳过:
cpp
nextval[i] = pattern[i] == pattern[next[i]]
? nextval[next[i]]
: next[i];
nextval 不改变渐进复杂度,但能减少重复字符模式下的无效比较。
工程注意事项
- 明确字符下标从 0 还是从 1 开始。
- 明确失败返回
-1、0还是其他值。 - 空模式串需要单独定义语义。
- Unicode 字符串不能简单按字节等同于字符。
- 大文本重复匹配时,应复用模式串预处理结果。
KMP 的本质是把模式串的自相似性编码为回退表,使匹配过程不重复扫描已经确认过的主串字符。