KMP – y神模板
引言:由于本人与4月20日前周五的一节数据结构课中偶然听到了老师讲
kmp
这个算法的概念并且发现老师讲的听不懂一点儿导致异常难受,于是花了两天左右自行弄懂了kmp
算法的逻辑,并写下本文以便以后复习,并作留念。声明:本文仅供对kmp理解的人扩展思路,不能教会不会
kmp
算法的人kmp的理解核心 Σ(っ °Д °;)っ还不会kmp?:以下学习资料(教程链接)助你速通,
一、next数组快速生成
#include <iostream>
using namespace std;
const int N = 1e5+1, M = 1e6+1;
char s[M], p[N];
int n, m, ne[N];
int main() {
cin >> n >> p+1 >> m >> s+1;
// 创建next数组代码:
for (int i = 2, j = 0; i <= n; i ++) {
while (j && p[i] != p[j+1]) j = ne[j];
if (p[i] == p[j+1]) j ++;
ne[i] = j;
}
// kmp匹配代码:
/*
暂时省略
*/
return 0;
}
前置条件的解释
对于代码中的变量名,这里进行解释:
变量名 | 含义 |
---|---|
s | 主串 |
p | 模式串(需要在主串中被匹配并找到位置) |
n | 模式串长度 |
m | 主串长度 |
i | 当前需要求的next 数组下标位置 |
j | i 位置字符回溯后的位置 |
对于本模板需要注意三个小的细节:
- 输出输出的字符下标都是从
1
开始存储,0
位置置空 - 每次比对时都是比对
s
串的i
下标和p
串的j+1
位下标 - 回溯:也是kmp和暴力匹配的本质区别,在于匹配失败,instead of
i
向后移动,butj
向前移动我称之为"回溯",注意和递归那个回溯本质不是同一个东西。
如图所示:
对于模式串ababab
在字符数组中的存储情况如上。
代码图示解读
for循环
对于代码:
for (int i = 2, j = 0; i <= n; i ++)
i
和j
的默认位置如图所示:
解释:
细节 | 对应的原理 |
---|---|
i=2 | i=1 位置的相等前后缀最大长度一定是0 ,而 next 数组中默认都为0,所以 i 直接跳过第一位即可 |
j=0 | 每一次都比较j+1 和i 位置是否相等,最开始也就是第一次迭代需要求i=2位置 next 数组的值,那么就需要比较j=1和i=2位置在模式串 p 中字符是否相等 |
i<=n | 这个简单,越过n 就超过模式串p 的长度了,同时也就表明next 数组计算完毕。 |
for循环中后半部分代码(比较简单放在前面)
if (p[i] == p[j+1]) j ++;
ne[i] = j;
这段代码的含义:如果i
位置字符和j+1
位置字符相等,j
后移一位,并且计算出了next
在i
位置的值。
如图:
for循环中第一句
while (j && p[i] != p[j+1]) j = ne[j];
如果不相等,使用ne
(变量名冲突所以不适用next)数组回退。
二、kmp匹配部分
#include <iostream>
using namespace std;
const int N = 1e5+1, M = 1e6+1;
char s[M], p[N];
int n, m, ne[N];
int main() {
cin >> n >> p+1 >> m >> s+1;
// create nextArray.
for (int i = 2, j = 0; i <= n; i ++) {
while (j && p[i] != p[j+1]) j = ne[j];
if (p[i] == p[j+1]) j ++;
ne[i] = j;
}
// kmp.
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) {
cout << i - n << ' ';
j = ne[j];
}
}
return 0;
}
每次进行匹配,
-
如果匹配成功
j++
-
如果匹配不成功,并且
j
还有向前回溯的可能,则回溯j = ne[j]
-
如果已经匹配出一个结果,
j == n
,打印这个子串的初始位置,并且向前回溯