KMP算法详解

来源:互联网 发布:手机双桌面软件 编辑:程序博客网 时间:2024/06/08 20:09
一种改进的字符串匹配算法,由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 时的情况。

     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 
     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:

     1 2 3 4 5 7 8 9 …… 
     A =  a b a b a b a a b a b … 
     B =        a b a b a c b 
           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]的情况:

     = 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 
          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 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 
                    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 8 9 …… 
     A = a b a b a b a b a b … 
     B =                    a b a b a c b 
                    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 dobegin       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[ ] )   { 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[]){ 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;
原创粉丝点击