参考文章——卡尔大佬的B站视频,个人觉得部分讲解不够细致,作补充说明:我会按照代码将流程走一遍,把我觉得难理解的地方用自己的方式解释一遍,不恰当的地方欢迎指正。
void getNext(int* next, const string& s){
int j = 0;
next[0] = 0;
for(int i = 1; i < s.size(); i++) { // 注意i从1开始
while (j >= 0 && s[i] != s[j]) { // 前后缀不相同了
j = next[j - 1]; // 向前回退
}
if (s[i] == s[j]) { // 找到相同的前后缀
j++;
}
next[i] = j; // 将j(前缀的长度)赋给next[i]
}
}
1. 首先定义:i —— 前缀表的尾; j —— 前缀表的前缀的尾; next[0] 默认是为0
这三点很简单理解。
2. 开始循环:前缀表为【a,a】当前缀a与后缀a相同,i和j都后移一位,与此同时会更新next
*需要注意的第一点就是j的后移是需要我们自己写的j++,然后跳出本轮循环,i是在循环中后移
3. i后移之后,又因为i代表前缀表的末尾,所以此时的前缀表为【a,a,b】,我们的目的是寻找该表中的最长相同前后缀。此时比较的是【a,a】和【a,b】:
4. while循环中,此时s[i] != s[j],所以j要回退,回退到:前一位的最长相同前后缀(此时为0),从而比较更小的前缀【a】和后缀【b】:
*这一步回退我觉得是这部分代码的精髓,后面我将详细解释。
5. 此时j已经退到了下标为0的位置,不再进入while,更新next,再进入下一轮for循环:i++
6. 进入循环,判断值相同,则i和j都前移一位,并且更新next填入j(j++ 后为1)
7. 重复6,
8. 判断i和j不同,开始进入while循环,选择最长相同前后缀,
目前是【a,a,b】和【a,a,f】,
9. 开始回退,这一步回退我觉得比较难理解,实现的操作是【a,a】和【a,f】这两个前后缀作比较,但是代码比较巧妙,并不是简单的将j后退一格而已,而是“ j = next[ j - 1 ] ”,j跳转到了前一位的最长相同前后缀值,也就是本例中下标为1的位置。
我加一个例子,如果是下图,就不是回退一格比较【a,b】和【b,f】了,而是j直接跳转到下标为0的位置上。从而next数组赋下一个值为0.
最长相同前后缀意味着与前缀相同元素的个数,也就是可以跳过的个数。
我们在之前的循环中已经走到了比较【aba】和【abf】的地步,意味着前面两位肯定都是对应的上的。此时表【a,b】中的这个b在next中为0,意味着和之前位置没有相同的值,也就是后面表【b,f】的b也和前表【a,b】中b的之前位置没有相同的值,而后面【b,f】中的b刚好是要和前面【a,b】中的a位置是对上的,所以前面一位都不同了,f说我还比个p啊,直接给我个0算了呗。
再加个例子,若是把f换成a:【a,a,b,a,a,a】,最后的a说,既然前面的都相同了,那我就有了比较的意义,要是再相同就加1,更新next。不同就和我们最初的例子一样,再次让j回退。
最后代码上需要注意的是,虽然我们习惯性的先判断相同情况,判断不同情况。
但是代码中while要在if的前面,因为一旦先判断了相等的情况,j就会前移一位,从而影响了while中j的指向,造成错误。