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

打印 上一主题 下一主题

主题 1723|帖子 1723|积分 5173

在做KMP算法处置惩罚字符串范例题前,可以先参考一下KMP算法原理
下面是笔者写的一个简朴KMP算法示例~

KMP算法模板

  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. // 生成 nextval 数组
  5. // nextval 数组是对 next 数组的优化,用于在 KMP 算法中更高效地进行字符串匹配
  6. // 它可以避免在匹配过程中进行不必要的回溯
  7. vector<int> getNextval(const string& P) {
  8.     // 获取模式串 P 的长度
  9.     int n = P.size();
  10.     // 初始化 nextval 数组,长度为 n,初始值都设为 0
  11.     vector<int> nextval(n, 0);
  12.     // nextval 数组的第一个元素设为 -1,表示当模式串的第一个字符就不匹配时,主串指针需要后移
  13.     nextval[0] = -1;
  14.     // j 用于遍历模式串 P,k 用于记录当前最长相等前后缀的长度
  15.     int j = 0, k = -1;
  16.     // 开始构建 nextval 数组,循环直到 j 遍历到模式串的倒数第二个字符
  17.     while (j < n - 1) {
  18.         // 如果 k 为 -1 或者当前遍历到的模式串字符 P[j] 等于 P[k]
  19.         if (k == -1 || P[j] == P[k]) {
  20.             // j 和 k 都向后移动一位
  21.             j++; k++;
  22.             // 如果 P[j] 不等于 P[k],则 nextval[j] 就等于 k
  23.             // 否则,将 nextval[k] 的值赋给 nextval[j],避免不必要的回溯
  24.             nextval[j] = (P[j] != P[k]) ? k : nextval[k];
  25.         } else {
  26.             // 如果 P[j] 不等于 P[k],则将 k 更新为 nextval[k],继续寻找合适的前后缀
  27.             k = nextval[k];
  28.         }
  29.     }
  30.     // 返回构建好的 nextval 数组
  31.     return nextval;
  32. }
  33. // KMP 匹配算法
  34. // 在主串 S 中查找模式串 P 第一次出现的位置
  35. int kmp(const string& S, const string& P) {
  36.     // 调用 getNextval 函数生成模式串 P 的 nextval 数组
  37.     vector<int> nextval = getNextval(P);
  38.     // i 用于遍历主串 S,j 用于遍历模式串 P
  39.     int i = 0, j = 0;
  40.     // 获取主串 S 和模式串 P 的长度
  41.     int lenS = S.size(), lenP = P.size();
  42.     // 开始进行匹配,只要主串和模式串都还没遍历完就继续循环
  43.     while (i < lenS && j < lenP) {
  44.         // 如果 j 为 -1 或者当前主串字符 S[i] 等于模式串字符 P[j]
  45.         if (j == -1 || S[i] == P[j]) {
  46.             // 主串和模式串的指针都向后移动一位
  47.             i++; j++;
  48.         } else {
  49.             // 如果不匹配,则将 j 更新为 nextval[j],根据 nextval 数组进行回溯
  50.             j = nextval[j];
  51.         }
  52.     }
  53.     // 如果模式串 P 被完全匹配(j 等于模式串的长度),则返回匹配的起始位置
  54.     // 匹配起始位置为 i - j,否则返回 -1 表示未找到匹配
  55.     return (j == lenP) ? i - j : -1;
  56. }
  57. int main() {
  58.     // 定义主串 S
  59.     string S = "ababaaababaaababaaababaa";
  60.     // 定义模式串 P
  61.     string P = "ababaaababaa";
  62.     // 调用 kmp 函数在主串 S 中查找模式串 P 的位置
  63.     int pos = kmp(S, P);
  64.     // 输出匹配的位置
  65.     cout << "匹配位置: " << pos << endl; // 输出 0
  66.     return 0;
  67. }
复制代码
洛谷P3375【模板】KMP例题


输入样例
  1. ABABABC
  2. ABA
复制代码
输出样例
  1. 1
  2. 3
  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[j]时,i 和 j 同时后移;当 s2!=s2[j] 时,j 回溯到 next[j - 1] 的位置继续比较,直到找到匹配或 j 为 0。

  • 字符串匹配


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

  1. #include <iostream>
  2. #include <vector>
  3. #include <string>
  4. using namespace std;
  5. // 构建 next 数组
  6. vector<int> getNext(const string& s) {
  7.     int n = s.size();
  8.     vector<int> next(n, 0);
  9.     for (int i = 1, j = 0; i < n; ++i) {
  10.         while (j > 0 && s[i] != s[j]) {
  11.             j = next[j - 1];
  12.         }
  13.         if (s[i] == s[j]) {
  14.             ++j;
  15.         }
  16.         next[i] = j;
  17.     }
  18.     return next;
  19. }
  20. // KMP 匹配
  21. vector<int> kmp(const string& s1, const string& s2, const vector<int>& next) {
  22.     vector<int> positions;
  23.     int n1 = s1.size(), n2 = s2.size();
  24.     for (int i = 0, j = 0; i < n1; ++i) {
  25.         while (j > 0 && s1[i] != s2[j]) {
  26.             j = next[j - 1];
  27.         }
  28.         if (s1[i] == s2[j]) {
  29.             ++j;
  30.         }
  31.         if (j == n2) {
  32.             positions.push_back(i - n2 + 1 + 1); // 位置从 1 开始计数
  33.             j = next[j - 1];
  34.         }
  35.     }
  36.     return positions;
  37. }
  38. // 输出匹配位置
  39. void printPositions(const vector<int>& positions) {
  40.     for (int pos : positions) {
  41.         cout << pos << endl;
  42.     }
  43. }
  44. // 输出每个前缀的最长 border 长度
  45. void printNext(const vector<int>& next) {
  46.     for (int i = 0; i < next.size(); ++i) {
  47.         if (i > 0) cout << " ";
  48.         cout << next[i];
  49.     }
  50.     cout << endl;
  51. }
  52. int main() {
  53.     ios::sync_with_stdio(false);
  54.     cin.tie(nullptr);
  55.     string s1, s2;
  56.     cin >> s1 >> s2;
  57.     // 构建 next 数组
  58.     vector<int> next = getNext(s2);
  59.     // 进行 KMP 匹配
  60.     vector<int> positions = kmp(s1, s2, next);
  61.     // 输出匹配位置
  62.     printPositions(positions);
  63.     // 输出每个前缀的最长 border 长度
  64.     printNext(next);
  65.     return 0;
  66. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

东湖之滨

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表