KMP算法的个人理解

最近看了几天KMP算法...版本颇多...

总结起来其实就两类

一类求next,再求nextval

一类直接求nextval.


简单的,先说一些概念...

一般的搜索算法 BF..

有两个指针,一个指向模式串,一个指向目标串...

然后两个指针比较:然后根据比较相等或不等,去改变两个指针的指向!

两个指针都会有回溯动作...


而KMP算法的目标串指针从来不回溯..

模式串指针根据nextval值来进行回溯..

那么回溯的值应该为多少呢?


先理解 "不为自身的最大首尾重复子串" ,该语句有个前提:指向模式串的指针的位置.

假设为j位置. 那么next[j]便表示在j之前的 "不为自身的最大首尾重复子串的长度"  . 

为什么要用这个 "不为自身的最大首尾重复子串" 呢?

这个理论证明我也不会... 但是,简单的说就是这样能很大限度的使用上前面匹配的信息.(而不用再次进行目标串指针回溯.)

这样能有更长的跨度.(省掉了中间一些必然会失败的匹配比较)



一.  简单匹配算法

先来看一个简单匹配算法的函数:

int Index_BF ( char S [ ], char T [ ], int pos ) 
{ 
/* 若串 S 中从第pos(S 的下标0≤pos<StrLength(S))个字符
起存在和串 T 相同的子串,则称匹配成功,返回第一个
这样的子串在串 S 中的下标,否则返回 -1    */
    int i = pos, j = 0; 
    while ( S[i+j] != '\0'&& T[j] != '\0') 
    if ( S[i+j] == T[j] ) 
        j ++; // 继续比较后一字符
    else 
    { 
        i ++; j = 0; // 重新开始新的一轮匹配
    }
    if ( T[j] == '\0') 
        return i; // 匹配成功   返回下标
    else 
        return -1; // 串S中(第pos个字符起)不存在和串T相同的子串
} // Index_BF

   此算法的思想是直截了当的:将主串S中某个位置i起始的子串和模式串T相比较。即从 j=0 起比较 S[i+j] 与 T[j],若相等,则在主串 S 中存在以 i 为起始位置匹配成功的可能性,继续往后比较( j逐步增1 ),直至与T串中最后一个字符相等为止,否则改从S串的下一个字符起重新开始进行下一轮的"匹配",即将串T向后滑动一位,即 i 增1,而 j 退回至0,重新开始新一轮的匹配。

例如:在串S=”abcabcabdabba”中查找T=” abcabd”(我们可以假设从下标0开始):先是比较S[0]和T[0]是否相等,然后比较S[1] 和T[1]是否相等…我们发现一直比较到S[5] 和T[5]才不等。如图:

当这样一个失配发生时,T下标必须回溯到开始,S下标回溯的长度与T相同,然后S下标增1,然后再次比较。如图:

这次立刻发生了失配,T下标又回溯到开始,S下标增1,然后再次比较。如图:

这次立刻发生了失配,T下标又回溯到开始,S下标增1,然后再次比较。如图:

又一次发生了失配,所以T下标又回溯到开始,S下标增1,然后再次比较。这次T中的所有字符都和S中相应的字符匹配了。函数返回TS中的起始下标3

如图:

. KMP匹配算法

还是相同的例子,在S=”abcabcabdabba”中查找T=”abcabd”,如果使用KMP匹配算法,当第一次搜索到S[5]T[5]不等后,S下标不是回溯到1T下标也不是回溯到开始,而是根据TT[5]==’d’的模式函数值(nextval[5]=2,为什么?后面讲),直接比较S[5]T[2]是否相等,因为相等,ST的下标同时增加;因为又相等,ST的下标又同时增加。。。最终在S中找到了T。如图:

KMP匹配算法和简单匹配算法效率比较,一个极端的例子是:

S=AAAAAA…AAB(100A)中查找T=”AAAAAAAAAB”,简单匹配算法每次都是比较到T的结尾,发现字符不同,然后T的下标回溯到开始,S的下标也要回溯相同长度后增1,继续比较。如果使用KMP匹配算法,就不必回溯.

对于一般文稿中串的匹配,简单匹配算法的时间复杂度可降为O (m+n),因此在多数的实际应用场合下被应用。

KMP算法的核心思想是利用已经得到的部分匹配信息来进行后面的匹配过程。看前面的例子。为什么T[5]==’d’的模式函数值等于2nextval[5]=2),其实这个2表示T[5]==’d’的前面有2个字符和开始的两个字符相同,且T[5]==’d’不等于开始的两个字符之后的第三个字符(T[2]=’c’.如图:

也就是说,如果开始的两个字符之后的第三个字符也为’d’,那么,尽管T[5]==’d’的前面有2个字符和开始的两个字符相同,T[5]==’d’的模式函数值也不为2,而是为0

   前面我说:在S=”abcabcabdabba”中查找T=”abcabd”,如果使用KMP匹配算法,当第一次搜索到S[5]T[5]不等后,S下标不是回溯到1T下标也不是回溯到开始,而是根据TT[5]==’d’的模式函数值,直接比较S[5]T[2]是否相等。。。为什么可以这样?

刚才我又说:“(nextval[5]=2),其实这个2表示T[5]==’d’的前面有2个字符和开始的两个字符相同”。请看图 :因为,S[4] ==T[4]S[3] ==T[3],根据next[5]=2,有T[3]==T[0]T[4] ==T[1],所以S[3]==T[0]S[4] ==T[1](两对相当于间接比较过了),因此,接下来比较S[5]T[2]是否相等。。。

有人可能会问:S[3]T[0]S[4]T[1]是根据next[5]=2间接比较相等,那S[1]T[0]S[2]T[0]之间又是怎么跳过,可以不比较呢?

因为S[0]=T[0]S[1]=T[1]S[2]=T[2],而T[0]  !=  T[1], T[1]  !=  T[2],==>  S[0]  != S[1],S[1] != S[2],所以S[1]  != T[0],S[2] != T[0]. 还是从理论上间接比较了。

有人疑问又来了,你分析的是不是特殊轻况啊。

假设S不变,在S中搜索T=abaabd”呢?

答:这种情况,当比较到S[2]T[2]时,发现不等,就去看next[2]的值,nextval[2]=-1,意思是S[2]已经和T[0]间接比较过了,不相等,接下来去比较S[3]T[0]吧。

假设S不变,在S中搜索T=abbabd”呢?

答:这种情况当比较到S[2]T[2]时,发现不等,就去看next[2]的值,nextval[2]=0,意思是S[2]已经和T[2]比较过了,不相等,接下来去比较S[2]T[0]吧。

假设S=”abaabcabdabba”S中搜索T=abaabd”呢?

答:这种情况当比较到S[5]T[5]时,发现不等,就去看next[5]的值,nextval[5]=2,意思是前面的比较过了,其中,S[5]的前面有两个字符和T的开始两个相等,接下来去比较S[5]T[2]吧。

总之,有了串的nextval值,一切搞定。那么,怎么求串的模式函数值nextval[n]呢?

回到next...我想,人们一般是先发现next值,再改进的nextval值.所以先还是看看next的求法...

void GetNext(const char* T,int next[])
{
	int j=1,k=0;
	next[0] = -1;//固定为-1.
	while(T[j]!='\0')
	{
		next[j]=k;//这个地方其实是这个算法的弊端.
	//开始计算下一个next值	
		if(T[j]==T[k])//两个字串继续延伸变长
			++k;
		else if(T[j]!=T[0])//不再变长,且与第一个字符都不匹配
			k=0;
		else  //匹配一个字符
			k=1;
		++j;//下一个next值.
	}//while
	for(int i=0;i<j;i++)//打印
		printf("%2d ",next[i]);
}//myget_nextval

目前这段条理还是很清晰的...

1111.bmp

一般T[0 ~j-1] == dest[m-j ~m-1],,,T[j] != dest[m]时::

     根据next[j]==k;  得知 T[0 ~k-1] == T[j-k-1 ~j-1] == dest[m-k-1 ~m-1].

    所以移动j-k位置...

但是这个next问题存在,既是T[j]T[k]尚未比较(实际上可以比较),,,(上面所说的弊端)

    如果T[j]==T[k], 那么T[j]!=dest[m]==>T[k]!=dest[m],,,那么这次根据next[j]移动会直接导致再一次根据next[k]的移动(如图).

        嘿嘿,需要再次移动,,这次next[k]...(这个很有递归感觉)(具体请想象第二个字串和第三个字串之间再次移动后的字串)

    如果T[j]!=T[k], 哎,只好比较一下了.- -||.


.接下来.就是改进了.改进next改名了...叫做 nextval.

通过next得出nextval算法


if( T[ next[j] ] == T[j] )
    nextval[j] = nextval[ next[j] ];
else
    nextval[j] = next[j];

综合这两过程...(还是不理解 - -||)

下面两个写法...

注意中间其实含有上面那个通过nextnextval过程...

void GetNextVal(const char* T,int next[])
{
	next[0]=-1;
	int k=-1,j=0;
	while( T[j]! = '\0' )
	{
		if( k!=-1 && T[k]!=T[j] )
			k=next[k];
		++j;++k;
		if(T[k]==T[j])
			next[j]=next[k];
		else
			next[j]=k;
	}
	for(int i=0;i<j-1;i++)
		printf("%2d ",next[i]);
}
void Get_NextVal(const char *T,int next[])
{
	int j=0,k=-1;
	next[0]=-1;
	while(T[j/*+1*/]!='\0')
	{
		if(-1==k || T[j]==T[k] )
		{
			++j;++k;
			if(T[j]!=T[k])
				next[j]=k;
			else
				next[j]=next[k];
		}//if
		else
			k=next[k];
	}//while
	for(int i=0;i<j-1;i++)
		printf("%2d ",next[i]);
}//get_nextval 

未完待续....

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值