重温KMP算法

最近面试时被问到了有关字符串查找的问题,在此回顾一下《算法导论》上介绍的KMP算法。

KMP算法是一个用来解决在一个字符串里查找特定子字符串的算法,这个算法不需要回溯,因为它会对子字符串(下面称为模式串)进行一个预处理。根据预处理得到的信息可以在进行模式串匹配时获取下一个跳转匹配位置,具体如下。

比如我们有模式串:P=abcabd,有目标串:T=aaabcabgccdddd(P,T的下标都从1开始)。

匹配首先从P[1]和T[1]开始。显然P[1]匹配T[1],但P[2]不匹配T[2]。相同的情况一致持续到P[2]和T[3]匹配。

之后P[1]~P[5]和T[3]~T[7]匹配,但P[6]和T[8]不匹配,这时一般的策略是T回溯到T[4]再与P[1]重新进行匹配。然而观察后可以看到P[1]~P[5]的后缀P[4]~P[5]恰好是P的前缀P[1]~P[2],那么我们其实可以让T[8]直接与P[3]开始匹配,因为P[1]~P[2]肯定匹配T[6]~T[7]。

这样就避免了回溯,但是我们怎么知道P应该让哪个位置的字符与T当前位置的字符进行比较呢,这就需要提前计算这个跳转位置。与这个跳转位置具有一一对应的是当前S字符串已经匹配到的位置,如匹配到P[6]发生不匹配,则跳转到P[3],用一个式子表示即为:

next[6] = 3

下面的函数计算了针对每一个不匹配位置对应的跳转位置:

(由网上抄录)

void get_next(char *t,int *next)
{
    
    int i=1,j=0;
    next[0]=next[1]=0;
    while (i<(int)strlen(t))
    {
        if (j==0||t[i]==t[j])
        {
            i++;
            j++;
            next[i]=j;
        }
        else j=next[j];
    }
    
}

而KMP算法最终描述为(同为网上抄录):

int index_KMP(char *s,char *t,int pos)
{
    int i=pos,j=1;
    while (i<=(int)strlen(s)&&j<=(int)strlen(t))
    {
        if (j==0||s[i]==t[j-1])
        {
            i++;
            j++;
        }
        else j=next[j];
    }
    if (j>(int)strlen(t))
        return i-strlen(t)+1;
    else 
        return 0;
}

这个方法返回模式串全匹配时,T的第一个匹配字符位置。如果找不到全匹配的位置,则返回0。

点赞