kmp算法及简单应用

kmp算法是模式匹配中的经典算法,在处理很多和字符串的问题时都会用到。不过这个算法虽然实现起来很简单,思路想要描述清楚却不容易,网上有很多资料,但感觉都讲的不太清楚,有些代码都是错误的。为了验证自己已经弄清楚了,决定从头开始介绍这个算法。

假定T为待匹配的文本,P为模式。假定T为ababaabcbab,P为ababaca。我们先从T的第一个字符开始匹配起,粗体表示匹配失败的字符

T a b a b a a b c b a b
P a b a b a c

如果按照朴素的匹配算法,那我们下次应该就从第二个字符开始匹配。那有没有更快一些的方法呢?观察一下P,前两个字符分别和第三、第四个字符相等,也就是说如果我们下次可以从T的第1+2,也就是第三个字符开始匹配起。也就是说假定P[1...q] = T[s + 1...s+q],那么满足P[1...k]=T[ss + 1... ss + k] 的ss最小是多少,其中ss + k = s + q。

进一步地解释,假定匹配在T的第t位和P的第p位失效了,那么我们接下来要继续比较T的第t位和P的(p-x)位,相当于把P往右移了x位。那么如果我们能知道p-x的值,整个匹配过程就很好理解了,t每次增大1,当匹配失效时置p为p-x,继续匹配。

如何知道p-x的值呢?我们先考察一下它的意义,匹配失败并重置p为p-x后我们可以继续比较T的第t位和P的p-x位,这说明在这位之前,t的长为p-x-1的左串等于P的长为p-x-1的前缀。事实上这就是p-x的定义。对于所有的p,记其p-x的值为向量next。那么给定P,对于其中的第i位来说,如果P[i-k+1...i]和P[1...k]相等,且k < i,则next[i] = k。

求next向量的过程可表示如下,先初始化next[0]为0,假设我们已经知道next[i-1] = k,求next[i]。
1. 如果P[k + 1] = P[i],则next[i] = k + 1
2. 如果P[k + 1] != P[i] 并且 k > 0,则令k = next[k] 直到P[k + 1] = P[i]或 k = 0
3. 第2步得到了新的k,如果P[k + 1] = P[i],那么next[i] = k + 1,否则为k。

完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#define MAX 10000

void
gen_next(char *p, int *next)
{
    int len = strlen(p);

    *next = 0;
    int k = 0;
    int i = 1;

    for (i = 1; i < len; i ++) {
        while (k > 0 && *(p + k) != *(p + i))
            k = *(next + k);
        if (*(p + k) == *(p + i))
            k ++;
        *(next + i) = k;
    }

    // We can set next[0] to -1 to make the code simpler
    // *next = -1;
}

int
find(char *p, char *q)
{
    assert(p && q);

    int *next, lenq = strlen(q), lenp = strlen(p), count = 0;
    next = (int *)malloc(sizeof (int) * lenq);
    if (next == NULL)
        return -1;
    memset(next, 0, sizeof (int) * lenq);

    gen_next(q, next);

    int pi, qi;
    pi = qi = 0;
    while (pi < lenp && qi < lenq) {
        // If next[0] == -1, the while loop can be writen is this way
        //
        // if ((*(p + pi) == *(q + qi) || qi == -1) && pi < lenp && qi < lenq)
        //     pi ++, qi ++;
        // else
        //     qi = *(next + qi);
        while (*(p + pi) == *(q + qi) && pi < lenp && qi < lenq)
            pi ++, qi ++;
        if (qi == lenq) {
            printf("found one match, begins at %d\n", pi - lenq);
            count ++;
            qi = *(next + qi - 1);
            continue;
        }
        if (qi == 0) {
            pi ++;
        }  else {
            qi = *(next + qi);
        }
    }

    return count;
}

int
main(int argc, char **argv)
{
    char s[MAX] = "ababab";
    char p[MAX] = "abab";

    printf("total %d match\n", find(s, p));

    return 0;
}

发表评论


注意 - 你可以用以下 HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>