BF算法、KMP算法

在串和子串匹配的问题中,一般会存在两种算法:BF算法KMP算法
比如在串:"QBFHILMEACHILK"中查看是否存在模式串"HILK"问题

首先,先说基础的BF算法,又叫做朴素匹配算法,用的是穷举的方法。
基于上面的问题,BF算法的思路是这样的:
(1)首先会将主串按顺序和模式串的第一个字母进行比较
在这里插入图片描述
(2)接下来继续匹配,仍然没有匹配上
在这里插入图片描述
在这里插入图片描述
(3)到这一步,终于可以有匹配的元素了
在这里插入图片描述
但是我们可以看到,在最后一个元素K的匹配上出现了问题,那么这个时候就是没有完全的匹配上,我们还需要继续往下走。那么这个时候我们需要怎么做呢?
我们应该回退到4号下标 i 的位置重新进行匹配,否则的话会遗漏掉一部分内容的匹配,会出现bug,所以在这里会有指针回退的处理
(4)接下来,按照上述流程往下走,一直到有串匹配。
指针回退位置:x = i - 已经匹配的个数 + 1;

int BF(std::string s,std::string p)
{
	int i = 0;	//遍历S主串
	int j = 0;	//遍历P模式串
	while(i < s.size() && j < p.size())//有遍历完成的就退出
	{
		if(s[i] == p[j])
		{
			i++;
			j++;
		}
		else
		{
			//在模式串中j的数值就是已经匹配的个数,所以指针i回退的位置应该是i-j+1;
			i = i - j + 1;	//下一趟主串需要匹配的位置
			j = 0;	//模式串回退到头部
		}
	}
	return (j == p.size()) ? (i-j) : -1;
}

归根结底,BF算法就是利用了穷举,然后一一对比,这种方法是很浪费时间的。
——————————————————————

接下来是比较牛的KMP算法:

我们可以发现,之所以造成了时间的浪费,在于我们把所有的情况都列举出来了,那么我们有没有什么办法,可以去电一部分呢?我们是这样处理的,在KMP算法中,让主串的指针不回退,只让模式串的指针回退。这样就会节省很多的时间。

核心思想:为了找到可以省略的部分元素比较,比如下面元素的匹配:
在这里插入图片描述
省略1:在这里插入图片描述

省略2:
在这里插入图片描述
省略3:
在这里插入图片描述
我们可以看到,省略1和2是因为开头就不匹配,最重要的就是省略3中的内容,我们怎么可以让主串中的指针不动,然后让模式串中的指针回退到相应的位置上呢?
如果,主串中的后缀 == 模式串中的前缀,如图所示:ABC == ABC,所以此时我们可以忽略这部分的比较,直接让主串中的指针从竖线部分往下比较就可以了,因为前面部分已经是比较过了。而在失配点之前都是已经匹配好的元素,所以主串中的后缀 == 模式中的后缀,所以有如下式子:
目的是寻找:主串中的后缀 == 模式串中的前缀
寻找的条件:主串中的后缀 == 模式中的后缀
总结:寻找模式串中的后缀和前缀相同元素的个数,就是模式串中指针每次返回的位置。

下面举一个例子来说明最大前缀和最大后缀的问题:"ABCABCD"
1.如果在A失配,我们统一给数值-1,因为就没有匹配,此时后缀和前缀相同元素的个数为-1;
2.如果在B失配,可以看到没有满足条件的,所以此时后缀和前缀相同元素的个数为0;
3.如果是第二个B失配,那么前面的ABCA,此时就是有一个元素A满足(最大前缀 = 最大后缀),所以此时后缀和前缀相同元素的个数为1.
4.如果是第二个D失配,那么前面就是ABCABC,此时后缀和前缀相同元素的个数为3.
那么这些元素就构成了一个数组next,到时候指针回退的位置就一目了然了。还有一个特殊的值就是-1,我们不可能回退到-1的位置,那么j == -1代表什么呢?
在这里插入图片描述
此时,失配点前面的元素没有一个是可以匹配的上的,这就是j == -1所表示的意思,那么我们的处理就是让两个指针都向后移动一下就可以了。

  • 那么我们怎么求解这个next数组呢?即怎么求最大的匹配数呢?简单的串我们可以肉眼去看,但是怎么用通解去实现呢?
    如果失配点之前K前缀和后缀相同,即:
    t[0] t[1] … t[k-1]
    t[j-k] t[j-k+1] … t[j-1]
    这两者是相同的,如果此时再多加一个元素呢?
    t[0] t[1] … t[k-1] t[k]
    t[j-k] t[j-k+1] … t[j-1] t[j]
    除了黑体外其他的部分都是一样的,所以我们只需要判断新添加的元素是否相等就可以了。
void GetNext(std::string t,int *next)
{
	int len = t.size();
	int j = 1;
	int k = 0;
	if(len == 0)
	{
		return ;
	}
	next[0] = -1;

	//至少有两个元素才有处理的必要
	if(len > 1)
	{
		next[1] = 0;

		//因为每次求的是j+1的位置
		while(j < len -1)
		{
			if(k == -1 || t[k] == t[j])
			{
				next[++j] = ++k;
			}
			else
			{
				//最精髓的就是这一句,画图
				k = next[k];
			}
		}
	}

}

int KMP(std::string s,std::string t)
{
	int slen = s.size();
	int tlen = t.size();
	int i = 0;//主串的下下标
	int j = 0;//模式串的下标
	
	//用于存放指针回退下标
	int* next = (int*)malloc(sizeof(int)*tlen);
	GetNext(t,next);

	while(i < slen && j < tlen)
	{
		if(j == -1 || s[i] == t[j])	//在这里我们要明白j == -1 的意义,当j == -1的时候,就是主串和模式串当前没有匹配的元素。
		{
			i++;
			j++;
		}
		else
		{
			//模式串回退,主串不回退
			j = next[j];
		}
	}
	return (j == tlen) ? (i-j) : -1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值