理解KMP算法

 KMP算法用于字符串模式匹配,目标串T=[T1.....Tn],模式串P=[P1....Pm],这里n>=mi代表T的索引指针,j代表P的索引指针,传统字符串匹配算法,在Ti!=Pj的时候,i指针需要回退到i-j的位置,同时j回退到0,也就是模式串P开始的位置,这样传统算法的匹配过程的复杂度就是O(m*n)。其实在Ti!=Pj的时候,i指针不需要回退,[Ti-j+1...Ti-1][P1....Pj-1]是相等的,可能只需要让j回退到中间的某个位置k,使得T[1....k]=T[j-k,j-1]。这个过程相当于,把已经匹配的P[1....j-1],找出最长的相等的前缀和后缀。

                   

     从上图中可以看出,模式串P,目标串T,在i=9j=5的位置,字符不匹配,这时传统的做法是将i回退到这轮匹配开始的字符i=5的下一个字符,i=6,同时将j回退到0的位置。但是我们发现已经匹配的T[5....8]=P[0....3],把相等的部分记成字符串集合J[0....3],可行的一种做法就是:把T模式串的i位置之前的一部分看成是刚才J串的后缀,把P串开始的一部分看做是J串的前缀,那么如果这个前缀和后缀相等的话,那么j指针回退到这个J串的最后的位置即可,因为P前缀已经保证了T后缀相等,i指针不用动,j指针移动到P模式串前缀的后一个位置即可,在尝试T[i]P[j]的匹配。总之,在字符串匹配过程中,模式串PT已经匹配的部分,通过这个PT共有的部分,可以推测出,T的每个位置,后缀串和前缀串相等的最大长度。

    KMP算法的精髓就是记录模式串中前缀最后位置的next数组,我的理解是next[i]i表示字符串后缀最后的位置,前缀开始的位置是0k表示后缀开始的位置,P[0......i-k] =P[k.....i],这个next[i]就等于i-k



     如上图,模式串P=''abcaabc''next[6]表示以c为结尾的P串的后缀,以a开始的前缀,是的这个前缀和后缀相等,那么c的位置相对于前缀的位置,这个前缀中的位置就是next[6]的值。

代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void get_next( char * s, int * next) //s表示模式串,next表示next数组的头指针
{
     int length= strlen (s);
     int i=1,j=0;
                                //i表示模式串的每一个元素的指针,每次加1递增,j表示前缀串中,每一个元素的位置。
     next[0]=-1;
     for (;i<length;i++){        //开始递归模式串中每一个元素
         if (s[i]==s[j]){        //如果当前元素和前缀的第一个元素相等,表示开始匹配前缀和后缀串,j++表示前缀的下一个位置的元素。
             next[i]=j;         //同时把当前next[i]设置成为前缀中的位置。
             j++;
         } else {                 //当前位置和前缀串中的不一致,
             if (s[i]==s[0]){    //如果当前位置和前缀串中的第一个元素相同,那么表示又重新开始匹配新一轮后缀
                 next[i]=0;     //重新开始匹配后缀,所以next的值是0,表示前缀的第一个元素的位置
                 j=1;           //前缀的指针变成下一个位置
             }
             else {              //如果当前位置和第一个位置的元素也不相同,表示没有这样的以这个元素为前缀的字符串,把这个next值变成-1.
                 next[i]=-1;
                 j=0;
             }
         }
     }
}

字符串匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    //target是目标串,s是模式串,data是next数组
void find( char *target, char *s, int * data){
   int i=0;              //i是目标串的指针
   int j=0;              //j是模式串的指针
   for (;i< strlen (s);){
      if (s[i]==target[j]){ //如果模式串和目标串匹配
         if (j== strlen (target)-1){ //如果完全匹配,就说明在目标串中找到了模式串
            printf ( "start:%d\n" ,i-j+1);
            break ;
         } else {
            j++; //否则继续比较下一个字符
            i++;
         }
      } else {    //如果这两个字符不匹配
        if (j==0||data[j-1]==-1){    //如果是和模式串的第一个字符做匹配失败,或者next数组的前一个值是-1,表示前一个位置的后缀,在当前模式串中找不到前缀,所以这时,i指针需要向前移动,
                 j=0;
                 i++;
             } else {               //表示匹配前缀的后一个位置的元素。
                 j=data[j-1]+1;
             }
      }
   }
}

     说明,这里next数组中每个元素的含义是,这个元素在前缀字符串中的位置,所以在当前位置j不匹配的时候,应该首先取得匹配的字符串中前缀的最后的位置,然后新的j的值就应该是j+1的位置,j之前的元素就是原来匹配字符串的前缀。如图2中所示,如果在j=6的位置元素匹配失败,那么j=6元素之前的字符串中,b元素的位置是5,应该对应到前缀中j=1的位置,那么j的位置应该是j+1=2,再和目标串去匹配。

验证程序:


总结:相对于朴素的字符串匹配算法,复杂度为O(m*n),KMP算法中目标串的指针不用回缩,复杂度为O(m+n),大大减小了模式串搜索的复杂度。



参考文章:

1.http://blog.csdn.net/v_july_v/article/details/7041827


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值