算法设计与分析——Horpool算法(字符串匹配算法)

目录

  • 前言
  • 一、算法思想分析
  • 二、算法效率分析
  • 三、算法代码
    • C语言代码
  • 后记

前言

在一段字符串中,查找另一段字符串是否在此字符串中,如果在,那么其位置是多少?这是一种经典的字符串匹配问题,但是字符串匹配问题其实还有非常多的拓展用法。 从算法的基础出发,应用到实际中,才是这个算法的作用最大体现。例如,搜索引擎中,就有字符串匹配算法的影子,一个好的字符串匹配算法,就能够大大提升一个搜索引擎的搜索速度。

一、算法思想分析

Horspool算法的核心思想是利用预处理的结果来减少字符串的比较过程,这里的预处理是针对需要查找的子字符串(或称作模式串
(以下思想分析篇幅可能有点长,请做好准备,如果不喜欢看大段文字的朋友,也可以先看算法理解再来看分析。)
那么如何对模式串进行预处理?在我们得到模式串时,我们将根据模式串来建立一张移动表,而在模式串和文本(被查找的字符串)比较时,模式串将从左向右移动,每次移动的距离就是由这张移动表决定的。
该移动表对应的是,在本次比较匹配失败后,如果文本中的某个字符与模式串中最后一个字符是比较对象(就是这个两个本次匹配在相互比较),那么模式串需要往右移动的距离。干讲起来有些拗口,举个栗子:在某次匹配时,其匹配结果是失败的,如果这个时候与模式串最后一个字符(具体要看模式串了)对应的字符是w,而在预处理表中,w对应的移动次数是3,那么这个时候模式串就向右移动3个单位(即位置+3),而如果在预处理表中没有w,那么,我们就需要移动模式串的长度个距离。
可是,我们的移动表是怎么得出来的呢?这张移动表的生成正是和它的作用相对应的,为什么我们能够按照如上的方法来使用移动表呢?这就是Horpool算法的精妙之处了。
首先,Horpool算法的字符串比较是从模式串最后一个字符来和文本的对应位置来一个一个比较的。这也就说明了,我们是以模式串的最后一个字符为基准的。想象一下,如果我们有一次匹配失败了,如果在文本中与模式串最后一个对应的字符根本就不在模式串中,我们还有必要一个一个来移动的比较么?是不是可以直接跳过模式串长度个距离来进行下一次匹配?而相对应的,如果这个对应的字符在模式串中,那么我们是不是只要移动模式串中这个字符距模式串最后一个字符的最近距离?注意一下,这里必须是最近距离!
所以,我们的移动表生成方法出来了。对模式串进行处理,将模式串中有的字符与这个字符距最后一个字符串的距离做对应,就生成了移动表了,而不再模式串的,一概对应的都是模式串的长度。但是,这个地方我们需要注意的是,这个距离最小是1,不存在0。
光说的话可能有点稀里糊涂,下图举个栗子来说:
算法设计与分析——Horpool算法(字符串匹配算法)_第1张图片
上图不仅将移动表附上,比较过程同样附上,可以看出,第五次比较已经匹配成功,大家可以参考一下。下面再附上《算法设计与分析基础》中的示例来帮助大家更好的理解。
算法设计与分析——Horpool算法(字符串匹配算法)_第2张图片
所以,算法思想总结为:
算法设计与分析——Horpool算法(字符串匹配算法)_第3张图片

二、算法效率分析

假设模式串的长度为m,文本长度为n。那么在最差的情况下,算法效率是 O(n*m) ,看起来似乎是和蛮力法(一个一个的去比较。。。)差不多的。但是对于随机文本来说,Horpool算法的效率是处于 O(n) 的,只有在极端情况(也就是最坏情况)下才会出现 O(n*m) 的情况。平均来说,Horpool算法显然是比蛮力法快得多的。

三、算法代码

C语言代码

在代码中,我附上了许多测试数据,大家可以尝试进行一下测试,如果有什么疑问,欢迎留言或私信。

/* Horspool 算法 */
#include
#include
#define MAXN 1000000
#define CHARLEN 128
char P[MAXN],T[MAXN];
/*ASCII 128位字符*/
int Table[CHARLEN];
/*生产移动表算法*/
void ShiftTable(int m) {
	// m模式字符串长度
	for(int i=1; i<=CHARLEN; i++) {
		Table[i]=m;
	}
	for(int i=0; i<m-1; i++) {
		Table[P[i]]=m-1-i;
	}
	return ;
}

/* 匹配算法 */
int HorspoolMatching(int m,int n) {
	// m是模式长度 n是文本长度
	int i=m-1;   // 刚开始 模式尾部
	while(i<=n-1){
		printf("\n尾部元素待过的位置:%d\n",i);
		int k=0;
		while(k<=m-1 && P[m-1-k]==T[i-k]){
			
			k++;  // 计算匹配的个数 这个状态 
		}
		printf("当前位置的比较次数:%d\n",k==m?k:k+1); // 自拟代码 计算比较次数 
		if(k==m) return i-m+1; // 如果我们匹配到的个数位模式长度  那么直接返回查找字符串在的文本的位置 
		else i=i+Table[T[i]]; // 如果我们没有匹配到全部 按照移动表来进行移动 
	}
	return -1; // 默认返回-1 如果我们匹配完了 都还没有找到 就是-1 
}

/*主函数*/
/*
测试数据一 (书本数据 见截图) 
BARBER
JIM_SAW_ME_IN_A_BARBERSHOP

测试数据二 (书本习题一) 
BAOBAB
BESS_KNEW_ABOUT_BAOBABS

测试数据三
00001
1000个0

测试数据四
10000
1000个0 

*/
int main() {
	printf("请输入模式字符串:\n");
	gets(P);
	/* 生产表 */
	ShiftTable(strlen(P));
	printf("\n文本字符串:\n");
	gets(T);
	// 为1000个0准备的测试数据 
//	for(int i=0;i<1000;i++){
//		T[i]='0';
//	} 
	/* 开始匹配 */
	int index = HorspoolMatching(strlen(P),strlen(T));
	/* 结果 */	
	printf("\n模式匹配到的首位置是:%d",index);
}

后记

其实在高效的字符串匹配算法中,Horpool算法算是好理解的了,下次要分析的KMP算法,其思想就比较难以理解了,有一个关键的地方需要好好理解。
在我看来,Horpool算法是个非常巧妙的算法(词穷…),利用了预处理机制,跳过一些没有必要比较的位置。
这次介绍就到这里了,如果各位对文章或算法有什么疑问或建议,欢迎私信或留言,如果有错误,也可指出,感激不尽。

你可能感兴趣的:(算法设计与分析)