模式匹配——从BF算法到KMP算法

 1.BF算法

即暴力算法,是普通的模式匹配算法

算法思想:

将主串S的第一个字符与子串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符与T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。

普通模式匹配算法,其实现过程没有任何技巧,就是简单粗暴地拿一个串同另一个串中的字符一一比对,得到最终结果。

代码复杂度

最理想的时间复杂度为O(n),即第一次匹配成功,n为子串的长度

最坏情况的时间复杂度O(n*m),即两个串每次匹配,都必须匹配至子串的最末尾才能判断匹配失败,n为子串长度,m为主串长度。

BF算法实现

int BF(const char* str, const char* sub, int pos)//O(n*m)
//pos指从主串中该位置开始进行比较
{
	assert(str != NULL && sub != NULL);
	if (str == NULL || sub == NULL || pos<0 || pos>strlen(str))
	{
		return -1;
	}
	int i = pos;
	int j = 0;
	int lenstr = strlen(str);
	int lensub = strlen(sub);
	while (i < lenstr && j < lensub)
	{
		if (str[i] == sub[j])//相等,往后走
		{
			i++;
			j++;
		}
		else//不相等,i回退到刚才位置的下一个+1,j回退到0
		{
			i = i - j + 1;
			j = 0;
		}
	}
	//利用子串是否遍历完成,来判断查找是否成功,注意:不能利用主串来判断;
	if (j >= lensub)
	{
		return i - j;
	}
	else
	{
		return -1;
	}
}

int main()
{
	const char* str1 = "ababcabcdabcde";
	const char* str2 = "abcd";
	printf("%d  \n", BF(str1, str2, 6));//9
	printf("%d  \n", BF(str1, str2, 10));//-1

	return 0;
}

2.KMP算法

KMP算法不回退主串的指针i,借助next数组中储存的信息把子串移动到正确的位置继续进行匹配,时间复杂度为O(n+m),m为主串长度,n为子串长度

理解next数组

next 数组各值的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀。例如如果next [j] = k,代表j 之前的字符串中有最大长度为k 的相同前缀后缀。

也意味着在某个字符失配时,该字符对应的next 值会告诉你下一步匹配中,模式串应该跳到哪个位置(跳到next [j] 的位置)。如果next [j] 等于0或-1,则跳到模式串的开头字符,若next [j] = k 且 k > 0,代表下次匹配跳到j 之前的某个字符,而不是跳到开头,且具体跳过了k 个字符。

求解next数组

计算next数组,只与子串有关

next数组的本质是寻找子串中“相同前后缀的长度”,并且一定是最长的前后缀

 

 首先对于第一个字符,显然不存在比它短的前后缀,所以next直接为0

 接着对于前两个字符,同样没有相同的前后缀,所以next为零

对于前三个字符,由于A是公共的前后缀,所以next为1

对于前四个字符,由于AB是共同的前后缀,next为2

对于前五个字符,找不到相同的前后缀,next为0

由此得到整个next数组。

KMP算法实现

static int* GetNext(const char* str)
{
	int len = strlen(str);
	int* next = (int*)malloc(len * sizeof(int));
	next[0] = -1;//不能再退;
	next[1] = 0;//j=1,k=0;

	int j = 1;
	int k = 0;
	while (j+1 < len)
	{
		if ((k==-1)||str[k] == str[j])
		{
			next[++j] = ++k;
			/*next[j + 1] = k + 1;
			j = j + 1;
			k = k + 1;*///OK
		}
		else
		{
			k = next[k];
		}
	}

	return next;
}

int KMP(const char* str, const char* sub, int pos)//O(n*m)
{
	assert(str != NULL && sub != NULL);
	if (str == NULL || sub == NULL || pos<0 || pos>strlen(str))
	{
		return -1;
	}
	int i = pos;
	int j = 0;
	int lenstr = strlen(str);
	int lensub = strlen(sub);

	int* next = GetNext(sub);
	while (i < lenstr && j < lensub)
	{
		if ((j==-1)||str[i] == sub[j])//相等,往后走
		{
			i++;
			j++;
		}
		else//不相等,i不回退,
		{
			//i = i - j + 1;//i不回退
			j = next[j];
		}
	}
	free(next);
	//利用子串是否遍历完成,来判断查找是否成功,注意:不能利用主串来判断;
	if (j >= lensub)
	{
		return i - j;
	}
	else
	{
		return -1;
	}
}

int main()
{
	const char* str1 = "ababcabcdabcde";
	const char* str2 = "abcd";
	printf("%d  \n", KMP(str1, str2, 0));//5
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值