东湖之滨 发表于 2025-4-6 17:52:21

KMP 算法全剖析:高效实现字符串匹配与模板题详解

在做KMP算法处置惩罚字符串范例题前,可以先参考一下KMP算法原理
下面是笔者写的一个简朴KMP算法示例~
https://i-blog.csdnimg.cn/direct/69f8e027506748da8a6d7398ba0fe16c.png
KMP算法模板

#include <iostream>
#include <vector>
using namespace std;

// 生成 nextval 数组
// nextval 数组是对 next 数组的优化,用于在 KMP 算法中更高效地进行字符串匹配
// 它可以避免在匹配过程中进行不必要的回溯
vector<int> getNextval(const string& P) {
    // 获取模式串 P 的长度
    int n = P.size();
    // 初始化 nextval 数组,长度为 n,初始值都设为 0
    vector<int> nextval(n, 0);
    // nextval 数组的第一个元素设为 -1,表示当模式串的第一个字符就不匹配时,主串指针需要后移
    nextval = -1;
    // j 用于遍历模式串 P,k 用于记录当前最长相等前后缀的长度
    int j = 0, k = -1;
    // 开始构建 nextval 数组,循环直到 j 遍历到模式串的倒数第二个字符
    while (j < n - 1) {
      // 如果 k 为 -1 或者当前遍历到的模式串字符 P 等于 P
      if (k == -1 || P == P) {
            // j 和 k 都向后移动一位
            j++; k++;
            // 如果 P 不等于 P,则 nextval 就等于 k
            // 否则,将 nextval 的值赋给 nextval,避免不必要的回溯
            nextval = (P != P) ? k : nextval;
      } else {
            // 如果 P 不等于 P,则将 k 更新为 nextval,继续寻找合适的前后缀
            k = nextval;
      }
    }
    // 返回构建好的 nextval 数组
    return nextval;
}

// KMP 匹配算法
// 在主串 S 中查找模式串 P 第一次出现的位置
int kmp(const string& S, const string& P) {
    // 调用 getNextval 函数生成模式串 P 的 nextval 数组
    vector<int> nextval = getNextval(P);
    // i 用于遍历主串 S,j 用于遍历模式串 P
    int i = 0, j = 0;
    // 获取主串 S 和模式串 P 的长度
    int lenS = S.size(), lenP = P.size();
    // 开始进行匹配,只要主串和模式串都还没遍历完就继续循环
    while (i < lenS && j < lenP) {
      // 如果 j 为 -1 或者当前主串字符 S 等于模式串字符 P
      if (j == -1 || S == P) {
            // 主串和模式串的指针都向后移动一位
            i++; j++;
      } else {
            // 如果不匹配,则将 j 更新为 nextval,根据 nextval 数组进行回溯
            j = nextval;
      }
    }
    // 如果模式串 P 被完全匹配(j 等于模式串的长度),则返回匹配的起始位置
    // 匹配起始位置为 i - j,否则返回 -1 表示未找到匹配
    return (j == lenP) ? i - j : -1;
}

int main() {
    // 定义主串 S
    string S = "ababaaababaaababaaababaa";
    // 定义模式串 P
    string P = "ababaaababaa";
    // 调用 kmp 函数在主串 S 中查找模式串 P 的位置
    int pos = kmp(S, P);
    // 输出匹配的位置
    cout << "匹配位置: " << pos << endl; // 输出 0
    return 0;
}
洛谷P3375【模板】KMP例题

https://i-blog.csdnimg.cn/direct/d66bfbbed37042ef89c1765d891e378d.png
输入样例
ABABABC
ABA
输出样例
1
3
0 0 1
算法思路

本题主要涉及两个核心任务:一是找出字符串 s2 在字符串 s1 中全部出现的位置;二是盘算 s2 每个前缀的最长 border 长度。我们可以使用 KMP(Knuth-Morris-Pratt)算法来高效地完成这两个任务。

[*]盘算最长 border 长度(构建 next 数组)


[*]next 数组的界说:next 表示字符串 s2 长度为 i 的前缀的最长 border 长度。
[*]构建过程:使用双指针法,一个指针 i 用于遍历字符串 s2,另一个指针 j 用于记录当前最长 border 的长度。当 s2=s2时,i 和 j 同时后移;当 s2!=s2 时,j 回溯到 next 的位置继续比较,直到找到匹配或 j 为 0。

[*]字符串匹配


[*]利用已经构建好的 next 数组,使用双指针法在 s1 中匹配 s2。一个指针 i 遍历 s1,另一个指针 j 遍历 s2。当 s1=s2 时,i 和 j 同时后移;当 s1!=s2 时,j 回溯到 next 的位置继续比较,直到找到匹配或 j 为 0。当 j 等于 s2 的长度时,说明找到了一个匹配,记录匹配位置并继续匹配。
代码示例

#include <iostream>
#include <vector>
#include <string>

using namespace std;

// 构建 next 数组
vector<int> getNext(const string& s) {
    int n = s.size();
    vector<int> next(n, 0);
    for (int i = 1, j = 0; i < n; ++i) {
      while (j > 0 && s != s) {
            j = next;
      }
      if (s == s) {
            ++j;
      }
      next = j;
    }
    return next;
}

// KMP 匹配
vector<int> kmp(const string& s1, const string& s2, const vector<int>& next) {
    vector<int> positions;
    int n1 = s1.size(), n2 = s2.size();
    for (int i = 0, j = 0; i < n1; ++i) {
      while (j > 0 && s1 != s2) {
            j = next;
      }
      if (s1 == s2) {
            ++j;
      }
      if (j == n2) {
            positions.push_back(i - n2 + 1 + 1); // 位置从 1 开始计数
            j = next;
      }
    }
    return positions;
}

// 输出匹配位置
void printPositions(const vector<int>& positions) {
    for (int pos : positions) {
      cout << pos << endl;
    }
}

// 输出每个前缀的最长 border 长度
void printNext(const vector<int>& next) {
    for (int i = 0; i < next.size(); ++i) {
      if (i > 0) cout << " ";
      cout << next;
    }
    cout << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    string s1, s2;
    cin >> s1 >> s2;

    // 构建 next 数组
    vector<int> next = getNext(s2);

    // 进行 KMP 匹配
    vector<int> positions = kmp(s1, s2, next);

    // 输出匹配位置
    printPositions(positions);

    // 输出每个前缀的最长 border 长度
    printNext(next);

    return 0;
}

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: KMP 算法全剖析:高效实现字符串匹配与模板题详解