KMP算法
- KMP算法用于串的模式匹配
- 设主串为
text
,模板串为str
- 若匹配成功返回该模板串首次出现在主串的下标,若失败则返回 -1
暴力解法
i_t
为主串比较的下标i_s
为模板串比较的下标- 若某次该位置匹配成功,主串和模板串的比较位置都置为下一位;若
i_s
到达边界,则匹配成功,退出循环 - 若匹配失败,模板串比较位置退回到起始位置,即
i_s = 0
,所以i_s
也是已经匹配成功的长度;主串比较位置改为上一轮开始比较位置的下一位置,相当于主串整体前移一位,即为i_t = i_t - i_s + 1
;若i_t
越界,则匹配失败,退出循环
int violentSolution(string text, string str) {
//return text.find(str); //用于测试
int i_t = 0; //主串比较下标
int i_s = 0; //模板串比较下标
while (i_t < text.length() && i_s < str.length()) {
if (text[i_t] == str[i_s]) {
i_t++;
i_s++;
}
else {
i_t = i_t - i_s + 1; //主串整体前移一位
i_s = 0; //模板串比较位置重新置为开头位置
}
}
//错误的,若在最后匹配成功了,例如:text = "123abc",str = "abc",匹配成功时两个下标都会分别等于串长度
//return i_t == text.length() ? -1 : i_t - i_s;
//应该以 模板串下标为判断标准
return i_s = str.length() ? i_t - i_s : -1;
}
- 注意最后的返回,应以模板串下标来判断
KMP解法
前缀、后缀和部分匹配值
-
前缀:除去末位字符,串中所有 头部子串,必须包含首位字符
-
后缀:除去首位字符,串中所有 尾部子串,必须包含末位字符
-
部分匹配值:串的前缀和后缀的 最长相等前后缀长度
计算模板串的next数组
对于模板串 str
: ababaaababaa
,其next
数组为:
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
字符 | a | b | a | b | a | a | a | b | a | b | a | a |
部分匹配值 | -1 | 0 | 0 | 1 | 2 | 3 | 1 | 1 | 2 | 3 | 4 | 5 |
next
数组值为: -1 0 0 1 2 3 1 1 2 3 4 5
- 规定 :
next[0] = -1;
next[1] = 0;
-
next[j]
就是 以 0~j -1 组成的字符串的 部分匹配值例如:
next[5] = 3
,str[5] = a
,对于 0~4组成的串ababa
,其前缀有: a、ab、aba、abab;其后缀有:baba、aba、ba、a;最长相等前后缀为aba
,即部分匹配值为 3 。 -
部分匹配值
next[j]
在 KMP 算法中有何作用?决定了与主串
text
匹配时,若该位j
str[j] != text[i]
时,证明了模板串这次从 头到j-1
位都是匹配成功的,但下次匹配时,不需从头开始匹配,此时模板串str
从j - next[j]
到j - 1
的字符(后缀)已经是和 主串text
的0
到next[j] - 1
的字符(前缀)匹配成功了,只需让text[i]
和str[next[j]]
再次比较,循环这个过程,可以减少多次重复比较。 -
如图:
![](https://img-blog.csdnimg.cn/e72928f0c9834b23b36d192a0f0e4edd.png#pic_center)
计算过程如下:
i
:计算部分匹配值的位置;cur
:与str[i]比较的位置- 比较 i-1 位置的字符 与 next[i-1]即为 cur 位置的字符是否相同
- 若相同,next[i] 为 next[i-1]的值+1;同时i++,cur也++
- 若不同,cur = next[cur],再次比较,一直到 cur = 0时,此时str[i-1]与str[0]比较,若相同,则next[i] = 1;若不同为0
void getNext(string str,int *next) {
if (str.length() == 0) {
return;
}
if (str.length() == 1) {
next[0] = -1;
}
next[0] = -1;
next[1] = 0;
int i = 2; // 计算部分匹配值的位置
int cur = 0; // 与str[i]比较的位置
// 对应三个分支
while (i < str.length()) {
if (str[i - 1] == str[cur]) {
next[i++] = ++cur;
}
else if(cur>0) {
cur = next[cur];
}
else { //cur=0,且比较不相同的情况
next[i++] = 0;
}
}
}
KMP
- 如果比较相同,则都++,相当于主串和模板串的比较位置都后移一位
- 如果不同,主串位置不动,模板串比较位置置为next[i_s];
- 一直到模板串首字符与 text[i_t]比较,若相同则继续1操作,若不同则将主串比较位置后移一位,下一轮比较
int kmp(string text, string str) {
if (text.length() < 1 || str.length() < 1) {
return -1;
}
int i_t = 0; //主串该比较下标
int i_s = 0; //模板串比较下标
int* next = new int[str.length()];
getNext(str, next);
// 1. 如果比较相同,则都++,相当于主串和模板串的比较位置都后移一位
// 2. 如果不同,主串位置不动,模板串比较位置置为next[i_s];
// 一直到模板串首字符与 text[i_t]比较,若相同则继续1操作,若不同则将主串比较位置后移一位,下一轮比较
while (i_t < text.length() && i_s < str.length()) {
if (text[i_t] == str[i_s]) {
i_t++;
i_s++;
}
else if(next[i_s] == -1) {
i_t++;
}
else {
i_s = next[i_s];
}
}
delete[] next;
return i_s = str.length() ? i_t - i_s : -1;
}
-
同暴力解法,返回应以模板串的下标来判断,而非主串
-
kmp算法,主要是要理解next数组的意义,以及求解过程,其过程与kmp的模板比较过程有异曲同工之妙。