不得不说ACM哪怕是没有结果,对于算法能力的训练是毋庸置疑的……
因为老师划了重点,所以讲一下horspool的字符串匹配算法的原理吧。
先声明几个概念,被找的字符串称为匹配串,要找的字符串被称为模式串,当前和模式串相匹配的匹配串的子串被称为匹配子串(废话
在朴素算法中,我们要找一个匹配串是否存在模式串,例如HuangZhi is a genius的匹配串串和ChengJinSen的模式串,那么我们一开始用的方法是用:
HuangZhi is(11 character)
去匹配:
ChengJinSen(11 character)
如果每个字母都相等,那么我们就认为这个模式找到了。
但是这个算法的效率是多少呢,假设匹配串的长度为N,模式串的长度为m,那么易证时间效率为:O(n*m),在很多时候这个效率都是很慢的。
而Horspool算法(在我看来)是有这样一个基本思想:如果我提前知道了这个步骤是可以跳过的,那我就可以不用去验证了。
我们拿saber和archer作为模式串和匹配串好了:
archer
saber
很明显,在初次对齐的时候,其并不相等,而且我们注意到了,此次匹配串对应的子串是:arche,而最后一个字母是e.
那么很明显的,假如我们一个个移动下去,直到模式串的下一个e为止,都不可能达到匹配的结果。
这里e就是下一个,所以我们把saber稍稍的改一下,改成sebar,这时候就会发现,假设你一个个挪,到e之前的所有都不可能和arche里的e匹配,所以这里我们完全可以直接把e挪到arche的e的位置,然后在进行匹配计算,这样就节省了不少的时间。也就是说,跳过e到模式串最后一个字母之间的距离。
以上的情况,可以归纳为情况1,即匹配子串的最后一个在模式串中出现过,且不是模式串的最后一个字母(最后的特殊性会讲到)
那么,假设最后一个字符在模式串里从来没有出现过呢?那这样,假设一个个移,每一个都必然得不到匹配结果,直到该字符不在匹配子串中,如此,则是直接跳过整个子串的长度。
以上情况归为情况2,匹配子串的最后一个字母没有在模式串里出现过,且不是模式串的最后一个字母。
好了,为什么上面要提到模式串的字母呢?因为这个查询方法存在一个问题,假设匹配子串的最后一个字母在模式串中出现过,且为模式串的最后一位呢?那这样就要分成两种情况来讨论,
1,是模式串的最后一位字母在模式串中唯一,那这样就类似于情况2,直接跳过这个匹配子串最后字母的匹配,即跳过字符长度。这个我们姑且称为情况3。
2,是该字母在模式串中不唯一,那这样就仿照情况二,找到离模式串结尾最近的,且同样是这个字母的字母的位置,并跳转以后匹配。这个我们称为情况4
然后是这个算法的计算机实现,总结了下上述规律,我们发现这个模式串跳转的机制,可以不用每次查找模式串中是否有同样的值,而用预处理的方式来实现,根据情况一二的总结,我们得出,模式串中存在的字母,向右找到其最贴近模式串结尾的同样的字母,并把其离结尾的位置存下,方便应用,若不存在,则直接跳转模式串长度,为了方便,我们也把它存下来,不过跳转长度设为模式串长度。根据情况3,4的总结,我们得出,最后一个字母不应纳入上述的计算。
附带试验用算法:
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<string>
#define INF 0x3f3f3f3f
using namespace std;
int main() {
string M, P;
cin >> M >> P;
map<char, int>NEXT;
for (int i = 0; i < 26; i++)
NEXT[‘a‘ + i] = P.size();
for (int i = 0; i < 26; i++)
NEXT[‘A‘ + i] = P.size();
for (int i = 0; i < P.size() - 1; i++)
NEXT[P[i]] = P.size() - i-1;
int space = 0;
for (int i = 0; i <= M.size()-P.size();)
{
space = i;
cout << M << endl;
for (int i = 0; i < space; i++)
cout << " ";
cout << P << endl << "_________________________________" << endl;
if (M.substr(i, P.size()) == P)
cout << "找到匹配串" << endl;
i += NEXT[M[i + P.size() - 1]];
}}