蛮力法进行字符串匹配的缺点在于每次失配后模式只向后移动一个位置。想要提高算法效率,就必须在不错过文本中一个匹配子串风险的前提下,尽量增大模式向后移动的幅度。Horspool就是这样一种算法之一,它的思想要比KMP算法容易。它采用了输入增加技术:对模式进行预处理得到一些信息,把这些信息存储在表中,当文本和模式进行匹配时就会用到这些信息。Horspool的匹配过程是从右向左进行的,在匹配过程中会出现以下四种情况:
情况1
文本:x x x x A x x x x x
模式: x x B
最后一个字符失配,且模式中不存在文本中的失配字符A,那么模式可以移动的幅度就是它的长度,这里是把模式向后移动3个位置,如下所示:
文本:x x x x A x x x x x
模式: x x B
情况2
文本:x x x x A x x x x x
模式: A x B
最后一个字符失配,且模式中存在文本中的失配字符A,这时应该把模式最右边的A和文本的A对齐,如下所示:
文本:x x x x A x x x x x
模式: A x B
情况3
文本:x x x B A x x x x x
模式: x W A
最后一个字符A相匹配,且模式中剩下的字符都不存在A,那么情况类似于1,移动幅度等于模式的全部长度,如下所示:
文本:x x x B A x x x x x
模式: x W A
情况4
文本:x x x B A x x x x x
模式: A W A
最后一个字符A相匹配,且模式中剩下的字符中存在A,那么情况类似于2,移动模式使二者对齐,如下所示:
文本:x x x B A x x x x x
模式: A W A
从上述过程可以看出,算法的关键是要知道文本中的某个字符(上述过程是字符A)是否在模式中存在,存在的话距模式最后一个字符相差多远。这些信息就是我们要存储在表中的信息。可以以文本中出现的每个字符为c自变量,发生失配时模式可以安全移动的最大距离为函数t(c),得到如下映射:
- 如果c不包含在模式的前m-1个字符中,则t(c) = 模式长度m
- t(c) = 模式前m-1个字符中最右边的c到模式最后一个字符的距离
例如有模式ABCDE,那么在文本的所有字符之中,t(D) = 1,t(C) = 2,t(B) = 3,t(A) = 4,其余字符对应的函数均为模式长度5.对于这种函数映射,用C++的map容器再适合不过了。
以下是完整代码:
#include <iostream> #include <string> #include <map> #include <vector> using namespace std; void Get_Next(const string &text, const string &pattern, map<char, int> &next) { int text_len = text.size(); int pattern_len = pattern.size(); for (int i = 0; i < text_len; i++) next.insert(make_pair(text[i], pattern_len)); for (int i = 0; i < pattern_len - 1; i++) next[pattern[i]] = pattern_len - 1 - i; } void Horspool(const string &text, const string &pattern, vector<int> &result) { int text_len = text.size(); int pattern_len = pattern.size(); map<char, int> next; // 使用map容器记录移动表 Get_Next(text, pattern, next); for (int i = pattern_len - 1; i < text_len; /* NULL */) { int text_pos = i; int pattern_pos; for (pattern_pos = pattern_len - 1; pattern_pos >= 0; /* NULL */) { if (text[text_pos] == pattern[pattern_pos]) { text_pos--; pattern_pos--; } else { i += next[text[i]]; // i = 文本中对齐模式最后一个字符的位置 break; } } if (pattern_pos <= 0) { result.push_back(i - pattern_len + 1); // 完全匹配时的起始位置 i++; } } } int main() { string text = "hello world good google Nestle people google hello this is a test google"; string pattern = "google"; vector<int> result; Horspool(text, pattern, result); int cnt = 1; for (vector<int>::iterator iter = result.begin(); iter != result.end(); ++iter) cout << "match " << cnt++ << " = " << *iter << endl; system("pause"); return 0; }
运行结果:
对于随机文本,Horspool算法的效率为Θ(n),n为文本长度。
参考:
《算法设计与分析基础》 P194-P197.
字符串匹配 — Horspool