KMP算法详解
首先回忆一下我们的朴素式模式匹配,对于朴素式模式匹配算法,他是存在“回溯”的情况的,即发现匹配失败之后,上一次匹配的下一格开始重新匹配,可以发现,这样的算法时间复杂度很高,所以我们来看一下KMP算法
KMP算法相对于朴素式模式匹配算法最大的优点就是主串指针不回溯,只有模式串的指针回溯
下面我们来分析一下:
如果 j = k 时发现匹配失败,说明 1 ~ k - 1都匹配成功
KMP算法代码
int Index_KMP(string s, string T, int next[]){
int i = 1, j = 1;
while(i <= s.size() && j <= T.size()){
if(j == 0 || s[i] == t[i]){
++i;
++j;
}
else
j = next[j];
}
if(j > T.size())
return i - T.size();
else
return 0;
}
上面代码中的next数组解释如下:当模式串的第j个字符匹配失败时,令模式跳到next[j]再继续匹配
求模式串的next数组
串的 前缀:包含第一个字符,且不包含最后一个字符的子串
串的后缀:包含最后一个字符,且不包含第一个字符的子串
当第j个字符匹配失败,由前1~j-1个字符组成的串记为S,则next[j] =S的最长相等前后缀长度+1
例如:模式串为 ‘ababaa’
序号j 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 1 1 2 3 4
再来一个做练习:‘aaaab’
序号j 1 2 3 4 5
模式串 a a a a b
next[j] 0 1 2 3 4
举例说明:比如说求next[3], 即在第三位匹配不对了 那么前面有aa是匹配上的,前缀是a ,后缀是a ,最长相同个数是1 最后再加1 得到next[3] = 2
下面写一下求模式串的next数组的代码
void get_next(string T, int next[]){
int i = 1, j = 0;
next[1] = 0;
while(i < T.size()){
if(j == 0 || T[i] == T[j]){
++i;
++j;
next[i] = j;
}
else
//否则令j = next[j];
}
}
//KMP算法
int Index_KMP(string S,string T){
int i = 1, j = 1;
int next[T.size() + 1];
get_next(T,next);//求模式串的next数组
while(i <= S.size() && j <= T.size()){
if(j == 0 || S[i] == T[i]){
++i;
++j;
}
else
j = next[j];//模式串向右移动
}
if(j > T.size())
return i - T.size();
else
return 0;
}
在上面的算法中,我们可以发现一些缺陷,比如说abbbabbc 与 abbc进行匹配,在发现第四个字母不同的时候,他会做一些无用功,
下面我们对KMP算法进一步进行优化,
利用nextval数组
nextval数组的求法:
先令nextval[1] = 0;
for(int j = 2; j < T.size();j++){
if(T[NEXT[j]] == T[j])
nextval[j] = nextval[next[j]];
else
nextval[j] = next[j];
}
KMP算法优化:当子串子串与模式串不匹配时j = nextval[j]