Horspool算法

Posted 望北i

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Horspool算法相关的知识,希望对你有一定的参考价值。

概念
horspool算法为字符串匹配算法,这是一个空间换时间的算法,算法把模式和文本的开头字符对齐,从模式的最后一个字符开始比较,如果尝试比较失败了,它把模式向后移。每次尝试过程中比较是从右到左的。我们不会像蛮力法那样移动一格,根据不同情况移动,有以下四种情况

  1. 如果模式中不存在c(在我们的例子中,c就是字母S),模式安全移动的幅度就是他的全部长度
    文本:S0 S1 S2……… D……………………

模式 B A R E B R

移动: B A R B E R

  1. 如果模式中存在c,但它不是模式的最好一个字符(在我们的例子中,c就是字母B),移动时应该把模式中最右边的c和文本中的c对齐:

文本:S0 S1 S2……… B……………………

模式: B A R E B R

移动: B A R B E R

  1. 如果c正好是模式中最后一个字符,但在模式中其他m-1个字符中不包含c,移动情况类似1,移动幅度为m

文本:S0 S1 S2…B R……………………

模式: B A R E B R

移动: B A R B E R

  1. 如果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,可以通过以下公式算出移动距离:

在这里插入图片描述

算法步骤

  1. 对于给定的长度为m的模式和在模式及文本中用到的字母表,按照上面的描述构造移动表。
  2. 将模式与文本的开始处对齐。
  3. 重复下面的过程,直到发现了一个匹配子串或者模式到达了文本的最后一个字符以外。从模式的最后一个字符开始,比较模式和文本中的相应字符,直到:要么所有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算法的主要内容,如果未能解决你的问题,请参考以下文章

Horspool算法

Horspool算法

Horspool算法-字符串匹配

哪个是更好的字符串搜索算法? Boyer-Moore 还是 Boyer Moore Horspool? [关闭]

哪个是更好的字符串搜索算法? Boyer-Moore 还是 Boyer Moore Horspool? [关闭]

LeetCode面试刷题技巧- 字符串匹配习题集