# 再探KMP
### 算法分析
KMP算法的时间复杂度应该是O(n + m)的;其中n是模式串p的长度,m是字符串s的长度;
因为遍历模式串p求next数组的时间复杂度为O(n),遍历s对p进行匹配的次数为O(m);对于字符串匹配的推导过程
首先是BF算法
```
#include <iostream>
using namespace std;
const int N = 1e5 + 10, M = 1e6 + 10;
int main() {
char p[N], s[M];
int n, m;
cin >> n >> m;
cin >> p + 1 >> s + 1;
for (int i = 1; i <= m - n + 1; i++) {
bool flag = true;
for (int j = 1; j <= n; j++) {
if (s[i + j - 1] != p[j]) {
flag = false;
break;
}
}
if (flag) cout << i << ' ' << i + n - 1 << endl;
}
}
```
可以看到BF算法的时间复杂度为O(n * m);
有神人优化出了KMP算法:
我们通过求出模式串数组p的前缀长度ne数组
ne[i] 表示的为p[1, i] 中最长前后缀的长度
求ne[i]的过程为先用模式串p对其自身进行匹配;
设其被匹配的为p1,进行匹配的为p2;
其逻辑为:
1、让p2后移一位用p1[i] 与 p2[j + 1]进行匹配, 这里使得i严格大于j
2、匹配中p1[k] != p2[j + 1] 使用j = ne[j](已经被计算过);
3、匹配过程中有三种匹配情况
- 1. j = 0 时,j被回溯到0, 或刚开始匹配时,p1[i] != p2[j + 1] 语句将不会被执行, 所以当找到p[i] == p[j + 1]需要if来判断
- 2. j = k时,并且回溯完没有被回溯到0, if判断多余
- 3. j = n时,表示整个串都被包含,虽然在找ne数组时不会发生,但在p与s匹配时会出现;
#include <iostream>
using namespace std;
// p[N] 是模式串(即p在s中多次以子串出现), s[M] 为字符串;
const int N = 1e5 + 10, M = 1e6 + 10;
char p[N], s[M];
// ne[i] 记录的是使p[N]中前 i 个字符形成的子串的最长前后缀的长度
int ne[N];
int main() {
int n, m;// n 为模式串p的长度,m为字符串s的长度;
//将字符数组数组预处理成从1开始的字符串;
cin >> n >> p + 1 >> m >> s + 1;
//在匹配过程中我们的j 一直是落后于 i的 i对比的一直是j + 1;
for (int i = 2, j = 0; i <= n; i++) {
//当我们的i遍历到第k个点的时候, 如果 j 未回溯到开头并且发现p[i] != p[j + 1]
//我们让j 回溯到 ne[j]再进行匹配, 因为ne[j] 中记录的就是p[1, j] 中最长前后缀的长度
while (j && p[i] != p[j + 1]) j = ne[j];
//匹配成功让j继续向下走
// 为什么用if 因为有可能前面j回溯到了最前面 而循环结束的;
if (p[i] == p[j + 1]) j++;
//记录一下ne[i], 表明p[1, i]中的最长前后缀长度为j;
ne[i] = j;
}
for (int i = 1, j = 0; i <= m; i++) {
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j++;
if (j == n) {
//有的题下标从0开始有的题下标从1开始;
cout << i - n << ' '; // i - n + 1是下标从1开始的;
j = ne[j];
}
}
}