(数据结构)串匹配算法——BF算法和KMP算法

串匹配

在主串中查找子串的位置

		主串S
		子串P

BF算法

定义两个控制变量i和j,i初始化在主串S串的0号位置,j初始化在子串P串的0号位置。

  • 若比较得出该位置主串和子串的元素相等,那么i++,j++;
  • 若比较得出该位置主串和子串的元素不相等,i回退到本次比较开始的下一个位置,j回退到0位置,再开始比较;(这里隐含了一种穷举的思想:相当于S串以每一个字符作为开始,和P串进行比较,直到找到为止)
    效率分析
    时间复杂度O(m*n)
    空间复杂度O(1)
int BF(const char *str,const char *sub,int pos)
{
	int len1 = strlen(str);
	int len2 = strlen(sub);
    
	int i = pos;
	int j =0;

	if(str  == NULL || sub == NULL || pos  < 0 || pos >= len1)
	{
		return -1;
	}

	while(i < len1 && j < len2)//主串未遍历完
	{
		if(str[i] == sub[j])
		{
			i++;
			j++;
		}
		else
		{
			//i回退到本次开始(i-j)的下一个位置,j退到0
			i= i-j+1;
			j = 0;
		}
	}

	if(j == len2)//子串已遍历完
		return i - j;
	else
		return -1;
}

KMP算法

前情摘要:
BF算法:当比较出现失配时,
对于主串S,i回退到上一次比较的下一个位置,即i = i - j + 1
对于子串P,j回到0位置,即j = 0
重点要学会理解算法的思想,其实代码实现并不难。
进入正题:
KMP的基本思想:
该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
对于每个子串 P 的每个元素 Pj,都存在一个实数 k ,使得子串 P 开头的 k 个字符(P 0 P 1…Pk-1)依次与 P j 前面的 k(P j-k P j-k+1…P j-1,这里第一个字符 P j-k 最多从 P1 开始,所以 k < j)个字符相同。如果这样的 k 有多个,则取最大的一个。子串 P 中每个位置 j 的字符都有这种信息,采用 next 数组表示,即 next[ j ]=MAX{ k }。

简单理解

在子串P中找k位置(失配时j应该回退的位置)的方法:
在P串中,每个位置之前,找两个完全相等的最长真子串,其中前一个子串以0位置开始后一个子串以当前位置-1结束,则当前位置的k值就是子串的长度,就是j失配的回退值

存储k值:next数组

大小应该和P串的长度相等

> next[0] = -1; 
> next[1] = 0;

其实,在网上有很多教程的next数组的前两位设定和我们所写的不太一样

> next[0] = 0; 
> next[1] = 1;

我们来讨论一下:
例如,对于下面的例子:

  		a   b    c    a   b   a     b   c   a   b    c 

k值 —— -1 0 0 0 1 2 1 2 3 4 5

  c   a    b    c......

在第一个位置就相当于失配了,那么在指针回溯时,子串P的next[j] = 0,还是当前位置,会进入一个死循环,因此,此时唯一地解决方法就是让主串指针i++;,而这与KMP的思想相悖,因为我们并不想操作和移动主串的指针。
当我们处理这种情况时,可以考虑同步步进子串p回溯到-1后再++;还是跑到0位置,而主串的指针i也++,这样就可以避免死循环了,将j == -1的情况写到str[i] == sub[j]的情况中去,也可以简化我们代码的书写。
实现如下:

int KMP(const char *str,const char *sub,int pos)
{
	int len1 = strlen(str);
	int len2 = strlen(sub);
    
	int i = pos;
	int j =0;

	if(str  == NULL || sub == NULL || pos  < 0 || pos >= len1)
	{
		return -1;
	}

    //p串的next数组
	int *next = (int*)malloc(len2 * sizeof(int));
	assert(next != NULL);

	GetNext(sub,next);

	while(i < len1 && j < len2)
	{
		//j == -1必须放在最前面,若把这个条件写到else内,则程序跑到这里会崩溃
		if( j == -1 ||  str[i] == sub[j])
		{
			i++;
			j++;
		}
		else
		{
			j = next[j];
		}
	}
	if(j >= len2)
		return i - j;
	else
		return -1;
}

下面的重点就是,如何通过算法实现GetNext(sub,next)
基本思想:
求i-+1位置的next[i+1]就是它和next[i]的关系,可以简单理解为寻求一个函数关系:

  				 next[i+1] = F(next[i])

假设已知:next[i] = k
则:P0…Pk-1 == Pi-k…Pi-1
那么对于Pk和Pi来说,它们有什么关系呢?
分以下两种情况讨论:

  1. Pk == Pi
  2. Pk != Pi

第①种情况:
可以推出:
P0…Pk == Pi-k…Pi
串的长度是k+1,即next[i+1] = k+1
———————————————————
第②种情况:
k = next[k];
在这里插入图片描述

void GetNext(const char *sub,int *next)
{
	int len =strlen(sub);
	
	next[0] = -1;
	next[1] = 0;

	int i = 1;
	int k = 0;
	
	while(i + 1 < len)
	{
		if(k == -1 || sub[k] == sub[i])
		{
			next[++i] = ++k;
			//next[i+1] = k+1;
			//k++;
			//i++;
		}
		else
		{
			//KMP的思想
			k = next[k];
		}
	}
}

今天就这么多!
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值