KMP 算法

KMP

模板1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
char s[maxn], p[maxn];
int nxt[maxn];

void getnxt(char *p){
int len = strlen(p), k = -1, i = 0; nxt[0] = -1;
while (i < len){
if (k == -1 || p[k] == p[i]) i++, k++, nxt[i] = k;
else k = nxt[k];
}
}
int kmp(char *s, char *p){
getnxt(p);
int slen = strlen(s), plen = strlen(p), i = 0, j = 0;
while (i < slen && j < plen){
if (j == -1 || s[i] == p[j]) i++, j++;
else j = nxt[j];
}
if (j == plen) return i - j;
return -1;
}

模板2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void getfail(int len, char* s, int fail[]) {
fail[1] = 0;
for (int i = 2; i <= len; i++) {
int cur = fail[i - 1];
while (cur > 0 && s[cur + 1] != s[i])
cur = fail[cur];
if (s[cur + 1] == s[i])
++cur;
fail[i] = cur;
}
}
void kmp(char *s, char *p) {
int slen = strlen(s + 1), plen = strlen(p + 1), cur = 0;
getfail(plen, p, nxt);
for (int i = 1; i <= slen; i++) {
while (cur > 0 && s[i] != p[cur + 1]) cur = nxt[cur];
if (p[cur + 1] == s[i]) cur++;
if (cur == plen) {
printf("%d\n", i - cur + 1);
cur = nxt[cur];
}
}
}

扩展KMP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
char s[maxn], t[maxn];
int nxt[maxn], extend[maxn];

void getnxt(char *s){
int n = strlen(s), p = 0, k = 1; nxt[0] = n;
while (p + 1 < n && s[p] == s[p + 1]) p++;
nxt[1] = p;
for (int i = 2; i < n; i++){
p = k + nxt[k] - 1;
if (i + nxt[i - k] <= p) nxt[i] = nxt[i - k];
else {
int j = max(p - i + 1, 0);
while (i + j < n && s[i + j] == s[j]) j++;
nxt[i] = j; k = i;
}
}
}
void exkmp(char *t, char *s){
getnxt(s); int tlen = strlen(t), slen = strlen(s), p = 0, k = 0;
while (p < tlen && p < slen && t[p] == s[p]) p++;
extend[0] = p;
for (int i = 1; i < tlen; i++){
p = k + extend[k] - 1;
if (i + nxt[i - k] <= p) extend[i] = nxt[i - k];
else {
int j = max(p - i + 1, 0);
while (i + j < tlen && j < slen && t[i + j] == s[j]) j++;
extend[i] = j; k = i;
}
}
}

注意

参数均为前一个是文本串,后一个为模板串。

应用

求 $p$ 串在 $t$ 串中的位置可重复的出现次数

传送门:子串查找

$kmp$ 时,$j=plen$ 时更新答案,并将 $j$ 转移到对应的 $nxt$ 值。

求 $s$ 串的最短循环节

传送门:Power Strings

$getnxt()$ 后,$nxt[len]$ 表示 $s$ 串前缀和后缀匹配的最大长度。

如果 $len-nxt[len]$ 整除 $len$,则这个串有循环节,循环节长度就是 $len-nxt[len]$。

求每个前缀的出现次数

每经过模板串上的一个位置,这个前缀和他的前缀函数等等出现次数都 $+1$。

匹配时更新一下每个位置的经过次数,最后倒序根据前缀函数更新次数。

代码

1
2
3
4
5
6
7
8
getfail(strlen(t + 1), t, fail);
for (int i = 1; i <= n; i++) {
while (cur && t[cur + 1] != s[i]) cur = fail[cur];
if (t[cur + 1] == s[i]) cur++;
ans[cur]++;
if (cur == m) cur = fail[cur];
}
for (int i = m; i >= 1; i--) ans[fail[i]] += ans[i];
  • 本文作者: XLor
  • 本文链接: https://xlor.cn/2018/10/kmp/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!