模式匹配算法KMP

Knuth-Morris-Pratt(KMP)算法

KMP算法是一种高效的前缀匹配算法,在传统蛮力(BF)匹配算法的基础上改进的地方在于每次移动的距离不是1可以是更大,没有进行回溯,BF算法的时间复杂度是O(m*n),而KMP算法的时间复杂度是O(m+n)。

假设执行第i+1趟匹配时,如果比较模式串P中的第j个字符时不匹配,也就是有

T[i,i+1,...,i+j-1]=P[0,1,...,j-1],T[i+j]≠P[j]  (打不了下标,就有数组的形式给出字符串)   (1)

BF算法下一趟是从目标的第i+1位置开始与模式串比较。如果匹配成功则有

T[i+1,i+2,...,i+m]=P[0,1,...m-1]                               (2)

如果模式串P有如下特征

P[0,1,...j-2]=P[1,2,...j-1]                     (3)

由(1)可知

T[i+1,i+2,...,i+j+1]=P[1,2,...j-1]                               (4)

由(3)(4)可知

T[i+1,i+2,...,i+j+1]≠P[0,1,...j-2]                               (5)

故由

T[i+1,i+2,....,i+m]≠P[0,1,...m-1]

所以第i+2趟是匹配可以不需要进行,因为一定不能匹配。

类似可以推得

P[0,1,...k-1]=P[j-k-1,j-k,...j-1]

这时才有

P[0,1,...k-1]=P[j-k-1,j-k,...j-1]=T[i+j-k,i+j-k+1,i+j-1]

模式串P从当前位置直接向右移动 j-k 位置,使模式串P的第 k 个字符P[k]与目标串T中的第i+j个字符对齐开始比较(前面 k 个已经匹配)。

造成BF算法效率低的主要原因是在算法执行过程中有回溯,而这些回溯是可以避免的。KMP算法的关键是在匹配失败时,确定下一次匹配的位置,设next[j]=k,表示当模式串P中第j个字符与母串T相应字符不匹配时,模式串P中应当由第K个字符与目标串中刚不匹配的字符对齐继续进行比较。

例如,模式串P="abaabcac",其对应的next[j]如下:

i

0

1

2

3

4

5

6

7

t[i]

a

b

d

a

b

c

d

e

next[i]

-1

0

0

0

1

2

0

0

 

next数组构造

                          ╔   -1,   j=0;

            next[j]=         ║max{k| 0<k<j 且 P[0,1,...,k-1]=P[j-k,j-k+1,..j-1}

                                  ╚    0,    其他情况

next数组求解是一个递推过程,

设next[j]=k,则有

P[0,1,...k-1]=P[j-k,j-k+1,...,j-1]

            next[j]=         ╔ max{k| 0<k<j 且 P[0,1,...,k]=P[j-k,j-k+1,..j-1}

                                  ╚    0,    其他情况

如果P[k]=P[j],有 next[j+1]=next[j]+1=k+1。

如果P[k]≠P[j],有 P[0,1,...,k]≠P[j-k,j-k+1,...j],

假设next[j+1]=h+1,则有下式成立

P[0,1,...h]=P[j-h+1,j-k+1,...j]    P[h]=P[j]

又因为

P[0,1,...h-1]=P[j-h,j-k+1,...j-1]=P[k-h,k-h+1,k-1]    (next[k]=h的情况)

即此时实际只需要满足 next[k]=h(前面已经求解过)时,P[h]=P[j] 就有next[j+1]=h+1,否则(不存在这样的h)next[j+1]等于0。

由此可以得到计算next的递推公式

 

KMP算法实现

 /* *******************************************************************
    created:    2006/07/02
    filename:     KMP.cpp
    author:        李创
                 http://www.cppblog.com/converse/ 
                
                参考资料: 严蔚敏<<数据结构>>

    purpose:    KMP字符串匹配算法的演示
******************************************************************** */ 
 
#include  < stdio.h > 
#include  < stdlib.h > 
#include  < assert.h > 
#include  < string .h > 
 
 #define  MAX_LEN_OF_STR    30             //  字符串的最大长度 
 
typedef  struct  String                 //  这里需要的字符串数组,存放字符串及其长度 
 {
     char     str[MAX_LEN_OF_STR];     //  字符数组 
      int         length;                     //  字符串的实际长度 
 } String,  * PString;

 //  得到字符串的next数组 
 void  GetNextArray(PString pstr,  int  next[])
 {
    assert(NULL  !=  pstr); 
    assert(NULL  !=  next);
    assert(pstr -> length  >   0 );

     //  第一个字符的next值是-1,因为C中的数组是从0开始的 
     next[ 0 ]  =   - 1 ;
     for  ( int  i  =   0 , j  =   - 1 ; i  <  pstr -> length  -   1 ; )
     {
         //  i是主串的游标,j是模式串的游标
         //  这里的主串和模式串都是同一个字符串 
          if  ( - 1   ==  j  ||                          //  如果模式串游标已经回退到第一个字符 
             pstr -> str[i]  ==  pstr -> str[j])     //  如果匹配成功 
          {
             //  两个游标都向前走一步 
              ++ i;
             ++ j;
             //  存放当前的next值为此时模式串的游标值 
             next[i]  =  j;
        } 
         else                                  //  匹配不成功j就回退到上一个next值 
          {
            j  =  next[j];
        } 
    } 
} 
 
 //  KMP字符串模式匹配算法
 //  输入: S是主串,T是模式串,pos是S中的起始位置
 //  输出: 如果匹配成功返回起始位置,否则返回-1 
 int  KMP(PString S, PString T,  int  pos)
 {
    assert(NULL  !=  S);
    assert(NULL  !=  T);
    assert(pos  >=   0 );
    assert(pos  <  S -> length);
    
     if  (S -> length  <  T -> length)
         return   - 1 ;

    printf( " 主串\t = %s\n " , S -> str);
    printf( " 模式串\t = %s\n " , T -> str);

     int   * next  =  ( int   * )malloc(T -> length  *   sizeof ( int ));
     //  得到模式串的next数组 
     GetNextArray(T, next);

     int  i, j;
     for  (i  =  pos, j  =   0 ; i  <  S -> length  &&  j  <  T -> length; )
     {
         //  i是主串游标,j是模式串游标 
          if  ( - 1   ==  j  ||                  //  模式串游标已经回退到第一个位置 
             S -> str[i]  ==  T -> str[j])  //  当前字符匹配成功 
          {
             //  满足以上两种情况时两个游标都要向前进一步 
              ++ i;
             ++ j;
        } 
         else                          //   匹配不成功,模式串游标回退到当前字符的next值 
          {
            j  =  next[j];
        } 
    } 
 
    free(next);

     if  (j  >=  T -> length)
     {
         //  匹配成功 
          return  i  -  T -> length;
    } 
     else 
      {
         //  匹配不成功 
          return   - 1 ;
    } 
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值