软件设计师之改进的模式匹配算法-KMP算法(实例、公式、代码),内容很多你忍一下~~

首先我们来看一下教材(教材讲的非常高大上不理解也没关系,继续往下看就行了):
在这里插入图片描述
在这里插入图片描述

然后教材还给了一个例题:
在这里插入图片描述
如果看不懂上面的内容的话,从这开始往下看也没问题哦~
其实这个例题一句句的看,结合上面的公式还是勉强能理解的,只不过解题过程可能会比较慢,总的来说匹配的关键就是移位,移位的关键就是上面那个表里面next[i]里面的值。
next[j]=k,含义是:下标为j 的字符前的字符串最长相等前后缀的长度为k。 我认为理解这个是比较关键的,接下来我来解释下什么是最长相等前后缀。
比如有个字符串是 abcdab
所谓的前缀集合我们就可以理解为去掉最后一个字符,然后从第一个字符往后迭代,每一次迭代时的长度增加1然后存到集合里面,后缀集合相反,于是有了下面这个结果
前缀的集合:{a,ab,abc,abcd,abcda}
后缀的集合:{b,ab,dab,cdab,bcdab}
通过对比两个集合,可以看到相同的部分为ab,长度是2,所以最长相当前后缀长度就是2
再次回到上面不好理解的例题,子串是abababb,我们随便算一个j=4的情况吧,next[4]对应的第五个字母也就是a,我们刚才说了:next[i]=j,含义是:下标为i 的字符的字符串最长相等前后缀的长度为j。这个“前”字很重要,所以我们需要的是第五个之前的四个字母,也就是abab,于是乎:
前缀的集合:{a,ab,aba}
后缀的集合:{b,ab,bab}
可以得到相等的部分是ab,长度是2,所以上面的next[4]对应的值是2,似不似赶赶单单。
那么这个时候编教材的人不乐意了,“劳资辛辛苦苦给你们弄个公式,你们却用这种方法投机取巧”,那么接下来我们再用公式算一下这个next[4]:
子串abababb
在这里插入图片描述

根据公式,当j=4的时候,k的值可以为1,2,3,我们先用大的值进行测试(因为求得是最长相等前后缀,大值满足条件就不用管小的了),看看其满不满足后面的条件
在这里插入图片描述

下面分析里面的P0代表子串的第一个字母,P1代表第二个,以此类推:
当k=3时,等式左边为P0P1P2=aba 等式右边为P1P2P3=bab 左边不等于右边,等式不成立
当k=2时,等式左边P0P1=ab 等式右边P2P3=ab 左边等于右边,等式成立,所以next[4]=2

相关代码及改进代码(参考其它KMP算法文章):

typedef struct
{	
	char data[MaxSize];
	int length;			//串长
} SqString;
//求next数组
//SqString 是串的数据结构
//typedef重命名结构体变量,可以用SqString t定义一个结构体。
void GetNext(SqString t,int next[])		//由模式串t求出next值
{
	int j,k;
	j=0;k=-1;
	next[0]=-1;//第一个字符前无字符串,给值-1
	while (j<t.length-1) 
	//因为next数组中j最大为t.length-1,而每一步next数组赋值都是在j++之后
	//所以最后一次经过while循环时j为t.length-2
	{	
		if (k==-1 || t.data[j]==t.data[k]) 	//k为-1或比较的字符相等时
		{	
			j++;k++;
			next[j]=k;
			//对应字符匹配情况下,s与t指向同步后移
			//通过字符串"aaaaab"求next数组过程想一下这一步的意义
			//printf("(1) j=%d,k=%d,next[%d]=%d\n",j,k,j,k);
       	}
       	else
		{
			k=next[k];
			**//我们现在知道next[k]的值代表的是下标为k的字符前面的字符串最长相等前后缀的长度
			//也表示该处字符不匹配时应该回溯到的字符的下标
			//这个值给k后又进行while循环判断,此时t.data[k]即指最长相等前缀后一个字符**
			//为什么要回退此处进行比较,我们往下接着看。其实原理和上面介绍的KMP原理差不多
			//printf("(2) k=%d\n",k);
		}
	}
}

int KMPIndex(SqString s,SqString t)  //KMP算法
{

	int next[MaxSize],i=0,j=0;
	GetNext(t,next);
	while (i<s.length && j<t.length) 
	{
		if (j==-1 || s.data[i]==t.data[j]) 
		{
			i++;j++;  			//i,j各增1
		}
		else j=next[j]; 		//i不变,j后退,现在知道为什么这样让子串回退了吧
    }
    if (j>=t.length)
		return(i-t.length);  	//返回匹配模式串的首字符下标
    else  
		return(-1);        		//返回不匹配标志
}

在这里插入图片描述

优化代码

void GetNextval(SqString t,int nextval[])  
//由模式串t求出nextval值
{
	int j=0,k=-1;
	nextval[0]=-1;
   	while (j<t.length) 
	{
       	if (k==-1 || t.data[j]==t.data[k]) 
		{	
			j++;k++;
			if (t.data[j]!=t.data[k]) 
//这里的t.data[k]是t.data[j]处字符不匹配而会回溯到的字符
//为什么?因为没有这处if判断的话,此处代码是next[j]=k;
//next[j]不就是t.data[j]不匹配时应该回溯到的字符位置嘛
				nextval[j]=k;
           	else  
				nextval[j]=nextval[k];
//这一个代码含义是不是呼之欲出了?
//此时nextval[j]的值就是就是t.data[j]不匹配时应该回溯到的字符的nextval值
//用较为粗鄙语言表诉:即字符不匹配时回溯两层后对应的字符下标
       	}
       	else  k=nextval[k];    	
	}

}


int KMPIndex1(SqString s,SqString t)    
//修正的KMP算法
//只是next换成了nextval
{
	int nextval[MaxSize],i=0,j=0;
	GetNextval(t,nextval);
	while (i<s.length && j<t.length) 
	{
		if (j==-1 || s.data[i]==t.data[j]) 
		{	
			i++;j++;	
		}
		else j=nextval[j];
	}
	if (j>=t.length)  
		return(i-t.length);
	else
		return(-1);
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值