KMP算法
Nevermore 2024-01-17 algorithm
# 求Next数组
Next数组用来计算匹配字符的回退位置,有多种求解的思路,本篇列举两种。不同的Next数组使用起来也会有所差异。
设置场景:
需要查找的字符串:a a b b c c f a a b b c f
模式字符串:a a b b c f
# 方式一
- 定义next[0] = -1; next[1] = 0。
- next[i] 表示下标从 0 到 i-1 的字符串,最大的相等前缀字符串和后缀字符串的长度
- 模式字符串对应的next数组为:[-1, 0, 1, 0, 2, 0]
- 若第i位字符不匹配,则回退到next[i].
- 求解:
- 若next[i] == k,则说明s[i-1] 前有k个相等的字符,即前缀s[0]...s[k-1] == s[i-k]...s[i-1]
- 若s[i] == s[k],则next[i+1] = k + 1;
- 若s[i] != s[k],则比较s[i] 和 s[k] = s[ next[i] ],若仍然不相等,则 s[ next[i] ]回退为s[ next[next[i] ] ] 直到 s[i] == s[k],此时next[i+1] = k + 1 = next[next[i] ...] + 1; (k为最终next数组回退到的下标)
void GetNext(std::vector<int>& next, const std::string& s) {
next[0] = -1;
next[1] = 0;
int size = s.size();
if (size < 3) return;
int k = 0; //记录最长相等子串
for (int i = 2; i < size; i++) {
while (k > -1 && s[k] != s[i - 1]) { //回退
k = next[k];
}
if (k == -1 || s[i - 1] == s[k]) { // next[i] 代表前 i-1 长度的子串,所有s[i-1]
next[i] = k + 1;
k++;
/* 优化写法
k++;
if (s[k] == s[i]) { //当前字符和回退的字符相等
next[i] = next[k];
}
else {
next[i] = k;
}*/
}
}
}
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
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
- Next数组的优化
若回退到的字符和当前字符相同,当前nextval[i] = 回退位置的nextval[i] ;若不同,当前nextval[i] = 原来的next[i]。
# 方式二
- next[0] = 0;
- next[i] 表示下标从 0 到 i 的字符串,前缀字符串(不包含最后一个元素)和后缀字符串(不包含第一个元素)最大的相等长度。
- **注意:**字符串 i s s i p ,其next数组为[0, 0, 0, 1, 0],“iss”和“ssi“对称但并不相等,只有'i'是相等的。
- 模式字符串对应的next数组为:[0, 1, 0, 2, 0, 0]
- 若第i位字符不匹配,则回退到next[i-1].
求解可参考方式一:
- 若next[i-1] == k,则说明s[i-1] (包含)前有k个相等的字符,即前缀s[0]...s[k-1] == s[i-k]...s[i-1]
- 若s[i] == s[k],则next[i] = k + 1;
- 若s[i] != s[k],则比较s[i] 和 s[k] = s[ next[i-1] ],若仍然不相等,则 s[ next[i] ]回退为s[ next[next[i-1] ] ] 直到 s[i] == s[k],此时next[i] = k + 1 = next[next[i-1] ...] + 1; (k为最终next数组回退到的下标)
void GetNext(std::vector<int>& next, const std::string& s) {
next[0] = 0;
int size = s.size();
int k = 0; //记录最长相等子串
for (int i = 1; i < size; i++) {
while (k > 0 && s[k] != s[i]) {
k = next[k-1]; //遇见冲突看前一位
}
if (s[i] == s[k]) { // next[i] 代表前 i-1 长度的子串,所有s[i-1]
k++;
}
next[i] = k;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- k和i还可以表示不同的含义:k为前缀末尾,也表示最大相等子串的长度。i为后缀末尾
- 如果s[i] != s[k],则k向前回退,直到相等
# KMP返回字符串下标
int KMPFind(const std::string& Original, const std::string& sub) {
int OriginlLen = Original.size();
int SubLen = sub.size();
if (OriginlLen == 0 || SubLen == 0) return -1;
std::vector<int> next(SubLen, 0);
GetNext(next, sub);
int j = 0;
for (int i = 0; i < Original.size(); i++) {
while (j > 0 && Original[i] != sub[j]) {
j = next[j - 1];
}
if (Original[i] == sub[j]) {
j++;
}
if (j == sub.size()) {
return (i - sub.size() + 1);
}
}
return -1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22