Sunday算法更快因跳过不匹配字符段,平均O(n);易手写因无需失败函数、逻辑清晰;shift表用256数组初始化为m+1,pattern[j]对应shift[pattern[j]]=m−j。

为什么 Sunday 算法比暴力匹配快,但又比 KMP 容易手写
Sunday 算法的核心是「看主串中对齐位置的后一位字符」——如果它不在模式串里,整段直接跳过;如果在,就跳到该字符在模式串中最后一次出现的位置对齐。这使得平均时间复杂度接近 O(n)(n 是主串长度),最坏也不过 O(mn),但实践中远好于暴力。它不维护失败函数、不回退指针,逻辑清晰,适合手写调试。
关键点在于:跳转依据是「主串中 i + m 位置的字符」(即当前窗口末尾的下一位),不是当前比对失败的那个字符。
如何构建 shift 表(注意索引和默认值)
shift 表本质是字符 → 向右跳多少位的映射。常见错误是:用 std::map<char int></char> 初始化为 0,导致没出现的字符被查成 0,跳不动;或者把偏移算成「从末尾到该字符距离」却忘了加 1。
- 正确做法:用大小为 256 的数组(覆盖 ASCII),初始化为
m + 1(即整个模式串长度 + 1,表示该字符未出现时直接跳过整段) - 遍历模式串
pattern时,对每个pattern[j],设shift[pattern[j]] = m - j(不是m - j - 1),因为要让该字符对齐到它在模式串中最后一次出现的位置,而跳的距离 = 模式串长度 − 它的最后索引 - 例如
"abab",'b'最后出现在索引 3,shift['b'] = 4 - 3 = 1;但注意,若后面还有'b',会被覆盖,这正是我们想要的「最后一次」
主循环怎么写才不越界、不错位
主串指针 i 表示当前窗口起始位置,每次迭代前必须确保 i ,否则剩余长度不够匹配。常见错误是先比对再判断边界,或在跳转后忘记检查 <code>i 是否超限。
立即学习“C++免费学习笔记(深入)”;
- 循环条件用
while (i ,不是 <code> - 内层比对用
for (int j = 0; j ,一旦 <code>haystack[i+j] != pattern[j]就跳出 - 若完整比对成功(
j == m),直接返回i - 否则计算跳数:
int k = i + m,如果k >= n,说明后面没字符可看,直接跳出;否则查shift[haystack[k]],更新i += shift[haystack[k]]
C++ 实现中 char 类型和符号问题怎么处理
如果直接用 char 当数组下标(如 shift[ch]),在某些平台 char 默认是 signed,遇到 '\xFF' 类字符会变成负数,导致数组越界。这是手写时最容易崩溃却最难定位的问题。
- 务必把字符强转为
unsigned char再做下标:shift[static_cast<unsigned char>(ch)]</unsigned> - 声明 shift 数组时用
int shift[256]{},并初始化为m + 1,不要依赖零初始化 - 如果模式串含非 ASCII 字符(如 UTF-8 中文),Sunday 本身不适用——它按字节匹配,不是按 Unicode 码点;此时应先明确输入是否纯 ASCII
真正麻烦的从来不是算法逻辑,而是边界检查和类型安全。写完先用 "aaaa" 匹配 "aa"、空模式串、模式串比主串长这些 case 过一遍,比盯着伪代码推导管用得多。


















