字符串算法 | 图解KMP

244 阅读2分钟

考虑一个字符串string = 'baabaccbaabaabac'

当前下标i = 11,从上图可以看到,i之前的字符串string[:i] = 'baabaccbaab'最长公共前后缀'baab',长度为4,我们用一个名为next的数组记录该长度,next[i-1] = 4

可以看到上图中下标j = 4与下标i = 11处字符相等,我们可以由此推断出字符串string[:i+1] = 'baabaccbaaba'最长公共前后缀的长度值为5。

int j = next[i-1];
if(s[i] == s[j]) {
    next[i] = next[i-1] + 1;
}

那如果i和j处字符不等呢?

此时当前下标来到i = 12的位置,此时i和j处字符不等,上面的代码自然也就不成立了。

想象让j处于i的身份...

既然next[i]不能由next[i-1]直接得到,那么不如试着从next[j-1]下手。

通过上图不难看出next[j-1]string[:j] = 'baaba'最长公共前后缀的长度为2,且下标k = 2与下标i = 12处字符相等,我们可以由此推断出字符串string[:i+1] = 'baabaccbaaba'最长公共前后缀的长度值为3。

int j = next[i-1];
int k = next[j-1];
if(s[i] == s[j]) {
    next[i] = next[i-1] + 1;
} elif(s[i] == s[k]) {
    next[i] = next[j-1] + 1;
}

按照这种方法,我们可以求出整个next数组的值,next[i]的值表示从0开始以i结尾的字符串的最长公共前后缀的长度。

给出代码如下。

void GetNext(char* str) {
    next[0] = 0;
    next[1] = str[0] == str[1] ? 1 : 0;

    for(int i = 2; i < strlen(str); ++i) {
        int j = next[i-1];
        while(1) {
            if(str[j] == str[i]) {
                next[i] = j + 1;
                break;
            } else {
                if(j > 0) {
                    j = next[j-1];
                } else {
                    next[i] = 0;
                    break;
                }
            }
        }
    }
}

那我们搞来这个next数组是为了啥呢?

我们在一个长串中寻找string = 'baabaccbaabaabac',下图中绿色表示匹配成功,红色表示匹配失败,蓝色表示尚未匹配。

考虑匹配中的一个中间状态如下图。

在字符'a'的位置上匹配失败,我们并没有让短串向右错一个位置继续从头匹配,而是从最长公共前后缀的下一位开始匹配。这就是kmp较暴力匹配来的更快的地方。

在实际写代码的时候,我们需要对next数组稍作修改。

// GetNext()补充
for(int i = strlen(s) - 1; i > 0; --i) {
	next[i] = next[i-1];
}
next[0] = -1;

于是我们得到如下图所示的新next数组。

给出代码如下。

int kmp(char* str1, char* str2) {
    int i = 0, j = 0;
    while(i < strlen(str1) && j < strlen(str2)) {
        if(str1[i] == str2[j]) {
            ++i, ++j;
        } else {
            if(next[j] == -1) {
                ++i;
            } else {
                j = next[j];
            }
        }
    }
    if(j == strlen(str2)) {
        return i - strlen(str2);
    } else {
        return -1;
    }
}

你可以在leetcode 28.实现 strStr()尝试提交,通过了记得点个赞哦。