@AquariusGX

QQ:651572770 加我请注明来意。 twitter: @aquariusgx

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

   一种改进的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此称之为KMP算法。此算法可以在O(n+m)的时间数量级上完成串的模式匹配操作,其基本思想是:每当匹配过程中出现字符串比较不等时,不需回溯指针,而是利用已经得到的“部分匹配”结果将模式向右“滑动”尽可能远的一段距离,继续进行比较。

   假如,A="abababaababacb",B="ababacb",我们来看看KMP是怎么工作的。我们用两个指针 i 和 j 分别表示,A[i-j+1…i] 与 B[1...j] 完全相等。也就是说,i 是不断增加的,随着 i 的增加 j 相应地变化,且 j 满足以 A[i] 结尾的长度为 j 的字符串正好匹配B串的前 j 个字符(j当然越大越好),现在需要检验 A[i+1] 和 B[j+1] 的关系。当 A[i+1]=B[j+1] 时,i 和 j 各加1;什么时候 j=m 了,我们就说 B 是 A 的子串(B串已经整完了),并且可以根据这时的i值算出匹配的位置。当A[i+1]<>B[j+1],KMP的策略是调整 j 的位置(减小j值)使得 A[i-j+1...i] 与 B[1…j] 保持匹配且新的 B[j+1] 恰好与 A[i+1] 匹配(从而使得i和j能继续增加)。我们看一看当 i=j=5 时的情况。

     i  =  1 2 3 4 5 6 7 8 9 ……
     A =  a b a b a b a a b a b …
     B =  a b a b a c b
     j  =  1 2 3 4 5 6 7

    此时,A[6]<>B[6]。这表明,此时 j 不能等于5了,我们要把 j  改成比它小的值 j'。j' 可能是多少呢?仔细想一下,我们发现,j' 必须要使得 B[1..j] 中的头j'个字母和末j'个字母完全相等(这样j变成了j'后才能继续保持i和j的性质)。这个 j' 当然要越大越好。在这里,B [1..5]="ababa",头3个字母和末3个字母都是"aba"。而当新的j为3时,A[6] 恰好和 B[4] 相等。于是,i 变成了6,而 j 则变成了4:

     i  =  1 2 3 4 5 6 7 8 9 ……
     A =  a b a b a b a a b a b …
     B =        a b a b a c b
     j  =        1 2 3 4 5 6 7

    从上面的这个例子,我们可以看到,新的 j 可以取多少与 i 无关,只与B串有关。我们完全可以预处理出这样一个数组P[j],表示当匹配到B数组的第 j 个字母而第 j+1 个字母不能匹配了时,新的j最大是多少。P[j]应该是所有满足 B[1..P[j]]=B[j-P[j]+1..j] 的最大值。
     再后来,A[7]=B[5],i和j又各增加1。这时,又出现了A[i+1]<>B[j+1]的情况:

     i  = 1 2 3 4 5 6 7 8 9 ……
     A = a b a b a b a a b a b …
     B =       a b a b a c b
     j  =       1 2 3 4 5 6 7

     由于 P[5]=3,因此新的 j=3:
     i =  1 2 3 4 5 6 7 8 9 ……
     A = a b a b a b a a b a b …
     B =            a b a b a c b
     j =             1 2 3 4 5 6 7

   这时,新的 j=3 仍然不能满足 A[i+1]=B[j+1],此时我们再次减小 j 值,将 j 再次更新为   P[3]:

     i =  1 2 3 4 5 6 7 8 9 ……
     A = a b a b a b a a b a b …
     B =                 a b a b a c b
     j  =                 1 2 3 4 5 6 7

现在,i 还是7,j 已经变成1了。而此时 A[8] 居然仍然不等于 B[j+1]。这样,j 必须减小到  P[1],即0:

     i =  1 2 3 4 5 6 7 8 9 ……
     A = a b a b a b a a b a b …
     B =                    a b a b a c b
     j  =                 0 1 2 3 4 5 6 7

     终于,A[8]=B[1],i 变为8,j 为1。事实上,有可能j到了0仍然不能满足 A[i+1]=B[j+1](比如A[8]="d"时)。因此,准确的说法是,当j=0了时,我们增加i值但忽略 j 直到出现A[i]=B[1] 为止。
     这个过程的代码很短,我们在这里给出:

j:=0;
for i:=1 to n do
begin
       while (j>0) and (B[j+1]<>A[i]) do j:=P[j];
       if B[j+1]=A[i] then j:=j+1;
       if j=m then
       begin
          writeln('Pattern occurs with shift ',i-m);
          j:=P[j];
       end;
end;
 

    最后的j:=P[j]是为了让程序继续做下去,因为我们有可能找到多处匹配。
    这个程序或许比想像中的要简单,因为对于 i 值的不断增加,代码用的是for循环。因此,这个代码可以这样形象地理解:扫描字符串A,并更新可以匹配到B的什么位置。

 

附:

在KMP算法中,依据模式串的next函数值实现字串的滑动,若令next[j]=k;则next[j]表示当模式串中的Pj与主串中相应字符不相等时,令模式串的Pk与主串的相应字符进行比较。

求模式串的next函数:

void Get_next ( char *p, int next[ ] )   /*求模式串p的next函数值并存入数组next*/
{
	int i = 0,  j = -1, slen;
	slen = strlen(p);
	next[0] = -1;
	while ( i < slen )
 	{
		if (  j = -1 || p[i] == p[j] )	{	++i; ++j; next[i] = j;	}
		else	j = next[j];
	}
}
KMP模式匹配算法,设模式串第一个字符的下标为0:
int Index_KMP(char *s, char *p, int pos, in next[])
/*利用模式串p的next函数和,求p在主串中从第pos个字符开始的位置*/
/*若匹配成功,则返回模式串在主串中的位置(下标),否则返回-1*/
{
	int i = pos-1, j = -1, slen = strlen(s), plen = strlen(p);
	while (i < slen && i < plen)
	{
		if(j = -1 || s[i] == p[j])		{  ++i; ++j;	}
		else	j = next[j];
	}
	if(j > plen)		return i-plen;
	else		return -1;
}
 

正文内容转自:matrix67.com

参考:http://hi.baidu.com/jzyznoi/blog/item/5080fcd3beae19dea9ec9ab9.html

posted on 2010-03-24 02:04  aquariusgx  阅读(573)  评论(0编辑  收藏  举报