KMP算法的next数组计算
next数组的意思
在KMP算法中,目标字符串的游标不可能回退,只有用于匹配的字符串的游标会回退,而回退的位置却不是任意的,它通过next数组来获取下一次回退的位置。个人认为,对于next数组的理解和计算是整个KMP算法的核心 。
对于某一个字符串,关于next数组的解释如下:
在上面有 next[j]=k ,表明 k 前面的浅蓝色区域和 j 前面的浅蓝色区域是相同的。(不包括k,j)
上图给出的则是当字符串 j 位置所在的字符和主串对应的字符不相等时,j 回退到 k ,在继续和主串对应的字符进行匹配。这样字符串 k 前面的字符就不需要比较了,有效的解决了暴力匹配中重复计算的问题。
next数组的推导和计算
首先,对于字符串的第一个字符,我们约定:next[0] = -1
,不同的教程可能是将next数组的下标从1开始计算,并记 next[1] = 0
,这个无伤大雅,基本原理摆在这里。
现在,我需要去求next[j+1],而目前已知 next[j] = k 。则如下图:
此时,需要考虑的是 s[k] 和 s[j] 是否相等,下面给出讨论:
当 s[k] == s[j] 时
则应当有 next[j+1] = next [j] + 1
,由上图显而易见可以得出的结论
当 s[k] != s[j] 时
则不能像等于的情况下一样,简单的得出结论,我们需要找到一个新的k1,满足 k1 前的子串和 j 前的子串是相等的。注意到 k1 一定小于 k ,毕竟s [k] 已经不符合条件了。
再上图中有 s[k1] == s[j]
,那么则有next[j+1] = next [k1] +1。现在问题来到了怎么找到K1:
注意到下标 k 前面存在一段子串和下标 j 前面的一段子串完全相同。并且下标 k 前的子串是一定长于下标 k1 前面的子串(k1 < k),那么在下标 k 前的子串中,一定能找出一个子子串和 k1 前的子串相等。
在上图中,1 和 2 和 3 是完全相等的子串。由于1 和 2 已经相等了,那么有关系 next[k] = k1
带入上面的式子中,则有 next[j+1]=next[next[j]] + 1
。
此时还有一个问题,如果不存在这样的 k1 怎么办,那么最终肯定有的k1 = -1,就是从头开始匹配。此外,注意到不一定是只嵌套一层next,因为上面的结论是成立在存在有的前提上,它可能需要经过多次嵌套才能得出结果。
一个例子
游标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
字符 | a | b | a | b | a | a | a | b |
next | -1 | 0 | 0 | 1 | 2 | 3 | 1 | 1 |
对于 next[0], 规定为 -1,根据前面的分析:next[1] = 0;
对于 next[2],则观察 1 和 next[1] = 0,即 b 和 a,不相等;而next[next[1]] = -1,因此 next[2] = 0;
对于 next[3],观察 2 和 next[2] = 0,即 a 和 a,相等,故 next[3] = next[2] + 1 = 1;
对于 next[4],观察 3 和 next[3] = 1,即 b 和 b,相等,故 next[4] = next[3] + 1 = 2;
对于 next[5],观察 4 和 next[4] = 2,即 a 和 a,相等,故 next[5] = next[4] + 1 = 3;
对于 next[6],观察 5 和 next[5] = 3,即 a 和 b,不相等,next[next[5]] = 1,与 5 比较即 b 和 a,不相等,继续递归 next[next[next[5]]] = next[next[3]] = next[1] = 0;比较 0 和 5 即 a 和 a,相等,因此 next[6] = next[next[next[5]] + 1 = 1;
对于 next[7],观察 6 和 next[6] = 1,即 a 和 b,不相等,next[next[6]] = 0,0 和 6相等,因此 next[7] = next[next[6]] + 1 = next[1] + 1 = 1;
代码实现
#include<bits/stdc++.h>
using namespace std;
class KMP {
private:
string target;
string str;
int m;
int n;
int* next;
void get_next(){
// 用next数组来并保存下次开始匹配的位置
next = new int[m];
next[0] = -1;
int i = 0;
int j = -1;
while(i < m){
if(j == -1 || str[i] == str[j]){
i++;
j++;
next[i] =j;
}else{
j = next[j];
}
}
}
public:
KMP(string target,string str);
int find();
};
KMP::KMP(string target, string str){
this->target = target;
this->str = str;
this->n = target.size();
this->m = str.size();
get_next();
}
int KMP::find(){
int i = 0;
int j = 0;
while(i<n&&j<m){
if(j==-1||target[i]==str[j]){
i++;
j++;
}else{
j=next[j];
}
}
if(j==m){//遍历完str
return i-m;
}else{
return -1;
}
}
int main(){
KMP test("ababaaacdefababaaabg","ababaaab");
cout<< test.find()<<endl;
}
图片和部分思路借鉴:KMP 算法中的 next 数组