← 返回文章列表
TECH NOTE

KMP算法详解

约 6 分钟阅读 2535 字

为什么使用KMP

解决问题:在主串“SSSSSSSSSSSSSSSSSSSSSSSA”中查找”SSB”,比较到最后一个才知道不匹配,然后回溯,效率低。

KMP优势

分析子串n本身的特点(生成next数组),在主串m查找子串的时候,不需要重头遍历,从有重复部分的地方开始遍历即可。时间复杂度:O(m+n)

暴力查找遍历的时间复杂度:O(mn)

如何使用KMP

生成next数组

生成next数组最重要的就是:前缀和后缀相同的字符个数(找到相同个数的字符串,后面使用KMP回溯时,就可以快速回溯到特征字符串,避免每次都回溯到第一个字符)

什么是前后缀

将字符串从中间一分为二,前面为前缀,后面为后缀。
举例:
“ABCDABC”前缀为“ABC”,后缀也为“ABC”
“ABCD”前缀为“AB”,后缀为“CD”
next数组就是存放的到j位置,前后缀相同字符的个数。
举例:
“ABCDABC”
next[] = {0,0,0,0,1,2,3}
其中取next[5]为例
截取字符:ABCDAB,其中前缀“ABC”和后缀“DAB”有相同字符“AB”,长度为2,即next[5] = 2。

生成next数组代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void findNext(std::vector<int>& next, std::string pattern) {
int i = 1, j = 0;
while (i < pattern.length()) {
if (pattern[i] == pattern[j]) {
j++;
next[i] = j;
i++;
}
else if (j == 0) {
next[i] = 0;
i++;
}
else {
j = next[j-1];
}
}
}

KMP使用

将主串(下标i)和子串(下表j)进行比较。
i一直递增。
如果主串下标i处与子串下标j处相同,则j递增;
否则j回溯到next[j-1]处。
因为next是存放的是:截至到下标处相同的字符串个数。所以j要回溯到next[j-1]处。
举例
主串:“ABABCABABD”
子串:“ABABD”
i = 4, j = 4时,字符串不相同,此时j需要开始回溯。
原理:
第一次匹配j之前有n个相同的字符(后缀);
第二次匹配的时候,就取子串前n个字符(前缀)。
这也是为什么要找前后相同的字符串个数的原因。

KMP实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int search(std::string text, std::string pattern, std::vector<int> next) {
int i = 0, j = 0;
while ( i < text.length() && j < pattern.length()) {
if (text[i] == pattern[j]) {
if (j == pattern.length() - 1) {
return i - (pattern.length() - 1);
}
i++;
j++;
}
else if (!j) {
i++;
}
else {
j = next[j - 1];
}
}
return -1;
}

测试用例

  1. 寻找主串中子串位置。
1
2
3
4
5
6
7
8
void main() {
std::string text = "ABABDABACDABABCABAB";
std::string pattern = "ABABCABAB";
std::vector<int> next(pattern.length());
findNext(next, pattern);
int positions = search(text, pattern, next);
std::cout << positions;
}
  1. 214最短回文串
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
int search(std::string text, std::string pattern, std::vector<int> next) {
int i = 0, j = 0;
while (i < text.length() && j < pattern.length()) {
if (text[i] == pattern[j]) {
if (j == pattern.length() - 1) {
return pattern.length();
}
i++;
j++;
}
else if (!j) {
i++;
}
else {
j = next[j - 1];
}
}
return j;
}

void findNext(std::vector<int>& next, std::string pattern) {
int i = 1, j = 0;
while (i < pattern.length()) {
if (pattern[i] == pattern[j]) {
j++;
next[i] = j;
i++;
}
else if (j == 0) {
next[i] = 0;
i++;
}
else {
j = next[j-1];
}
}
}
void main() {
std::string s = "abcd";
std::vector<int> next(s.length());
findNext(next, s);
std::string s2 = s;
std::reverse(s2.begin(), s2.end());
int positions = search(s2, s, next);
std::cout << positions;
}

INTERACTION / 讨论与互动

评论与表情点赞