KMP算法

来源:互联网 发布:淘宝优惠券赚佣金软件 编辑:程序博客网 时间:2024/05/21 22:52

//KMP算法#include<stdio.h>#include<string.h>void getNext(char *p,int *next){    int j,k;    next[0]=-1;    j=0;    k=-1;    while(j<strlen(p)-1)    {        if(k==-1||p[j]==p[k])    //匹配的情况下,p[j]==p[k]        {            j++;            k++;            next[j]=k;        }        else                   //p[j]!=p[k]            k=next[k];    }}int KMPMatch(char *s,char *p){    int next[100];    int i,j;    i=0;    j=0;    getNext(p,next);    while(i<strlen(s))    {        if(j==-1||s[i]==p[j])        {            i++;            j++;        }        else        {            j=next[j];       //消除了指针i的回溯        }        if(j==strlen(p))            return i-strlen(p);    }    return -1;}int main(){char *s = "ababcababa";char *p = "ababa";int ret = KMPMatch(s, p);printf("%d\n",ret);return 0;}




转载:http://www.cnblogs.com/dolphin0520/archive/2011/08/24/2151846.html

引用:http://blog.csdn.net/yaochunnian/article/details/7059486

在介绍KMP算法之前,先介绍一下BF算法。

一.BF算法

    BF算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串P的第一个字符进行匹配,若相等,则继续比较S的第二个字符和P的第二个字符;若不相等,则比较S的第二个字符和P的第一个字符,依次比较下去,直到得出最后的匹配结果。

    举例说明:

    S:  ababcababa

    P:  ababa

  BF算法匹配的步骤如下

           i=0                                   i=1                             i=2                         i=3                          i=4

  第一趟:ababcababa         第二趟:ababcababa      第三趟:ababcababa    第四趟:ababcababa    第五趟:ababcababa

             ababa                            ababa                          ababa                        ababa                       ababa

            j=0                                   j=1                            j=2                         j=3                         j=4(i和j回溯)

 

              i=1                                 i=2                           i=3                            i=4                        i=3

 第六趟:ababcababa         第七趟:ababcababa       第八趟:ababcababa     第九趟:ababcababa   第十趟:ababcababa

              ababa                              ababa                           ababa                        ababa                        ababa

             j=0                                  j=0                           j=1                           j=2(i和j回溯)            j=0

 

              i=4                                    i=5                          i=6                           i=7                          i=8

第十一趟:ababcababa       第十二趟:ababcababa    第十三趟:ababcababa   第十四趟:ababcababa   第十五趟:ababcababa

                     ababa                               ababa                           ababa                          ababa                          ababa

               j=0                                    j=0                         j=1                            j=2                         j=3

 

                    i=9

第十六趟:ababcababa

                       ababa

                    j=4(匹配成功)

代码实现:

复制代码
int BFMatch(char *s,char *p){    int i,j;    i=0;    while(i<strlen(s))    {        j=0;        while(s[i]==p[j]&&j<strlen(p))        {            i++;            j++;        }        if(j==strlen(p))            return i-strlen(p);        i=i-j+1;                //指针i回溯    }    return -1;    }
复制代码
   其实在上面的匹配过程中,有很多比较是多余的。在第五趟匹配失败的时候,在第六趟,i可以保持不变,j值为2。因为在前面匹配的过程中,对于串S,已知s0s1s2s3=p0p1p2p3,又因为p0!=p1!,所以第六趟的匹配是多余的。又由于p0==p2,p1==p3,所以第七趟和第八趟的匹配也是多余的。在KMP算法中就省略了这些多余的匹配。

二.KMP算法

    KMP算法之所以叫做KMP算法是因为这个算法是由三个人共同提出来的,就取三个人名字的首字母作为该算法的名字。其实KMP算法与BF算法的区别就在于KMP算法巧妙的消除了指针i的回溯问题,只需确定下次匹配j的位置即可,使得问题的复杂度由O(m*n)下降到O(m+n)。

  在KMP算法中,为了确定在匹配不成功时,下次匹配时j的位置,引入了next[]数组,next[j]的值表示P[0...j-1]中最长后缀的长度等于相同字符序列的前缀。

***************************

next数组的含义

重点来了。下面解释一下next数组的含义,这个也是KMP算法中比较不好理解的一点。

  令原始串为: S[i],其中0<=i<=n;模式串为: T[j],其中0<=j<=m

  假设目前匹配到如下位置

               S0,S1,S2,...,Si-j,Si-j+1...............,Si-1Si, Si+1,....,Sn

                                   T0,T1,.....................,Tj-1Tj, ..........

  ST的绿色部分匹配成功,恰好到SiTj的时候失配,如果要保持i不变,同时达到让模式串T相对于原始串S右移的话,可以更新j的值,让Si和新的Tj进行匹配,假设新的jnext[j]表示,即让Sinext[j]匹配,显然新的j值要小于之前的j值,模式串才会是右移的效果,也就是说应该有next[j] <= j -1。那新的j值也就是next[j]应该是多少呢?我们观察如下的匹配:

      1)如果模式串右移1位(从简单的思考起,移动一位会怎么样),即next[j] = j - 1, 即让蓝色的SiTj-1匹配(注:省略号为未匹配部分)

               S0,S1,S2,...,Si-j,Si-j+1...............,Si-1Si, Si+1,....,Sn

                                   T0,T1,.....................,Tj-1Tj, .......... (T的划线部分和S划线部分相等【1】)

                                        T0,T1,.................Tj-2,Tj-1, ....... (移动后的T的划线部分和S的划线部分相等【2】)

        根据【1】【2】可以知道当next[j] =j -1,即模式串右移一位的时候,有T[0 ~ j-2] == T[1 ~ j-1],而这两部分恰好是字符串T[0 ~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串Tj前面部分的前缀和后缀相等部分的长度(好好揣摩这两个关键字概念:前缀、后缀,或者再想想,我的上一篇文章,从Trie树谈到后缀树中,后缀树的概念)。

      2)如果模式串右移2位,即next[j] = j - 2, 即让蓝色的SiTj-2匹配    

               S0,S1,...,Si-j,Si-j+1,Si-j+2...............,Si-1Si, Si+1,....,Sn

                                   T0,T1,T2,.....................,Tj-1Tj, ..........(T的划线部分和S划线部分相等【3】)

                                              T0,T1,...............,Tj-3,Tj-2,.........(移动后的T的划线部分和S的划线部分相等【4】)

        同样根据【3】【4】可以知道当next[j] =j -2,即模式串右移两位的时候,有T[0 ~ j-3] == T[2 ~ j-1]。而这两部分也恰好是字符串T[0 ~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串Tj前面部分的前缀和后缀相等部分的长度

     3)依次类推,可以得到如下结论:当发生失配的情况下,j的新值next[j]取决于模式串中T[0 ~ j-1]中前缀和后缀相等部分的长度, 并且next[j]恰好等于这个最大长度

    为此,请再允许我引用上文中的一段原文: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),

    同时移动之后,i之前的部分(即S[i-j+1 ~ i-1]),和j=next[j]之前的部分(即T[0 ~ j-2])仍然相等。显然,相对于BF算法来说,KMP移动更多的位数,起到了一个加速的作用! (失配的特殊情形,令j=next[j]导致j==0的时候,需要将i ++,否则此时没有移动模式串)。”

    于此,也就不难理解了我的关于KMP算法的第二篇文章之中:当匹配到S[i] != P[j]的时候有 S[i-j…i-1] = P[0…j-1]. 如果下面用j_next去匹配,则有P[0…j_next-1] = S[i-j_next…i-1] = P[j-j_next…j-1]。此过程如下图3-1所示。

  当匹配到S[i] != P[j]时,S[i-j…i-1] = P[0…j-1]

S: 0 … i-j … i-1 i …

P:       0 …   j-1 j …

  如果下面用j_next去匹配,则有P[0…j_next-1] = S[i-j_next…i-1] = P[j-j_next…j-1]。
所以在P中有如下匹配关系(获得这个匹配关系的意义是用来求next数组)

P: 0 … j-j_next  .…j-1_    …

P:        0    … .j_next-1 …

  所以,根据上面两个步骤,推出下一匹配位置j_next:

S: 0 … i-j … i-j_next …   i-1      i …

P:                   0   … j_next-1 j_next …

             图3-1 求j-next(最大的值)的三个步骤

    下面,我们用变量k来代表求得的j_next的最大值,即k表示这S[i]、P[j]不匹配时P中下一个用来匹配的位置,使得P[0…k-1] = P[j-k…j-1],而我们要尽量找到这个k的最大值。”。

      根据上文的【1】与【2】的匹配情况,可得第二篇文章之中所谓的k=1(如aaaa的形式),根据上文的【3】与【4】的匹配情况,k=2(如abab的形式)。

     所以,归根究底,KMP算法的本质便是:针对待匹配的模式串的特点,判断它是否有重复的字符,从而找到它的前缀与后缀,进而求出相应的Next数组,最终根据Next数组而进行KMP匹配。


***************************

  对于next[]数组的定义如下:

 1) next[j] = -1  j = 0

 2) next[j] = max(k): 0<k<j   P[0...k-1]=P[j-k,j-1]

 3) next[j] = 0  其他

 如:

 P      a    b   a    b   a

 j      0    1   2    3   4

 next    -1   0   0    1   2

 即next[j]=k>0时,表示P[0...k-1]=P[j-k,j-1]

 因此KMP算法的思想就是:在匹配过程称,若发生不匹配的情况,如果next[j]>=0,则目标串的指针i不变,将模式串的指针j移动到next[j]的位置继续进行匹配;若next[j]=-1,则将i右移1位,并将j置0,继续进行比较。

代码实现如下:

复制代码
int KMPMatch(char *s,char *p){    int next[100];    int i,j;    i=0;    j=0;    getNext(p,next);    while(i<strlen(s))    {        if(j==-1||s[i]==p[j])        {            i++;            j++;        }        else        {            j=next[j];       //消除了指针i的回溯        }        if(j==strlen(p))            return i-strlen(p);    }    return -1;}
复制代码

  因此KMP算法的关键在于求算next[]数组的值,即求算模式串每个位置处的最长后缀与前缀相同的长度, 而求算next[]数组的值有两种思路,第一种思路是用递推的思想去求算,还有一种就是直接去求解。 

1.按照递推的思想:

   根据定义next[0]=-1,假设next[j]=k, 即P[0...k-1]==P[j-k,j-1]

   1)若P[j]==P[k],则有P[0..k]==P[j-k,j],很显然,next[j+1]=next[j]+1=k+1;

   2)若P[j]!=P[k],则可以把其看做模式匹配的问题,即匹配失败的时候,k值如何移动,显然k=next[k]。

(我注:可以看到这种方式的代码和KMPMatch的代码很相似,其实本质就是在求next数组的时候,将P字符串自己当成模式匹配的问题。即P自己当成S匹配P的方式。这里很赞!)

   因此可以这样去实现:

复制代码
void getNext(char *p,int *next){    int j,k;    next[0]=-1;    j=0;    k=-1;    while(j<strlen(p)-1)    {        if(k==-1||p[j]==p[k])    //匹配的情况下,p[j]==p[k]        {            j++;            k++;            next[j]=k;        }        else                   //p[j]!=p[k]            k=next[k];    }}
复制代码
 
   2.直接求解方法
复制代码
void getNext(char *p,int *next){    int i,j,temp;    for(i=0;i<strlen(p);i++)    {        if(i==0)        {            next[i]=-1;     //next[0]=-1        }        else if(i==1)         {            next[i]=0;      //next[1]=0        }        else        {            temp=i-1;            for(j=temp;j>0;j--)            {                if(equals(p,i,j))                {                    next[i]=j;   //找到最大的k值                    break;                }            }            if(j==0)                next[i]=0;        }    }}bool equals(char *p,int i,int j)     //判断p[0...j-1]与p[i-j...i-1]是否相等  {    int k=0;    int s=i-j;    for(;k<=j-1&&s<=i-1;k++,s++)    {        if(p[k]!=p[s])            return false;    }    return true;}
0 0