KMP 算法

KMP算法是一种用于快速找出匹配的子字符串位置的算法。通过记录要匹配的字符串各位置前面子字符串最长相同前后缀的信息,达到加速匹配过程的效果。
前缀字符串是指从前向当前字符串位置数的字符串;后缀字符串是指从后向当前字符串数的字符串。这里是不包含自身位置的。例如字符串"aaabaa",其相同前缀缀长度是

a a a b a a b
-1 0 1 2 0 1 2

这里我们规定最前的字符对应的是-1,第二个字符因为前面字符串只有一个字符,所有记作0。
第三个字符是a,它前面的字符串是"aa",将这个字符串从前数和从后数,得到的最长字符串长度是1(因为不包含自身),所以记作1.
第四个字符是b,它前面的字符串是"aaa",那么从前数最长的前缀字符串是"aa",从后数,最长的后缀字符串是"aa",且两者匹配,那就记作2。
同理,第5个字符a,它前面的字符串是"aaab",因为从前数第一个字符是"a",从后数第一个"字符是"b",直接不匹配,那它就是0。

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
*@brief 计算子串每个位置的最长匹配前后缀的长度
*
* @param m 要计算的子串
* @param next_arr 返回的长度数组,实际上也是记录了匹配到的最长前缀的后面一个元素下标
* @param index 当前要计算的下标位置
* @param cn index的前一个字符最长匹配前后缀的长度,也就是index - 1位置对应的最长匹配前后缀的前缀的后面一个元素的下标
* @return vector<int>
*/
vector<int> getNextArray(string m) {
if(m.size() == 1) {
return vector<int> {-1};
}
else if(m.size() == 2) {
return vector<int> {-1, 0};
}

vector<int> next_arr{-1, 0}; //前两个位置规定为-1和0

//next_arr数组可以用动态数组的方法
//i-1位置记录的地址上的字符如果和i位置上的字符相同,第i位置记录的数值就应该是第i-1位置的数值加一;
//如果不相同就继续往前跳,直到找到一个位置上的字符和第i-1位置上的相同,第i位置上的数值就等于该位置上的数值加一,如果到头了也不等,就等于零
int index = 2; //第i位置下标
int cn = 0; //哪个位置和i-1处的字符比较
while (index < m.size()) {
if (m[index - 1] == m[cn]) { //如果相等
next_arr.push_back(cn + 1);
cn = next_arr.back(); //下个比较的位置
++index; //开始下个位置
}
else if (cn > 0) { //如果不相等,且没到头,cn向前跳
cn = next_arr[cn];
}
else { //到头了
next_arr.push_back(0);
++index;
}
}
return next_arr;
}

int KMP (string s, string m) {
//s是较长字符串,m是要匹配的字符串
if(s.size() == 0 || m.size() == 0 || s.size() < m.size()){
return -1;
}
vector<int> arr_s;
vector<int> arr_m;
vector<int> nextArr = getNextArray(m); //记录匹配字符串对应的数组
int index_s = 0;
int index_m = 0;
//O(N)
while (index_s < s.size() && index_m < s.size()) {
//对应位置匹配就同时++
if(s[index_s] == m[index_m]) {
index_s ++;
index_m ++;
}
//m到头了也没匹配上,那么就只能s向后移动了
else if (index_m == 0) {
index_s ++;
}
//如果没匹配上,根据next_arr记录的位置向前跳
else {
index_m = nextArr[index_m];
}
}
//最终是哪个下标先越界,index_m越界说明找到了子字符串,index_s越界了说明没找到
return index_m == m.size()? index_s - index_m: -1;
}