字符串匹配算法-----Sunday

哈希查找–一种非常高效的查找算法!!!

字符串匹配算法有很多种,今天我们就来讲一下其中一个匹配算法Sunday。Sunday算法较为容易理解,比KMP算法容易理解的多!!!而且,Sunday算法的匹配速度非常快,大家一定要掌握!!!

首先,我们先准备两个用于匹配的字符串,一个是主字符串,用于在这里面匹配匹配字符串,一个是匹配字符串。

主:“csapadgsdgdfegapgladgcsdnnsdcaqgeqtdf”
匹:“csdnnsdcaq”
在这里插入图片描述

算法过程

首先将匹配字符串与主字符串的开头对其,进行一个一个的比较字符串是否相等,如果相等则同时向后移动,直到第一个不相等的字符。如上图所示,第一个不相等的字符为主字符串中的a对应匹配字符串中的d。现在我们知道主字符串和匹配字符串此时是不匹配的,那么我们知道从主字符串和匹配字符串对齐位置处开始,往后数匹配字符串的长度(这里是10)个字符,此时对应的字符串一定和匹配字符串是不匹配的,如下图所示:
在这里插入图片描述
如上图所示,上面用虚线框柱的主字符串中的子字符串一定与匹配字符串不匹配。由于在此处时主字符串和匹配字符串不匹配,因此我们可以直接略过这个位置,看虚线框后面的一个字符,然后查找该字符在匹配字符串中从后往前数第一次出现的位置,并将这两个字符对其,重新将主字符串和匹配字符串进行比较,如下图所示:
在这里插入图片描述如上图所示,重新将两个字符串对其,即将这里的d字符对其,然后重新从匹配字符串的开头进行匹配两个字符串。匹配的第一个字符为主字符串的a与匹配字符串的c,显然不相等,因此,同样看从主字符串匹配开始处数匹配字符串长度个单位,这个长度之后的字符为a,因此在匹配字符串中从后往前查找a第一次出现的位置,将两个a对齐,即重复上一个步骤,如下图所示:
在这里插入图片描述
Sunday匹配算法的大致过程就是这样,只要当前有字符不匹配,那么从匹配开始处数匹配字符串长度个字符,这些个字符肯定不匹配,既然这些一定不匹配,那么我们就直接看匹配字符串长度的下一个字符,然后在匹配字符串中查找该字符从后往前,第一次出现该字符的位置,然后将这两个字符对其。看到这里,大家有没有发现仅这样匹配,有没有什么问题?我相信大家一定是会有哪些地方感觉不对。确实,如果仅按照上面这种方式,还有些问题,问题如下:

如果虚线框后的那个字符没有在匹配字符串中出现怎么办?

对于这种情况,其实很好解决。如果虚线框后面的这个字符不存在与匹配字符串中,那么如果主串中如果含有这个字符,那么匹配字符串能匹配成功吗?显然不能!!!,这个字符都不存在于匹配字符串中,怎么肯能匹配成功呢!!!因此,对于这种情况,我们直接将下一次匹配字符串的开头与下一个字符进行匹配!!!上图所示,虚线框后的字符g在匹配字符串中并未出现,因此我们直接将匹配字符串于g后的下一个字符对齐,如下图所示:
在这里插入图片描述如上图所示,在此种情况下,将主串中的I与匹配串中的c进行对齐,重新开始匹配。

如果虚线框后的那个字符在匹配字符串中出现了多次怎么办?

对于这个问题,并不复杂,如果匹配字符串中有多个虚线框后的那个字符,我们也是只考虑从匹配字符串后开始向前查找的第一个。为什么只考虑第一个就行了呢?其实这里我们并不需要在意这个字符出现了几次,甭管这个字符出现了几次,那么在此时这个匹配串和对应的同等长度的主串中的子串一定是不匹配的,那么既然此时都不匹配,我们同样是考虑虚线框后的那一个字符然后再重新在匹配串中查找该字符串的位置,重复操作,字符出现几次并不会影响我们这个操作,我们只需要考虑从后往前查找到的第一个字符位置就可以。

为什么我们要从后往前来查找与虚线框后那个字符相等的第一个字符,而不是从前往后查找?

下面我们在来看一下这个问题,为什么要从后往前查找,而不是从前往后查找匹配字符串中的字符呢?其实这个问题也很简单,不过这要结合着上面这个问题一起看,就是如果字符出现了多次,并且我们从前往后查找这个字符的位置,并且只看该字符第一次出现的位置,那会出现什么情况?从上面的图接着,虚线框后面的字符为d,然后我们从前往后在匹配字符串中查找字符d第一次出现的位置对齐,对齐后情况如下,我们先来看一下下图:

在这里插入图片描述前面的虚线框是我们对齐之前的位置,后一个是依据从前往后查找字符第一次出现的位置,下面我们同样依据前面的位置从后往前查找字符第一次出现的位置,如下图所示:
在这里插入图片描述上图是从后往前查找字符第一次出现的位置,下面我们将两种情况放一起对比分析一下,如下图:

在这里插入图片描述从上面我们可以看出,如果是由前往后查找字符第一次出现的位置,那么该匹配字符移动的距离将大于由后往前查找字符移动的距离。并且我们也可以看到,如果是由前往后查找,这种情况略过了其中可以成功匹配的字符串,组中将导致匹配失败,未成功匹配到字符串,然而主串中含有可成功匹配的子串;而由后往前查找字符则成功匹配了该字符串。现在我们应该清楚为什么要从后往前查找字符了吧,就是为了防止漏掉匹配字符串这种问题。

至此,Sunday算法算是基本上结束了,但是还是有一点小缺陷。从上面的过程,我们可以看出Sunday算法要比KMP算法快,但是,此种Sunday算法还不够好,还有需要改进的空间,那么怎么改进呢?
我们回顾一下Sunday算法的实现过程就可以发现问题,我们现在来分析一下Sunday算法的时间花费。首先,Sunday算法的实现过程中有诸葛字符进行判断是否相等的判断过程;有查找虚线框后的字符在匹配字符串中的位置的过程;有对齐的过程。对于判读字符是否相等的过程,这个是必须要做的。而对齐过程,只需一行代码就可以对齐,也并不耗费时间;然而,在查找字符时,我们就需要遍历匹配字符串来查找字符的位置,因此,在查找的时候我们需要花费大量的时间。因此,Sunday算法的实现过程中,其时间耗费大多是在查找虚线框后的这个字符在匹配字符串中的位置时所需的花费。如果我们能减少查找字符的时间花费,那么我们就能大大减少Sunday算法的时间复杂度。
怎么减少查找时间呢?
咦~,不知道大家在这里有没有想起一种非常高效的查找算法,哈希查找,其时间复杂度为O(1),因此可以很大程度上的减少我们的Sunday算法的时间复杂度。而我们在这里对Sunday算法的改进就是通过哈希查找算法来对Sunday算法进行优化。(哈希查找算法的具体实现可以看我上面文章,文章链接在本文开头已给出)

Sunday算法优化

对于使用哈希查找,那么我们第一步就是要建表。由于我们是要在匹配字符串中查找,因此只需要对匹配字符串进行建立哈希表即可。另外,我们查找的时候只看从后往前的字符第一次出现的位置。在我们建表时,只需要从前往后,依次对各个字符建立关系即可,因为对于重复的键,后出现的将会覆盖前面出现的,而这恰好可以实现我们想要的从后往前查找字符的效果!!!

匹配成功

上面并没有讲字符何时匹配成功,其实很简单,我们知道字符串的末尾都有一个‘\0’,只要匹配字符串走到’\0’,那么就表示字符串匹配成功。

代码实现

#include <stdio.h>
#include <string.h>

#define Number 256

//创建Hash_Table
void CreateHashTable(int arr[],const char * src)
{
	if(src == NULL) return;

	//将哈希表中每个值都初始化为-1
	for(int i = 0;i < Number;i++)
		arr[i] = -1;
	int i = 0;
	while(src[i] != '\0')	
	{
		//键值对
		arr[src[i]] = i;
		++i;
	}
}
//Hash_Search
int HashSearch(int arr[],const char ch)
{
	return arr[ch];
}
int Sunday(const char *main_src, const char *mat_src)
{
	if(main_src == NULL || mat_src == NULL) return -1;//-1 表示匹配失败

	int index = -1;
	int i = 0,j = 0;
	int nLen = strlen(mat_src);
	int position = -1;

	int arr[Number];
	CreateHashTable(arr,mat_src);

	while(main_src[i] != '\0' && mat_src[j] != '\0')
	{
		if(main_src[i] == mat_src[j])
		{
			++i;
			++j;
		}
		else
		{
			//哈希表中查找字符位置
			position = HashSearch(arr,main_src[i - j + nLen]);
			if(position == -1) 
				i += nLen - j + 1;
			else
				i += nLen - position - j;
				j = 0;

				//如果主串剩余的字符小于匹配串的长度,直接退出
				if(i + nLen > strlen(main_src))
					break;
		}
	}
	if(mat_src[j] == '\0')
		index = i - j;
	//返回匹配位置
	return index;

}

int main(void)
{
	const char *main_src = "agfhgthyuyiuiijgd";
	const char *mat_src = "jgdd";

	printf("main character is [%s]\nmatching character is [%s]\n",main_src,mat_src);
	int index = Sunday(main_src,mat_src);
	printf("matching character in main character position is [%d]\n",index);

	return 0;
}
  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bug.Remove()

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值