字符串匹配算法(BF算法与KMP算法)

文章介绍了BF算法,一种简单的字符串匹配方法,其时间复杂度为O(n*m)。接着讲解了KMP算法,它通过next数组避免了主串回退,提高了匹配效率,降低了时间复杂度。KMP算法的关键在于计算next数组,用于确定在失配时字串应回退的位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.BF算法

BF算法也叫朴素算法,就是字面意思,简单粗暴的将主串和字串进行匹配

当开始匹配 的时候,i和j同时往后跑, 一直到发生失配:

匹配到这一步是的时候发生失配,失配之后字串回到头部,而主串回到 i - j + 1的位置处 

也就是说,i必须回退到这次失配发生时的起点的下一个位置。

然后继续匹配,重复上面的过程。一直到匹配成功或者主串遍历完都没有匹配到结果的话,就结束。

可以看到,BF算法的时间复杂度很高,通常情况下他的时间复杂度为O(n*m),也就是O(n^2),其中n为主串的长度,m为字串的长度。

虽然时间复杂度很高,但是BF算法的思想很容易理解,代码写起来也比较容易:

int BF_string()
{
	const char* s = "abcabcdabcde";
	const char* t = "bcda";
	int slen = strlen(s);
	int tlen = strlen(t);
	int i = 0;
	int j = 0;
	while (i < slen&&j<tlen)
	{
		if (s[i] == t[j])
		{
			++i;
			++j;
		}
		else
		{
			i = i - j + 1;
			j = 0;
		}
	}
	if (j >=tlen)
	{
		return i-j;
	}
	return -1;
}

2.KMP算法

由于BF算法的时间复杂度略高,所以就诞生了KMP算法。

KMP算法与BF算法不同的地方在于,它不再是暴力求解,并且它的时间复杂度要远远低于BF算法。这是因为在KMP算法中,指向主串的i不用回退,所以提高了匹配的效率。可以说 它是对BF算法的一种改进

前面都和BF算法一样,但是当发生失配时, 就会发生俩种情况:

(1).第一种:失配时,子串前方没有相同的俩个字符或字符串,就像上面的图举得例子一样,字串前几个元素俩俩互不相同,但是字串前面那几个元素已经和主串进行过比较了并且是相同的,所以主串的前几个元素也是俩俩不相同。这个时候就 没有必要去回退  i  了,因为即使 i 回退了,也是没有效果的,前面那几个元素都俩俩不相同,i 的回退就没有意义了,无论怎么匹配都会失配的。

 (2).第二中情况就是发生失配时,字串前面有字符或字符串相等,并且相等的俩个字符串必须是:一个字符串的头为字串的头,另一个字符串的尾紧挨着发生失配的地方。比如这种:

 发生失配时的第二种情况。这个时候,按理来说i 应该需要回退,但是在KMP算法中,它使用j的回退来代替了i的回退。本来i 应该回退到第二个a位置,然后j去回退到头,但是这样再去比较的话,后俩个字符一定是 匹配成功的,所以干脆让i不动,j 回到头后自己向后走俩步,也就是回退到第二个a位置。

所以,KMP算法的难点就在于如何去计算j 应该回退的位置。每个j在发生失配的时候都有一个自己和回退位置,这个位置可以保存在next数组中,那么如何去求next数组就是个难题。

next数组是只与字串本身有关 的一个数组,和主串无关。每个字符串都有一个对应的next数组。

可以设计这样一个算法:刚开始时令 j 指向字串中第 1 个字符(j=1),i 指向第 2 个字符(i=2)。接下来,对每个字符做同样的操作:

如果 i 和 j 指向的字符相等,则 i 后面第一个字符的 next 值为 j+1,同时 i 和 j 做自加 1 操作,为求下一个字符的 next 值做准备;

如果 i 和 j 指向的字符不相等,则执行 j=next[j] 修改 j 的指向,然后以同样的方法对比 i 和 j 指向的字符,以此类推。当 j 的值为 0 时,将 i 后面第一个字符的 next 值置为 1。

再将i换成 j +1;

代码实现起来也比较好理解:

int* Get_next(const char* t)
{
	int len = strlen(t);
	int* p = new int[len] {};
	int j = 1;
	p[0] = -1;
	if (len > 1)
	{
		p[1] = 0;
	}
	while (j + 1 < len)
	{
		if (t[j] == t[p[j]])            
		{	                           
			p[j + 1] = p[j] + 1;      
		}
		else
		{
			p[j + 1] = 0;
		}
		++j;
	}
	
	for (int i = 0; i < len; ++i)
	{
		cout << p[i] << "   ";
	}
	return p;
}

int KMP_string()
{
	const char* s = "abcabcdabcde";
	const char* t = "aabbaac";
	int slen = strlen(s);
	int tlen = strlen(t);
	int i = 0;
	int j = 0;
	int* next = Get_next(t);
	while (i < slen && j < tlen)
	{
		if ((j==-1)||s[i] == t[j])
		{
			++i;
			++j;
		}
		else
		{
			j = next[j];
		}
	}
	if (j >= tlen)
	{
		free(next);
		return i-j;
	}
	free(next);
	return -1;
}

感谢阅读!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

g162512

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

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

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

打赏作者

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

抵扣说明:

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

余额充值