看到站内一篇讲的很明白的文章,本着“能给别人讲明白=自己真正理解”的原则,试着写一篇讲解加深自己的理解
原文:KMP 算法中的 next 数组推导(图解 + 代码实现)_kmp算法next数组-优快云博客
next数组:
对于字符串S中第j个元素,设其前面的元素组成的子串为S',则next[j]等于S’中重合的前缀和后缀的最大长度。
前缀:字符串中包含首字符且不包含尾字符的任意子串
后缀:字符串中包含尾字符且不包含首字符的任意子串
数学表述如下(这是元素下标以1为起始的情况,以0为起始的话都-1就可以了,后面我为了和看的原文一致采用下标0起始)
可以得到next数组值的(除了表示子串和主串失配时子串指针应指向的位置)另一个含义:
如果next[j]=k,则表示j前面有k个元素,可以与S中长度为k的前缀相同。也即下图中浅色部分相同)
https://i-blog.csdnimg.cn/blog_migrate/21533f0366919bc2886456cdce0cd583.png
S[j]=S[k]的情况
这是最简单的情况,如果S[j]=S[next[j]]=S[k],则对于j+1,也满足了上述条件,即next[j+1]=k+1=next[j]+1
S[j]!=S[k]的情况
这种情况最麻烦,也最难以理解。
考察next值的含义,我们可以得到,要求得next[j+1],就是要找一个k1,使得S[k1]=S[j]且next[j]=k1,也即下图中浅蓝色的区域要相同
找到k1=j比较容易,但是保证前面的部分相同有困难。考虑到k=next[j],也即S[0,k-1]=S[j-k,j-1],总能在k前面0找到一段长度为k1的部分,使得这部分可以与j前面长度为k1的部分重合(即下图的2和3,注意,此时2,3长度都小于k)。此时问题就转化为了找到一个k1使得1,2相同。
很显然,根据next数组的意义,此时k1=next[k]=next[next[j]]。 问题又回到了上面这样的形式。如此一层层递归下去。要么能找到符合条件的k,要么一直找不到。由于k是“顺着”next的值回溯,而next[0]=-1,next[1]=0是确定的。如果一直找不到的话,最后k势必会回溯到-1,这表示j+1前面已经不存在能够重合的前后缀了,next[j+1]=0
代码部分
void getNext(const string& s, int next[]) {
int len = s.size();
int j = 0; // 表示当前要求的 next[i]
next[0] = -1;
int k = -1; // 记录上一个 next
while (j < len - 1) {
if (k == -1 || s[j] == s[k]) {
next[++j] = ++k;
} else {
k = next[k];
}
}
}
用一个变量k来维护next的值。next[0]=-1是确定的。当s[next[j]]=s[k]=s[j]时,更新next[j+1]=k+1,否则,k顺着next值回溯,直到找到s[k]=s[j]或者到达边界条件k=-1,由于到达边界条件时,k=-1,next[j+1]=0=k+1,且初始条件为j=0,k=-1,下一步j=1时next[1]=0也是确定的。所以这几种情况可以合并。