绝想首页

KMP算法详解(转)

李钊 [深情] 2013-06-10 20:21:19 星期一 晴天 查看:165 回复:0 发消息给作者

如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段。

    
我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法。KMP算法是拿来处理字符串匹配的。换句话说,给你两个字符串,你需要回答,B串是否是A串的子串(A串是否包含B串)。比如,字符串A="I'm matrix67",字符串B="matrix",我们就说BA的子串。你可以委婉地问你的MM假如你要向你喜欢的人表白的话,我的名字是你的告白语中的子串吗?
    
解决这类问题,通常我们的方法是枚举从A串的什么位置起开始B匹配,然后验证是否匹配。假如A串长度为nB串长度为m,那么这种方法的复杂度是O (mn)的。虽然很多时候复杂度达不到mn(验证时只看头一两个字母就发现不匹配了),但我们有许多最坏情况,比如,A= "aaaaaaaaaaaaaaaaaaaaaaaaaab"B="aaaaaaaab"。我们将介绍的是一种最坏情况下O(n)的算法(这里假设 m0) 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的什么位置。

    
现在,我们还遗留了两个重要的问题:一,为什么这个程序是线性的;二,如何快速预处理P数组。
    
为什么这个程序是O(n)的?其实,主要的争议在于,while循环使得执行次数出现了不确定因素。我们将用到时间复杂度的摊还分析中的主要策略,简单地说就是通过观察某一个变量或函数值的变化来对零散的、杂的、不规则的执行次数进行累计。KMP的时间复杂度分析可谓摊还分析的典型。我们从上述程序的j 值入手。每一次执行while循环都会使j减小(但不能减成负的),而另外的改变j值的地方只有第五行。每次执行了这一行,j都只能加1;因此,整个过程中j最多加了n1。于是,j最多只有n次减小的机会(j值减小的次数当然不能超过n,因为j永远是非负整数)。这告诉我们,while循环总共最多执行了n次。按照摊还分析的说法,平摊到每次for循环中后,一次for循环的复杂度为O(1)。整个过程显然是O(n)的。这样的分析对于后面P数组预处理的过程同样有效,同样可以得到预处理过程的复杂度为O(m)
    
预处理不需要按照P的定义写成O(m^2)甚至O(m^3)的。我们可以通过P[1],P[2],...,P[j-1]的值来获得P[j]的值。对于刚才的B="ababacb",假如我们已经求出了P[1],P[2],P[3]P[4],看看我们应该怎么求出P[5]P[6]P[4]=2,那么P [5]显然等于P[4]+1,因为由P[4]可以知道B[1,2]已经和B[3,4]相等了,现在又有B[3]=B[5],所以P[5]可以由P[4] 后面加一个字符得到。P[6]也等于P[5]+1吗?显然不是,因为B[ P[5]+1 ]B[6]。那么,我们要考虑退一步了。我们考虑P[6]是否有可能由P[5]的情况所包含的子串得到,即是否P[6]=P[ P[5] ]+1。这里想不通的话可以仔细看一下:

        1 2 3 4 5 6 7
    B = a b a b a c b
    P = 0 0 1 2 3 ?

    P[5]=3
是因为B[1..3]B[3..5]都是"aba";而P[3]=1则告诉我们,B[1]B[3]B[5]都是"a"。既然P[6]不能由P[5]得到,或许可以由P[3]得到(如果B[2]恰好和B[6]相等的话,P[6]就等于P[3]+1了)。显然,P[6]也不能通过P[3]得到,因为B[2]B[6]。事实上,这样一直推到P[1]也不行,最后,我们得到,P[6]=0
    
怎么这个预处理过程跟前面的KMP主程序这么像呢?其实,KMP的预处理本身就是一个B自我匹配的过程。它的代码和上面的代码神似:

P[1]:=0;
j:=0;
for i:=2 to m do
begin
   while (j>0) and (B[j+1]B[i]) do j:=P[j];
   if B[j+1]=B[i] then j:=j+1;
   P[i]:=j;
end;

    
最后补充一点:由于KMP算法只预处理B串,因此这种算法很适合这样的问题:给定一个B串和一群不同的A串,问B是哪些A串的子串。

    
串匹配是一个很有研究价值的问题。事实上,我们还有后缀树,自动机等很多方法,这些算法都巧妙地运用了预处理,从而可以在线性的时间里解决字符串的匹配。我们以后来说。


顶一下(31 写日记 1271340 226425
分享排行

 

 

留住已经逝去的峥嵘岁月 记住曾经绽现的万种风情 在记忆即将淡漠的时候 来把这些重新回味

Copyright (C) 2008-2014 www.juexiang.com, All Rights Reserved.

京ICP备2023001011号-3   京公网安备11010802011908号

客服QQ 1017160561 违法和不良信息举报电话 13148464312 邮箱 1017160561@qq.com