kmp算法

文章目录
原文地址:https://ngunauj.github.io

之前的一片博客已经谈到了kmp,kmp应该是我在课堂上学习的第一个算法。当时对这个了解的并不是很深,而且比赛的时候直接套板子就行了。今天重新看了一遍这个算法,古人云:温故而知新,可以为师。

kmp算法也是用来处理字符串匹配问题的,如之前将的字符串哈希算法,可以用来判断某个字符串是否在另一个字符串中,可以统计字符串a在字符串b中出现的次数,kmp中的next数组可以用来求当前串的前缀和和后缀相等的最大长度。

1.kmp思想

当两个字符串进行匹配的时候,最暴力的方法是O(nm)的算法,如用字符串哈希可以达到O(m+n),kmp也可以达到O(m+n)而且理论上kmp要比字符串哈希快。那么这里我们要解释一下为什么kmp时间复杂度为O(m+n)。
假设字符串T的长度为n,字符串P的长度为m,m<=n,现在我要判断T是否包含P。暴力的方法当然是,依次枚举T的每一位,如果T[i] == P[0]那么我们就进行T[i+1] 和 P[1]的比较,一次类推,如果在某个位置如T[i+5] 和 P[5]不匹配了,那么我们只能从P[0]开始重新比较,那么这样,最坏的时间复杂度为O(n
m)。事实上如果我们提前计算出 P[5]和T[i+5]不匹配时,从P[x]开始重新匹配,那么时间复杂度将会降到O(n+m)。而提这个提前计算的过程就是计算next数组的过程。

2.next数组

next数组表示的是长度,下标从1开始,但是在遍历原字符串时,下标还是从0开始。假设我们现在已经求得next[1]、next[2]、……next[i],分别表示长度为1到i的字符串的前缀和后缀最大公共长度,现在要求next[i+1]。
图
由上图我们可以看到,如果位置i和位置next[i]处的两个字符相同(下标从零开始),则next[i+1]等于next[i]加1。如果两个位置的字符不相同,我们可以将长度为next[i]的字符串继续分割,获得其最大公共长度next[next[i]],然后再和位置i的字符比较。这是因为长度为next[i]前缀和后缀都可以分割成上部的构造,如果位置next[next[i]]和位置i的字符相同,则next[i+1]就等于next[next[i]]加1。如果不相等,就可以继续分割长度为next[next[i]]的字符串,直到字符串长度为0为止。类是dp。
这里举个例子求字符串:P:abcabcabcda的next数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
void makeNext(const char P[],int next[]) {
int i,j;
int m = strlen(P);
next[0] = 0;
for (i = 1,j = 0; i < m; ++i) {
while(j > 0 && P[i] != P[j])
j = next[j-1];
if (P[i] == P[j]) {
j++;
}
next[i] = j;
}
}

P的next素组为0,0,0,1,2,3,4,5,6,0,1

3.代码实现

下面给出一个字符串里有几个另一个字符串(可重叠和不可重叠两种)的示例代码
(1)可重复

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<stdio.h>
#include<string.h>
void makeNext(const char P[],int next[]) {
int i,j;
int m = strlen(P);
next[0] = 0;
for (i = 1,j = 0; i < m; ++i) {
while(j > 0 && P[i] != P[j])
j = next[j-1];
if (P[i] == P[j]) {
j++;
}
next[i] = j;
}
}
int kmp(const char T[],const char P[],int next[]) {
int n,m;
int i,q;
n = strlen(T);
m = strlen(P);
makeNext(P,next);
for (i = 0,q = 0; i < n; ++i) {
while(q > 0 && P[q] != T[i])
q = next[q-1];
if (P[q] == T[i]){
q++;
}
if (q == m){
printf("Pattern occurs with shift:%d\n",(i-m+1));
}
}
}
int main() {
int i;
int next[20]={0};
char T[] = "ababababaaaaaaa";
char P[] = "abcabcabcda";
printf("%s\n",T);
printf("%s\n",P );
// makeNext(P,next);
kmp(T,P,next);
for (i = 0; i < strlen(P); ++i){
printf("%d ",next[i]);
}
printf("\n");
return 0;
}

(2)不可重复

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<stdio.h>
#include<string.h>
void makeNext(const char P[],int next[]) {
int i,j;
int m = strlen(P);
next[0] = 0;
for (i = 1,j = 0; i < m; ++i) {
while(j > 0 && P[i] != P[j])
j = next[j-1];
if (P[i] == P[j]) {
j++;
}
next[i] = j;
}
}
int kmp(const char T[],const char P[],int next[]) {
int n,m;
int i,q;
n = strlen(T);
m = strlen(P);
makeNext(P,next);
for (i = 0,q = 0; i < n; ++i) {
while(q > 0 && P[q] != T[i])
q = next[q-1];
if (P[q] == T[i]){
q++;
}
if (q == m){
printf("Pattern occurs with shift:%d\n",(i-m+1));
q = 0;
}
}
}
int main() {
int i;
int next[20]={0};
char T[] = "ababababaaaaaaa";
char P[] = "abcabcabcda";
printf("%s\n",T);
printf("%s\n",P );
// makeNext(P,next);
kmp(T,P,next);
for (i = 0; i < strlen(P); ++i){
printf("%d ",next[i]);
}
printf("\n");
return 0;
}

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
,