KMP模式匹配算法分析

10 篇文章 0 订阅
6 篇文章 0 订阅
                <<KMP模式匹配算法分析>>
            Tags: alg,linux,devel


1. 朴素模式匹配算法(BF)

为了更好地理解某一事物, 最好的办法就是支了解它的发展史. 所以, 在介绍KMP之前,
先介绍一下最初的模式匹配算法(BF), 也称简单模式匹配算法.

BF算法思想很简单, 用模式串(P)的字符依次与目标串(T)的字符做比较,
    T       T0  T1  T2  ...  Tm-1  ...  Tn-1
    P       P0  P1  P2  ...  Pm-1

    如果, T0 = P0, T1 = P1, T2 = P2, ..., Tm-1 = Pm-1, 则匹配成功, 返回模式串
第0个字符P0在目标串中匹配的位置;
    如果在其中某个位置i: Ti!=Pi, 比较不相等, 这时可将模式串P右移一位, 用P中字
符从头与T中字符依次比较.


/*==========================================================================*
*  @Description:
*       朴素的模式匹配算法.
*       O(m*n)
*
*  @Param  t
*  @Param  p
*
*  @Returns:
*
*==========================================================================*/
int bf_find(const char *t, const char *p)
{
    assert(t != NULL && p != NULL);

    int i, j;
    int nT = strlen(t);
    int nP = strlen(p);

    for ( i = 0; i <= nT - nP; i++ )
    {
        for ( j = 0; j < nP; j++ )
            if ( t[i+j] != p [j] )
                break;
        if ( j == nP )
            return i;
    }

    return -1;
}





2. KMP算法

BF算法的时间复杂度是O (m*n), 这是因为有回溯, 但这些回溯是可以避免的, 也就是KMP
算法要做的事.

2.1 分析

设目标T=" T0 T1 ... Tn-1", 模式P=" P0 P1 ... Pm-1". 用BF算法做第s趟匹配比较时,
从目标T的第s个位置Ts与模式P的第0个位置P0开始比较, 直到在目标T第s+j位置Ts+j"
配":

    T       T0  T1  ...  Ts-1  Ts   Ts+1   Ts+2  ...   Ts+j-1   Ts+j   ...  Tn-1
                                          ||      ||         ||             ||            #
    P                                   P0   P1      P2       ...   Pj-1       Pj

这时, 应有
            Ts Ts+1 Ts+2 ... Ts+j-1 = P0 P1 P2 ... Pj-1                 (1)


按BF算法, 下一趟应从目标T的第s+1个位置起用Ts+1与模式P中的P0对齐, 重新开始匹配
比较. 若想匹配, 必须满足:
        P0 P1 P2 ... Pj-1 ... Pm-1 = Ts+1 Ts+2 Ts+3 ... Ts+j ... Ts+m   (A)

但, 如果在模式P中,
            P0 P1 ... Pj-2 != P1 P2 .. Pj-1                             (2)

则, 第s+1趟不用进行匹配比较, 就能断定它必然" 失配"; 即, (A)不成立! 因为由 (1)
(2)式可知:
        P0 P1 ... Pj-2 != Ts+1 Ts+2 ... Ts+j-1 ( = P1 P2 ... Pj-1 )


同理, 若:
            P0 P1 ... Pj-3 != P2 P3 ... Pj-1
则:
        P0 P1 ... Pj-3 != Ts+2 Ts+3 ... Ts+j-1 ( = P2 P3 ... Pj-1 )

    ... ...

依此类推, 直到对于某一值k, 使得:
        P0 P1 ... Pk    = Pj-k-1 Pj-k ... Pj-1
且      P0 P1 ... Pk+1 != Pj-k-2 Pj-k-1 ... Pj-1
才有:
        P0  P1  ...  Pk  =  Ts+j-k-1   Ts+j-k   ...   Ts+j-1
                                      ||                ||                  ||
                                      Pj-k-1       Pj-k             Pj-1


这样, 我们可以把在第s趟比较" 失配"时的模式P从当时位置直接向右" 滑动" j-k-1
( = s+j-k-1 - s ) 位.


2.2 next特征函数

关于k的确定方法, Knuth等人发现, 对于不同的j, k的取值不同, 它仅依赖于模式P本身
前j个字符的构成, 与目标无关. 因此, 可以用一个next特征函数来确定: 当模式P中第j
个字符与目标S中的相应字符失配时, 模式P中应当由哪个字符与目标中刚失配的字符重新
继续进行比较.

    设模式 P = " P0 P1 ... Pm-2 Pm-1", 由之前的分析可知, 它的next特征函数可以定
    义如下:
              /  -1,  当j=0
    next (j) = |  k+1, 当0<=k<j-1且使得" P0 P1 ... Pk" = " Pj-k-1 Pj-k ... Pj-1"的最大整数
              \  0,   其它情况


2.3 实现

2.3.1 KMP

有了next值, KMP算法也就可以实现了.


/*==========================================================================*
*  @Description:
*       KMP匹配算法.
*       O(m+n)
*
*  @Param  t
*  @Param  p
*
*  @Returns:
*==========================================================================*/
int kmp_find(const char *t, const char *p)
{
    assert(t != NULL && p != NULL);

    int nT = strlen(t);
    int nP = strlen(p);
    int posT = 0, posP = 0;
    int *next = NULL;

    if ( nP == 0 ) return 0;

    if ( NULL == (next = (int *)malloc(nP * sizeof(int))) ) return -1;
    kmp_ST_next(p, next, nP);

    while ( posP < nP && posT < nT )
    {
        if ( posP == -1 || p[posP] == t[posT] )
        {
            posP++;
            posT++;
        }
        else
        {
            posP = next[posP];
        }
    }
    if ( next != NULL ) free(next);

    if ( posP < nP ) return -1;

    return posT - posP;
}




2.3.2 Next值

static void kmp_ST_next(const char *p, int *next, int n)
{
    assert(p != NULL && next != NULL && n > 0);
    assert(strlen(p) == n);

    int j = 0, k = -1;
    next[0] = -1;

    while ( j < n - 1 )
    {
        if ( k == -1 || p[j] == p[k] )
        {
            j++; k++;
            next[j] = k;
        }
        else
        {
            /* 此刻, 有: (k!=-1) 且 (P[j]!=P[k]) 且 (next[j]==k);
             * next[j]==(k-1)+1 ==> "Pj-k Pj-k+1 ... Pj-1" == "P0 P1 ... Pk-1"
             *
             * 即: 串"P0 P1 ... Pj"的next值为k, 而P[j]!=P[k],
             *     所以, 串"P0 P1 ... Pj+1"的next值(next[j+1])只可能出现在
             *     串"P0 P1 ... Pk-1"中!!
             *
             *   由 "Pj-k Pj-k+1 ... Pj-1" == "P0 P1 ... Pk-1" 与
             *      "P0 P1 ... Pa" == "Pk-a-1 Pk-a ... Pk-1" (next[k]=a+1)
             *   得, "Pj-a-1 Pj-a ... Pj-1" == "P0 P1 ... Pa"
             *
             *   所以接下来只需要判断Pj是否等于Pa+1, 而Pa+1的下标即是next[k]!
             *   若相等, 则next[j+1]=next[k]+1; 若...
             * */
            k = next[k];
        }
    }
}

 


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值