Horspool算法
Posted 望北i
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Horspool算法相关的知识,希望对你有一定的参考价值。
概念
horspool算法为字符串匹配算法,这是一个空间换时间的算法,算法把模式和文本的开头字符对齐,从模式的最后一个字符开始比较,如果尝试比较失败了,它把模式向后移。每次尝试过程中比较是从右到左的。我们不会像蛮力法那样移动一格,根据不同情况移动,有以下四种情况
- 如果模式中不存在c(在我们的例子中,c就是字母S),模式安全移动的幅度就是他的全部长度
文本:S0 S1 S2……… D……………………
模式 B A R E B R
移动: B A R B E R
- 如果模式中存在c,但它不是模式的最好一个字符(在我们的例子中,c就是字母B),移动时应该把模式中最右边的c和文本中的c对齐:
文本:S0 S1 S2……… B……………………
模式: B A R E B R
移动: B A R B E R
- 如果c正好是模式中最后一个字符,但在模式中其他m-1个字符中不包含c,移动情况类似1,移动幅度为m
文本:S0 S1 S2…B R……………………
模式: B A R E B R
移动: B A R B E R
- 如果c是模式中的最好一个字符,而且在模式的前m-1个字符中也包含c,移动的情况类似于2:移动时应该把模式中前m-1个字符中的c和文本中的c对齐
文本:S0 S1 S2.……………… R……………………
模式: B A R E B R
移动: B A R B E R
这说明,比起蛮力算法每次总是移动一个位置,从右到左的字符比较使模式模式移动得更远。然而,如果在每次尝试时都必须检查模式中的每个字符,它的优势也会丧失殆尽。我们可以预先算出遇到某个字符要移动的距离,并把它存在一个表中。具体来说,对于每一个字符c,可以通过以下公式算出移动距离:
算法步骤
- 对于给定的长度为m的模式和在模式及文本中用到的字母表,按照上面的描述构造移动表。
- 将模式与文本的开始处对齐。
- 重复下面的过程,直到发现了一个匹配子串或者模式到达了文本的最后一个字符以外。从模式的最后一个字符开始,比较模式和文本中的相应字符,直到:要么所有m个字符都匹配(然后停止,要么遇到了一对不匹配的字符。在后一种情况下,如果c是当前文本中和模式的最后一个字符相对齐的字符,从移动表的第c列中取出单元格t(c)的值,然后将模式沿着文本向右移动t(c)个字符的距离。
建表算法
ShiftTable(P[0…m-1])
for i <-- 0 to n - 1 do Table[t[i]] <-- m //n表示文本串的长度, t[i]表示文本串
for j <-- 0 to m - 2 do Table[p[j]] <-- m - 1 - j //m表示模式串的长度, p[j]表示模式串
return Table
字符匹配过程算法
HorspoolMatching(p[0…m-1], t[0…n-1])
//输入:模式串p和文本串t
//输出:第一个匹配模式串最左端字符的下标,如果没有匹配模式串,则返回-1
ShifTable(p[0…m - 1]) //生成移动表
i <-- m-1 //模式串最右端位置
while i <= n - 1 do
k<–0 //匹配字符的个数
while k <= m - 1 and p[m - 1 - k] = t[i - k] do
k <-- k + 1
if k = m //如果匹配字符个数与模式串个数相等
return i - m + 1;
else i <-- i + Table[t[i]]
return -1
例题
考虑在一个由英文字母和空格(用下划线表示)构成的文本中查找模式BARBER.
代码
#include<stdio.h>
#include<string.h>
#define max 1000
#define m 10
char mode[max] = {'J', 'I', 'M', '_', 'S', 'A', 'W', '_', 'M', 'E', '_', 'I', 'N', '_', 'A', '_', '_', 'B', 'A', 'R', 'B', 'E', 'R', 'S', 'H', 'O', 'P'};//文本串
char string[m] = {'B', 'A', 'R', 'B', 'E', 'R'}; //模式串
int Table[max]; //移动表
int HorspoolMatching(int a, int b){
/*建表过程*/
//a是模式串的长度,b是文本串的长度
/*Table是一个可能出现的字母表和它对应的应该移动步数,也就是某个字母应该
* 移动多少步这么一个表,但并不需要dictionary集合来完成,因为字符都是可以
* 转化为ASCALL码的,我们直接用每个不同的下标表示每个字母和符号即可,而值便是
* 应该移动的步数*/
for(int i = 0; i < b; i++)
Table[mode[i]] = a; //首先移动将表中每个元素都置为m,m就是模式的长度
/*移动表此时所以元素的值都是6,但是我们现在需要改变模式
* 中那几个字符对应的值,也就是说如果出现模式中的字符,我们
* 是不应该移动6格去的,而且模式中有可能出现相同字符的,比如
* BARBER,但是horspool算法是从模式右方进行匹配的,所以我们
* 赋值的时候应该从左往右去赋值,这样模式中字符对应的移动步数
* 的最后一次改写是在该字符最后一次出现的时候
*/
/*Table是char集合,p[i]对应是字母,循环的时候,比如模式
* 是BARBER,那么首先string[i]='B',table[B]=6-1-0=5,然后
* Table[A]=6-1-1=4;Table[R]=6-1-2=3;然后此时又是计算B了,
* 这就是为什么我们这里要从左向右赋值的原因,这样B又被再次赋值,
* 而此时的B是离右边最近的,Table[B]=6-1-3=2,Table[E]=6-1-4=1,
*/
for(int j = 0; j < a - 1; j++)
Table[string[j]] = a - 1 - j; //执行完后此时真正形成一个移动表,表中元素包含全部ACALL码对应的字符,所应该移动的步长
/*匹配过程*/
int i = a - 1; //光标先定于模式最后一位
int k = 0; //匹配模式字符串字符个数
while(i < b){
k = 0;
/*K用来计量此时匹配了几个,K<m则表示此时还没有匹配完,
* P[m-1-k]是模式中对应字符,t[i-k]是文本串中对应字符
* 如果相同则k++,继续对照前一个
*/
while(k <= a - 1 && string[a - 1 - k] == mode[i - k]){
k++;
}
if(k == a) //当匹配个数等于模式串个数,返回下标
return i - a + 1;
else
i = i + Table[mode[i]]; //否则移动对应的table移动表需要移动的距离
}
return -1;
}
int main(){
int index = HorspoolMatching(strlen(string), strlen(mode));
printf("模式匹配到的首位置是:%d", index + 1);
return 0;
}
运行结果
分析
虽然Horspool算法的最差效率属于O(nm),但对于随即文本来说,它的效率是属于O(n)的。而且,虽然效率类型相同,但平均来说,Horspool算法显然要比蛮力算法快许多。
结尾
写博客是为了一是整理所学知识,亲生写代码的经验,而是为了总结经典算法,三是督促自己努力,懂得越多,越知道自己知识的浅薄,四是希望和他人多多交流,有什么不对的地方大佬们多多指点
以上是关于Horspool算法的主要内容,如果未能解决你的问题,请参考以下文章
哪个是更好的字符串搜索算法? Boyer-Moore 还是 Boyer Moore Horspool? [关闭]