KMP算法

一、思路

首先回顾以下朴素的查找算法,就是模式串P与文本串S匹配,当P的第0位和S的第0位比,P的第1位和S的第1位比,当他们不同的时候,P的第0位从S的第1位开始比,就这样直到比到P的最后一位。

KMP算法的巧妙之处在于不需要回溯文本串。当P[i]与S[j]不匹配的话,那么P[0~(i-1)]和S[(j-i)~(j-1)]是匹配的。如果在P[0~(i-1)]内有相同的前缀和后缀,那么如果像朴素算法一样一位一位的移动,到最终发生匹配一定是从P的最长相同前缀后缀的最后一个字符P[k]开始比对S[j],那么我们就可以不用从P的第0位比S的第1位,而是直接从P的P[k]开始比对S[j],这样j是不动的,动的只有模式串的i。当做到这就遇到了下一个需要解决的问题,怎么找k。也就是找i的下一个位置即next[i]。

next数组的求法。这个也可以用KMP算法的思想。首先第0位连前后缀都没有,所以我们规定next[0] = -1 。我们定义k是最长相同前缀后缀的前缀最后一位,j是后缀最后一位。那么起始k = -1 ; j = 0。如果P[k]==P[j] 那么k++,j++(即相同前后缀长度都加1)则P[0:j]中最长相同前后缀中前缀的最后一位就是k,所以next[j]=k。如果不相同,k就需要往前找。和KMP算法一样,这时候模式串就变成了P[0:k],文本串就变成了P[0:j]。那模式串中下一个与文本串匹配的位置不就是next[k]嘛。所以令k=next[k]。

二、代码

#include <iostream>
#include <vector>
using namespace std;
typedef struct
{	
	char data[MaxSize];
	int length;			//串长
} SqString;
//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);        		//返回不匹配标志
}

三、next数组优化 

优化的地方就是,如果P[k]与S[j]不匹配,我们让k=next[k],P[k]如果等于P[next[k]],那么注定P[next[k]]仍然是不等于S[j]的。所以还需要找next[k]的next[next[k]]。所以我们在求next数组的时候,如果k++,j++后,那么next[j]=k,如果这时P[k]==P[j],也就代表着P[j]==P[next[j]]了,所以我们让next[j]=next[k]。就实现了往前倒了两层。


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);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值