kmp,字符串匹配算法。

举个例子

文本串 aabaabaaf
模式串aabaaf

前缀表

存储 最长相等的前后缀 长度?——值得注意的是 相等 不是对称相等,而是完全相等。ab==ab ab!=ba
先根据模式串初始化前缀表

模式串的前缀 最长相等的前后缀 最长相等的前后缀长度
a 没有 0
aa a 1
aab 没有 0
aaba a 1
aabaa aa 2
aabaaf 没有 0

前缀表有人称为 next 数组, 也有人写 prefix 数组。我这里写 next 数组

前缀表的使用

在匹配到不同字符之后, 看该字符的前一位的前缀表所对应的值, 将比较指针跳到对应的值下标处。
也就是 j = next[j-1]
有些教学可能不是直接看前一位,但是具体原理都是差不多的。
核心:遇见冲突位置,要向前回退。

  1. void kmp(const string& s, const string& p) {
  2. for (int i = 0, j = 0; i < s.length(); ++i)
  3. {
  4. while (j && s[i] != p[j]) j = next[j - 1];// 不断前移j指针,直到成功匹配或移到头为止
  5. if (s[i] == p[j]) j++; // 当前位匹配成功,j指针右移
  6. if (j == p.length())
  7. {
  8. // 对s[i - j + 1 .. i]进行一些操作
  9. j = next[j - 1];
  10. }
  11. }
  12. }


怎么获得前缀表

如果暴力去求,那就没意思了。时间复杂度KMP - 图1,那等于没用kmp。巧妙的做法是:错开一位后,让模式串匹配自己——相当于是 前缀去匹配后缀。
很明显,next[0] == 0,之后的每一位就通过匹配中记录 j 的值获得。

  1. // next[0] = 0;
  2. void get_next(const string& p) {
  3. for (int i = 1, j = 0; i < plen; ++i)
  4. {
  5. while (j && p[i] != p[j]) // 遇见冲突
  6. j = next[j - 1];
  7. if (p[i] == p[j]) j++;
  8. next[i] = j;
  9. }
  10. }

来点题目

P3375 【模板】KMP字符串匹配

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int MAXN = 1e6 + 5;
  4. int next[MAXN];
  5. void get_next(const string& s) {
  6. for (int i = 1, j = 0; i < s.length(); ++i) {
  7. while (j && s[i] != s[j]) j = next[j - 1];
  8. if (s[i] == s[j]) j++;
  9. next[i] = j;
  10. }
  11. }
  12. void kmp(const string& s, const string& p) {
  13. for (int i = 0, j = 0; i < s.length(); ++i) {
  14. while (j && s[i] != p[j]) j = next[j - 1];
  15. if (s[i] == p[j]) j++;
  16. if (j == p.length()) {
  17. cout << i - j + 2 << '\n'; // 因为要1-index,所以是+2
  18. j = next[j - 1];
  19. }
  20. }
  21. }
  22. int main() {
  23. ios::sync_with_stdio(false);
  24. string s, p;
  25. cin >> s >> p;
  26. get_next(p);
  27. kmp(s, p);
  28. for (int i = 0; i < p.length(); ++i)
  29. cout << next[i] << ' ';
  30. return 0;
  31. }