之前笔者通过代码随想录学习了kmp算法,但是一直对于kmp算法next的数组求解过程并不是特别清楚,这几天我复习KMP算法时对next数组的求解有了一些自己的理解。于是想写一篇博客来发表一下自己的看法,希望能帮助大家更好的理解KMP算法。
(如果之前对于kmp算法不太了解的同学可以先点击下面的链接学习kmp算法的思想)
如果现在你已经了解了KMP算法的思想
现在我重点阐述如何求解next数组
代码如下所示
void dealnext(std::vector<int>& next,std::string &target) {
next[0] = 0;
int j = 0;//j为指向前缀末尾位置
for (int i = 1; i < target.size(); i++) {
if (target[i] == target[j]) {//如果前缀最后一个字符和后缀最后一个字符相等
j++;
next[i] = j;
continue;
}
while (j > 0 && target[i] != target[j]) {
j = next[j - 1];
}
if (target[i] == target[j]) {
j++;
}
next[i] = j;
}
}
求解next是一个动态规划的过程
初始值
next[i]代表的字符字串(target.substr(target.begin(),target.begin()+i))前后缀最长相等长度
动态规划的初始化即为next[0]=0;(target[0]的最长相等前后缀长度为0)
我们默认i为指向后缀末尾的位置 j为指向前缀末尾的位置
用i进行循环,i从1开始(next[0]已经初始化了)
j初始化为0,表示为next[0]的前后缀最长相等长度为0;
循环去给next[i]赋值
我重点讲解 循环体内的动态规划思路:
j如果不为0;
那么是说明next[i-1]的不为0;
next[i-1]的值为j;
那么即[0,i]的字符字串的最长相等前后缀和为j;
那么target[j]即为第j+1个字符
如果target[j]==tagret[i];
那么对于next[i]就等于next[i-1]+1(即为j+1);
j自增是为了表示进入下一个循环(i++)后next[i-1]=j;
如果target[j]!=target[i]
我们已经得到了next[i-1]的值为j(假设此时j>0)
即以i-1结尾的字串前后缀相等的字串最大长度为j;
那么这个最长的相等前后缀的前缀字串即为target.substr(target.begin(),tagrget.bebgin()+j)
我们已经得到这字符串子的最长前后缀相等长度了,(i的值肯定要大于next[i]的)大小为next[j-1]; 所以我们就再判断str[next[j-1]]是否等于str[i]
如果相等next[i]=next[j-1]+1;
如果不等 ,那么继续向前跳,
跳的终止条件即为x的大小为0;
x的大小为0了说明next[i-1]没有相等前缀和,此时是让target[0]与taget[i]进行匹配, 果不匹配,那么next[i]=0;i++,继续向后判断next[i]
总结
我们动态规划求next数组时,如果str[i]=target[j]那么很好理解,j++,next[i]++;
如果str[i]!=target[j]那么我们既要利用前面的求取的next数组,复用了kmp算法的字符串匹配思想,然后找到最长相等前后缀长度
KMP算法笔者的模拟实现:
namespace zjb {
class KMP {
int Kmp(std::string str,std::string target){
if (target == "") {
return 0;
}
std::vector<int>next(target.size(),0);
dealnext(next, target);
int index = 0;
for (int i = 0; i < str.size(); i++) {
if (index >= target.size()) {
return i - index;
}
if (str[i] == target[index]) {
index++;
continue;
}
while(str[i]!=target[index]&&index!=0) {
index = next[index - 1];
}
if (str[i]==target[index]) {
index++;
}
}
if(index==target.size()){
return str.size()-index;
}
return -1;
}
void dealnext(std::vector<int>& next,std::string &target) {
//next[i]代表的是以i为下标的前后缀最长相等长度
next[0] = 0;
int j = 0;//j为指向前缀末尾位置
for (int i = 1; i < target.size(); i++) {
if (target[i] == target[j]) {//如果前缀最后一个字符和后缀最后一个字符相等
j++;
next[i] = j;
continue;
}
while (j > 0 && target[i] != target[j]) {
j = next[j - 1];
}
if (target[i] == target[j]) {
j++;
}
next[i] = j;
}
}
};
}