next[0]必须为0,因其定义为子串pattern[0..0]的最长真前后缀长度,单字符无真前后缀;0-based下标使next[i]直接对应pattern[i]失配时回退位置,避免索引错位。

next 数组不是凭空编出来的,它本质是每个位置上「已匹配前缀的最长真前后缀长度」——算错一个值,整个匹配就可能跳过正确位置。
为什么 next[0] 必须是 0,且 next 下标从 0 开始对应模式串首字符?
因为 next[i] 定义的是子串 pattern[0..i] 的最长公共真前缀后缀长度,而真前缀/后缀不能等于自身,所以单字符子串没有真前后缀。强制设 next[0] = 0 是定义使然,不是经验约定。
若你用 1-based 下标(比如教材里写 next[1]=0, next[2]=1),实际编码时容易和 string::at() 或数组索引错位。C++ 中统一用 0-based 索引更安全:
-
next[i]对应pattern[i]失配时的回退位置(即下次比较从pattern[next[i]]开始) - 所有循环变量
i、j都按 0-based 设计,避免 +1/-1 补丁式修正 - 如果硬套 1-based 公式(如“
next[3]看pattern[2]和pattern[next[2]]”),在 C++ 里会访问越界或逻辑偏移
buildNext 函数里 while(j > 0 && pattern[i] != pattern[j]) 这个 while 为什么不能写成 if?
因为一次失配后,j 指向的位置可能仍不匹配,需要持续回退到更短的公共前缀。用 if 只尝试一级回退(即只查 next[j-1]),但真实场景中可能要跳多次,比如模式串 "aaaaab" 在末尾失配时,需从长度 4 → 3 → 2 → 1 → 0 层层收缩。
立即学习“C++免费学习笔记(深入)”;
这个 while 就是 KMP “利用已知匹配信息”的核心体现:只要当前 j 不为 0 且仍不匹配,就按 next[j-1] 继续往前找更短的可复用前缀。
- 错误写法:
if (j > 0 && pattern[i] != pattern[j]) j = next[j-1];→ 仅回退一次,next数组计算错误 - 正确逻辑:每次
j = next[j-1]后,仍要重新判断pattern[i] == pattern[j],否则漏掉中间可能匹配点 - 性能影响:虽然看起来像嵌套循环,但均摊时间复杂度仍是 O(m),因为
j总体递增次数 ≤i递增次数
手算 next 数组时,为什么 "ABCDABD" 的 next[5](对应第二个 'B')是 2,而不是 0 或 1?
看子串 "ABCDAB"(下标 0~5):它的真前缀有 "A", "AB", "ABC", "ABCD", "ABCDA";真后缀有 "B", "AB", "DAB", "CDAB", "BCDAB"。唯一相等的是 "AB",长度为 2。
这个值直接决定匹配失败时的滑动距离:若主串在该位置失配,模式串不必从头开始,而是对齐到第 2 位继续比——省掉重复比较 "AB" 这两字符。
- 常见误判:以为只要首尾字符相同(
'A'和'B')就该有公共前后缀 → 错,必须整个子串完全相等 - 边界陷阱:子串
"AAA"的next[2]是 2(前缀"AA"= 后缀"AA"),不是 1;很多人卡在这里 - 验证技巧:写出所有真前缀/后缀,按长度从大到小比对,第一个相等的就是
next[i]
C++ 实现中,next 和 nextval 到底要不要二选一?
绝大多数场景只用 next 就够了。nextval 是对 next 的进一步压缩,仅当模式串存在连续重复字符(如 "AAAA")时能减少一次无效比较,但代价是构建逻辑更绕、可读性下降。
除非你在高频调用、模式串极长且重复度极高(比如生物序列分析),否则不要提前优化。标准库级实现(如 LLVM 的 llvm::StringRef::find)也未引入 nextval。
-
nextval[i]本质是:若pattern[i] == pattern[next[i]],则把next[i]替换为nextval[next[i]],否则保留next[i] - 调试时若发现匹配结果正确但步骤略多,先检查
next是否算对,再考虑是否值得上nextval - 真正容易被忽略的是:无论用哪个数组,匹配主循环中的指针更新逻辑必须与之严格对应,否则行为不可预测


















