[暂定]KMP总结

算是正式开始学习的第一个算法,遇到了诸多困难,花了许多时间逐一解决,故整理思路留此文以便后来翻阅。

第一部分 字符串匹配

1朴素的模式匹配BF算法与KMP算法的时间复杂度比较

    KMP算法是一种线性时间复杂的字符串匹配算法,它是对BF算法(Brute-Force,最基本的字符串匹配算法的)改进。对于给的原始串S和模式串P,需要从字符串S中找到字符串P出现的位置的索引。

BF算法的时间复杂度O(strlen(S) * strlen(T)),空间复杂度O(1)

KMP算法的时间复杂度O(strlen(S) + strlen(T)),空间复杂度O(strlen(T))

2BF算法与KMP算法的区别

    假设现在S串匹配到i位置,T串匹配到j位置。那么总的来说,两种算法的主要区别在于失配的情况下,对j的值做的处理

   BF算法中,如果当前字符匹配成功,即s[i+j] == T[j],令j++,继续匹配下一个字符;如果失配,即S[i + j] != T[j]需要让i++,并且j= 0,即每次匹配失败的情况下,模式串T相对于原始串S向右移动了一位。

代码实现:

int Index(char s[], char p[], int pos)  
{    
	int i, j, slen, plen;    
	i = pos;    
	j = 0;    
	slen = strlen(s);    
	plen = strlen(p);    
	while (i<slen&&j<plen)    
	{    
		if((s[i]==p[j]))    
		{    
			i++;    
			j++;    
		}    
		else    
		{    
			i = i-j+1;    
			j = 0; 
		}    
	}    
	if(j>=plen)    
		return i-plen+1;
	else 
		return -1;       
}    




 而KMP算法中,如果当前字符匹配成功,即S[i]==T[j],令i++j++,继续匹配下一个字符;

如果匹配失败,即S[i] != T[j],需要保持i不变,并且让j = next[j],这里next[j] <=j -1,即模式串T相对于原始串S向右移动了至少1(移动的实际位数j - next[j]  >=1),    如果下次匹配是基于T向右移动一位,那么i之前的部分(即S[i-j+1 ~ i-1]),和j=next[j]之前的部分(即T[0 ~ j-2])仍然相等。

显然,相对于BF算法来说,KMP移动更多的位数,起到了一个加速的作用! (失配的特殊情形,令j=next[j]导致j==0的时候,需要将i ++,否则此时没有移动模式串)

二 KMP

1KMP 算法思想

KMP算法的本质便是:每一次匹配都是基于前一次匹配的结果,如何更好地利用这前一次匹配的结果呢?针对待匹配的模式串的特点,判断它是否有重复的字符,从而找到它的前缀与后缀,进而求出相应的Next数组,最终根据Next数组而进行KMP匹配。

 KMP算法借助于一个辅助数组next来确定当匹配过程中出现不等时,模式P右移的位置和开始比较的位置。next[i]的取值只与模式P本身的前i+1项有关,而与目标T无关。匹配过程中遇到Pi不等于Tj时,若next[i]>=0,则应将P右移i-next[i]位个字符,用P中的第next[i]个字符与Tj 进行比较;若:next[i]= -1,P中的任何字符都不必再与Tj比较,而应将P右移i+1个字符,从P0和Tj+1从新开始下一轮比较(可能不太好理解,自己找个例子,对着话一句一句试试看)

这种想法基于如下事实的:p[j]!=s[i]前,p[0]~p[j-1]跟s[i-j]~s[i-1]是匹配的(这里j>0,也就是说在不匹配前已经有匹配的字符了。否则如果j=0,则主串指针肯定不用回溯,直接向前变成i+1再跟p[0]比较就是了)  
   
  p[j]!=s[i]前,p[0]~p[j-1]跟s[i-j]~s[i-1]是匹配的,这说明了什么呢?这说明可以通过分析模式的p[0]~p[j-1]来分析s[i-j]~s[i-1]。

如果模式中存在p[0]~p[k-1]=p[j-k]~p[j-1](共k个匹配的字符,且k是满足这个关系的最大值),则可以知道s[i-k]~s[j-1]跟[0]~p[k-1]是匹配的,那么,s[i]只需要跟p[k]进行比较就行了。而这个k是跟主串无关的,只需要分析模式串就可以求出来(这就是普通的教材中next[j]=k这个假设的由来,普通教材中总喜欢假设这个k值已经有了,如果你逻辑思维强还没有什么,不然或多或少会把你卡在这的)。亦即next[j]=k。  
   
  如果上述的p[0]~p[k-1]=p[j-k]~p[j-1]串不存在会怎么样呢?这说明p[j]前的串中不存在p[0]...=...p[j-1]的情况,就连p[0]也不等于p[j-1],也就是说p[0]~p[j-1]中所有以p[j-1]为结尾的子串跟模式p都是失配的。基于上面p[0]~p[j-1]=s[i-j]~s[i-1]的事实,可以断定s[i-j]~s[i-1]中所有以s[i-1]结尾的子串跟模式p都是失配,这说明把主串的指针回溯到i-j+1~i-1都是没有必要的,既然没有必要回溯,而s[i]!=p[j],则s[i只能跟p[0]进行比较匹配了。亦即next[j]=0。  
   
  特殊情况下,j=0,即s[i]!=p[0],这时不用再用s[i]来跟p中的其它字符比较了,变成用s[i+1]跟p[0]进行比较。为了统一,可以让next[0]=-1。在下一轮的比较中,判断到j=-1的情况时,让i=i+1,j=j+1,自然就形成s[i+1]跟p[0]比较的效果了。  
   
  现在回过头来看教材上的next[j]的定义  
   
                              |-   -1,         当   j   =   0时  
          next[j]   =   |-   max{k   |   0   <   k   <   j   且   'p[0]...p[k-1]'   =   'p[j-k+1]...p[j-1]'   }  
                              |-   0,           其它情况  
                               
  是不是很容易理解呢?  
   
  时候再来看KMP的算法实现  

int Index(char s[], char p[], int pos, int next[])  
{  
	int i, j, slen, plen;  
	i = pos;  
	j = 0;  
	slen = strlen(s);  
	plen = strlen(p);  
	while (i<slen&&j<plen)  
	{  
		if((j==-1)||(s[i]==p[j]))  
		{  
			i++;  
			j++;  
		}  
		else   
			j=next[j];   
	}  
	if(j>=plen)  
		return(i-plen+1);  
	else  
		return-1;  
}  


   根据上面的推导,知道next[0]=-1。  
  如果已经知道next[j]=k,那么说明p[0]~p[k-1]=p[j-k]~p[j-1],现在next[j+1]应该是多少呢?  
   
  若p[k]=p[j],说明p[0]~p[k]=p[j-k]~p[j]成立,则有next[j+1]=k+1。  
   
  若p[k]!=p[j],即p[0]~p[k-1]!=p[j-k]~p[j-1],从KMP算法的角度来看,这时应p[j]应该跟p[next[k]]进行比较。也就是k=next[k],如此重复,由于next[k]<k,故最终必然是k=-1或p[k]=p[j]。如果是k=-1,则next[j+1]=0,否则next[j+1]=k+1,可以统一为next[j+1]=k+1。  
   
  下面就是GetNext函数的实现:  

void GetNext(char p[],int next[])    
{    
	int   i,   j,   slen;    
	slen = strlen(p);    
	i = 0;    
	j = -1;    
	next[0] = -1;    
	while (i<slen)    
	{    
		if((j==-1)||(p[i]==p[j]))    
		{    
			i++;    
			j++;    
			next[i] = j;    
		}    
		else    
		{    
			j = next[j];    
		}    
	}    
}    

 

参考文章

KMP算法

KMP next(j) (伟大的转折点)

经典算法研究系列:六、教你初步了解KMP算法

六之续、由KMP算法谈到BM算法

六之再续:KMP算法之总结篇



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值