














Sunday 算法由 Daniel M. Sunday 于 1990 年提出,是一种极其高效的单模式字符串匹配算法,尤其在处理长文本与较短模式串时表现出色。
Sunday 算法的匹配过程从左向右进行,但它在遇到不匹配时,只关注当前滑动窗口之外紧跟的下一个字符。
设文本串为 \(T\),长度为 \(n\),模式串为 \(P\),长度为 \(m\),当前匹配窗口为 \(T_{i \dots i+m-1}\)。如果在窗口内发现字符不匹配,算法会直接查看紧跟在窗口后的字符,即 \(T_{i+m}\):
为了快速决定滑动距离,需要预处理出一个偏移表,记录每个字符对应的滑动步长。
vector<int> sunday_search(const string &text, const string &pattern) {
int n = text.length(), m = pattern.length();
vector<int> results;
// 边界情况处理
if (m == 0 || n < m) return results;
// 1. 初始化偏移表,默认偏移量为 m + 1
// 假设字符集为 128 个 ASCII 字符
vector<int> shift(128, m + 1);
// 2. 计算模式串中每个字符的偏移量
for (int i = 0; i < m; i++) {
shift[pattern[i]] = m - i;
}
int i = 0;
// 3. 开始匹配
while (i <= n - m) {
// 检查当前窗口是否完全匹配
bool match = true;
for (int j = 0; j < m; j++) {
if (text[i + j] != pattern[j]) {
match = false;
break;
}
}
if (match) results.push_back(i);
// 如果已经到了文本末尾,无需再获取下一个字符
if (i + m >= n) break;
// 核心:根据窗口紧跟的下一个字符决定跳跃距离
char next_char = text[i + m];
i += shift[next_char];
}
return results;
}
如果题目中的字符串是由随机字符组成的(例如普通的英文文章、日志数据),Sunday 算法的速度往往很快。但如果题目刻意构造极端数据,Sunday 算法会退化为 \(O(n \times m)\) 的暴力搜索。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int f(const string &s, const string &t)
{
int n = s.length(), m = t.length();
vector<int> shift(128, m + 1);
int i, j;
for (j = 0; j < m; j++)
shift[t[j]] = m - j;
for (i = 0; i <= n - m; i += shift[s[i + m]]) {
j = 0;
while (j < m && s[i + j] == t[j]) j++;
if (j == m) return i;
}
return -1;
}
int main()
{
string a, b;
cin >> a >> b;
cout << f(a, b) << endl;
return 0;
}
假设输入字符串由 ASCII 可见字符组成:
abcde fg 时,输出为 -1。(正确/错误)abbababbbab abab 时,输出为 4。(正确/错误)GoodLuckCsp2022 22 时,第 20 行的 j++ 语句执行次数为 \(2\)。(正确/错误)f(a, b) 与下列哪个语句的功能最类似?a.find(b)a.rfind(b)a.substr(b)a.compare(b)baaabaaabaaabaaaa aaaa,第 20 行的 j++ 语句执行次数为?判断:正确
计算过程:
输入 a = "abcde", b = "fg"(\(n=5, m=2\))。shift 数组初始全为 \(3\)。
根据 b 计算偏移表:shift['f'] = 2, shift['g'] = 1。
"ab",'a' != 'f',在 \(j=0\) 处失配。查看下一字符 a[0+2] = a[2] = 'c',shift['c'] = 3,更新 \(i = 0 + 3 = 3\)。"de",'d' != 'f',在 \(j=0\) 处失配。查看下一字符 a[3+2] = a[5] = '\0'(C++中字符串末尾后的字符访问),shift['\0'] = 3,更新 \(i = 3 + 3 = 6\)。-1。判断:错误
计算过程:
输入 a = "abbababbbab", b = "abab"(\(n=11, m=4\))。默认 shift 为 \(5\)。
根据 b 计算偏移表:shift['a'] = 2, shift['b'] = 1。
"abba",在 \(j=2\) 处失配(a[2]='b' != t[2]='a')。下个字符为 a[0+4] = a[4] = 'b',更新 \(i = 0 + shift['b'] = 1\)。"bbab",在 \(j=0\) 处失配('b' != 'a')。下个字符为 a[1+4] = a[5] = 'a',更新 \(i = 1 + shift['a'] = 1 + 2 = 3\)。"abab",逐个比对全部成功(\(j\) 最终递增到 \(4\))。循环结束,直接返回当前的起始位置 \(3\)。判断:正确
计算过程:
输入 a = "GoodLuckCsp2022", b = "22"(\(n=15, m=2\))。默认 shift 为 \(3\)。shift['2'] = 1。
"Go", "dL", "ck"。每次都在首字符发生失配(j++ 执行 \(0\) 次),且下一位字符分别为 'o', 'u', 'C',其 shift 值均为 \(3\),因此 \(i\) 依次递增为 \(3, 6, 9\)。"sp",在 \(j=0\) 失配(j++ 执行 \(0\) 次)。下一位字符为 a[11]='2',shift['2']=1,更新 \(i = 9+1=10\)。"p2",首字符 'p' != '2' 再次在 \(j=0\) 失配(j++ 执行 \(0\) 次)。下一位字符为 a[12]='0',shift['0']=3,更新 \(i = 10+3=13\)。"22",与目标串 "22" 逐位比对。首字符相等(执行第 \(1\) 次 j++),第二个字符也相等(执行第 \(2\) 次 j++)。匹配成功,返回 \(13\)。j++ 语句仅在最后一次完全匹配时执行了 \(2\) 次。\(O(n \times m)\)
要让 Sunday 算法退化到最坏情况,必须同时满足两个极其苛刻的条件:
shift)尽可能小(最好每次只滑动 1 步)。极端数据构造示例解析:
假设构造这样的文本串(主串)和模式串:
"aaaaa...aaaaa" (长度为 \(n\),全为 'a')"aaa...aba" (长度为 \(m\),前 \(m-2\) 个全为 'a',倒数第 2 个为 'b',最后 1 个为 'a')执行过程推演:
"aaa...aba",字符 'a' 最后一次出现的位置是下标 \(m-1\)(即最后一位)。shift['a'] = m - (m - 1) = 1。'a',前 \(m-2\) 次比对('a' == 'a')都会成功。'a' 与模式串的 'b' 才会发生失配。'a',该字符必然是 'a'。shift['a'] = 1,因此窗口只能向右滑动 1 步,即 \(i \gets i + 1\)。复杂度计算:
由于每次只前进 1 步,外层循环大约会执行 \(n - m\) 次;而内层每次匹配都会进行 \(m-1\) 次字符比较。
总比较次数约为 \((n - m) \times (m - 1)\)。
当 \(n \gg m\) 时,时间复杂度退化为标准的 \(O(n \times m)\)。
A. a.find(b)
f(a, b) 的核心逻辑是寻找字符串 b 在 a 中首次出现的起始下标位置,若未找到则返回 -1。这与 C++ 标准库中 std::string::find 的行为高度一致(找不到时返回 string::npos,强转为 int 时值即为 -1)。
10
输入 a = "baaabaaabaaabaaaa", b = "aaaa"(\(n=17, m=4\))。shift['a']=1,shift['b']=5。
"baaa",首字符失配(j++ \(0\) 次)。下个字符为 a[4]='b',\(i \gets 0+5=5\)。"aaab",前 \(3\) 位匹配(j++ \(3\) 次)。下个字符为 a[9]='a',\(i \gets 5+1=6\)。"aaba",前 \(2\) 位匹配(j++ \(2\) 次)。下个字符为 a[10]='a',\(i \gets 6+1=7\)。"abaa",首位匹配(j++ \(1\) 次)。下个字符为 a[11]='a',\(i \gets 7+1=8\)。"baaa",首字符失配(j++ \(0\) 次)。下个字符为 a[12]='b',\(i \gets 8+5=13\)。"aaaa",全部匹配(j++ \(4\) 次)。返回结果。此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。