因为std::string::find是o(n×m)朴素匹配,遇重复字符易退化;kmp通过预处理实现o(n+m)最坏性能,且可定制逻辑、避免拷贝、适配嵌入式等场景。

为什么 std::string::find 不够用,还得手写 KMP?
因为标准库的 find 是朴素匹配(O(n×m)),遇到大量重复字符(比如日志里搜 "aaaaa...ab")会退化严重;而 KMP 预处理模式串,主串指针不回退,最坏也是 O(n+m)。实际在嵌入式、高频日志过滤或自定义匹配规则(比如跳过某些字节)时,必须自己控住匹配逻辑。
常见错误现象:next 数组算错导致越界或漏匹配;模式串长度为 0 或 1 时没特判;next[0] 初始化成 -1 还是 0 混用。
-
next[i]表示模式串pattern[0..i]的最长真前缀后缀长度 —— 必须严格按这个定义推,别凭感觉填 - 推荐统一用
next[0] = 0,避免负索引;构建时用双指针:j指向前缀尾,i指向后缀尾 - 匹配阶段:失配时
j = next[j-1](不是next[j]),且要加j > 0判断防下标越界
怎么写一个不崩的 kmp_search 函数?
核心是分离预处理和匹配,别把 next 数组每次重算。C++ 里建议封装成函数对象或静态局部 vector 缓存,尤其当同一模式串反复匹配不同文本时。
使用场景:实时流式文本扫描(如网络包 payload 解析)、内存受限环境(避免 std::string 临时拷贝)。
立即学习“C++免费学习笔记(深入)”;
- 输入参数用
const char*+size_t更轻量,比std::string少一次构造 - 返回值建议用
std::optional<size_t></size_t>(C++17)或-1表示未找到,别用size_t返回 -1 导致溢出 - 示例关键片段:
int j = 0; for (int i = 0; i < text_len; ++i) { while (j > 0 && text[i] != pattern[j]) j = next[j-1]; if (text[i] == pattern[j]) ++j; if (j == pattern_len) return i - j + 1; }
next 数组构建时最容易错哪几行?
90% 的崩溃来自这里:循环条件、边界判断、赋值时机三者不一致。尤其 C++ 里数组索引从 0 开始,但语义上 next[i] 描述的是 pattern[0..i] 的性质,容易混淆。
- 别写
for (int i = 1; i —— <code>next数组长度应等于模式串长,索引范围是[0, len) - 初始化
next[0] = 0后,循环从i = 1开始,j初始为 0 - 内层
while必须写成while (j > 0 && pattern[i] != pattern[j]) j = next[j-1];—— 少了j > 0就可能访问next[-1] - 匹配成功后赋值
next[i] = j + 1,不是j;因为j是当前已匹配长度,next[i]要存的是长度值
用 std::vector<int></int> 存 next 有啥隐患?
主要在小字符串场景下浪费内存:短模式串(比如 5 字符)却分配默认最小容量(常为 16),还触发堆分配。更麻烦的是,如果函数被频繁调用(如每毫秒一次),vector 构造/析构开销明显。
性能影响:实测在模式串平均长度 int next[32])比 vector 快 2~3 倍,且无动态分配风险。
- 若确定模式串长度上限(如 HTTP 头字段名不超过 64 字节),直接用
std::array<int></int>最稳 - 否则至少用
std::vector<int> next; next.reserve(pattern_len);</int>避免多次扩容 - 别在循环里写
std::vector<int> next(pattern_len);</int>—— 每次都零初始化,冗余
真正难的不是推导 KMP 理论,是把 next 数组的索引偏移、边界检查、内存布局这三件事,在同一段代码里同时做对。多数人栽在第 2 步和第 3 步的组合上,而不是第一步。










