KMP 算法

2023/03/08

Last updated on 2024/11/24

Table of Contents

如何在字符串 S 中查找模式串 P 是否存在?

一种简单的想法是进行蛮力匹配。使用两层循环查找模式串是否存在:外层循环移动模式串的位置,内层循环对字符串和模式串的对应字符进行比较。

相较于蛮力匹配,使用 KMP 算法可以提高匹配效率。

1 算法要点

推荐 KMP 算法讲解视频:最浅显易懂的 KMP 算法讲解

KMP 算法的关键在于构建 next 数组,需要注意:

2 实现代码

2.1 搜索第一个匹配项

搜索模式串 P 在字符串 S 中是否出现。如果出现,返回第一个匹配项的下标;否则,返回 -1。

int *build_next(char *P) {
    int m = strlen(P); // 模式串 P 的长度
    int *next = new int[m];
    next[0] = -1;
    int j = 0; // 主指针
    int t = -1; // 当前最长匹配前缀的结尾下一处指针

    while (j < m - 1) {
        if (t < 0 || P[j] == P[t]) {
            ++j;
            ++t;
            next[j] = (P[j] != P[t]? t : next[t]);
        } else {
            t = next[t];
        }
    }
    return next;
}

int kmp_match(char *S, char *P) {
    int *next = build_next(P);
    int n = strlen(S); // 字符串 S 的长度
    int m = strlen(P); // 模式串 P 的长度
    int i = 0; // 字符串 S 的指针
    int j = 0; // 模式串 P 的指针

    while (j < m && i < n) {
        if (j < 0 || S[i] == P[j]) {
            ++i;
            ++j;
        } else {
            j = next[j];
        }
    }
    delete [] next;
    return j == m? i - j : -1;
}

2.2 搜索全部匹配项

搜索模式串 P 在字符串 S 中出现的全部位置。

int *build_next(char *P) {
    int m = strlen(P); // 模式串 P 的长度
    int *next = new int[m + 1];
    next[0] = -1;
    int j = 0; // 主指针
    int t = -1; // 当前最长匹配前缀的结尾下一处指针

    while (j < m) {
        if (t < 0 || P[j] == P[t]) {
            ++j;
            ++t;
            next[j] = ((j == m || P[j] != P[t])? t : next[t]);
        } else {
            t = next[t];
        }
    }
    return next;
}

std::vector<int> kmp_match(char *S, char *P) {
    int *next = build_next(P);
    std::vector<int> pos; // 记录所有匹配项的位置
    int n = strlen(S); // 字符串 S 的长度
    int m = strlen(P); // 模式串 P 的长度
    int i = 0; // 字符串 S 的指针
    int j = 0; // 模式串 P 的指针

    while (i < n) {
        if (j < 0 || S[i] == P[j]) {
            ++i;
            ++j;
            if (j == m) {
                pos.push_back(i - j);
                j = next[j];
            }
        } else {
            j = next[j];
        }
    }
    delete [] next;
    return pos;
}