背景知识回顾
之前的文章中讲了字符串匹配问题中的单模匹配问题,即从一段文本串中找到另一个字符串是否出现过。母串(文本串)是指要从哪个字符串中查找,模式串指的是要查找哪个字符。之后介绍了暴力匹配算法,即从文本串中逐位向后和模式串比较。此外还介绍过KMP匹配算法,这种算法适合处理流式字符。
本文介绍Sunday匹配算法。
Sunday算法理论介绍
假设文本串S和模式串T匹配的过程中遇到如下情况,发现失配:
此时文本串匹配结果的下一位为a, 那么可以从模式串从后往前寻找‘a’这个字符,发现其在倒数第4位第一次出现,此时可以将模式串向后移动4个位置。 ‘a’对齐的这个位置称为黄金对齐位。
看此时文本串对应位置和模式串是否匹配,发现还不匹配,而且文本串的下一位为字母‘b’, 那么从模式串中从后往前寻找‘b’这个字符,发现其在倒数第8位,所以可以将模式串向后移动8个位置。
不断重复上面的过程,直到模式串和文本串完全匹配。
如果可以提前记录模式串中的每个字符,在模式串的倒数第几位出现,那么对于一个新的字符,就可以马上判断应该将模式串向后平移几位。
此外如果移动的过程中,发现文本串的下一位在模式串中从来没有出现过,此时可以设置模式串的一个虚拟位置-1,该位置和任何一个字符都能匹配。这样就可以使算法不需要额外判断。
Sunday算法代码实现
int sunday(const char *text, const char *pattern) {
#define BASE 256
int n = strlen(text), m, last_pos[BASE];
//last_pos的每一个索引表示一个字符,因为字符本身就能通过ascll码表示为整数。
//last_pos的值表示pattern中的元素在的哪一位出现
for (int i = 0; i < BASE; i++) last_pos[i] = -1; //默认为设置-1位置
for (m = 0; pattern[m]; m++) last_pos[pattern[m]] = m;
for (int i = 0; i + m <= n; i += (m - last_pos[text[i + m]])) {
//如果没匹配成功,那么text的索引i向后移动 m - last_pos[text[i + m]] 位,m为模式串的长度
int flag = 1;
for (int j = 0; j < m; j++) {
if (text[i + j] == pattern[j]) continue;
flag = 0;
break;
}
if (flag) return i;
}
return -1;
}
Sunday算法应用场景
Sunday算法适合在一大串长文本中寻找一个比较短的字符串是否出现过。
极端情况下Sunday算法的时间复杂度可以降低到O(n / m), n为文本串长度,m为模式串长度。