试证KMP计算循环节方法
- 给一个字符串,求最短循环节,如
abcabcabcabc
循环节为abc
- 给一个字符串,求在末尾最少补多少字母后成为一个有循环节的字符串,如
abcabca
需要补充bc
数据结构课程在string章节的实验课,多半会有循环节这道题,或许蒙的猜的或许课堂讲的,大多人会发现 strLen - next[strLen]
就是最短循环节的长度,无论是上述问题1
还是问题2
,都能求出循环节长度为3
,即abc
但总觉得哪里怪怪的,大家都默认这样正确,在网上却不太容易找到一个严谨的证明。这篇博客试着证明一下,当然依然是非数学的,期望更容易读懂。
我们知道KMP算法的next数组是由“最长相等前后缀”推得的,strLen - next[strLen]
其实就是“字符串长度”减去“整串最长相等前后缀长度”后的长度,循环节即这个长度的前缀,这里我们就是要证明:
strLen - next[strLen]
是字符串最短循环节的长度
一个字符串的最长相等前后缀,有两种情况:
情况1
:最长相等前后缀重叠
1 | mmmmmmmmmmmmmm--- |
这里 m 表示相匹配的串
情况2
:不重叠
1 | mmmmm------------ |
对于情况1
和情况2
,又分别有 循环刚好填满 ,和 循环未填满需要补充 的情况。
情况1
最长相等前后缀重叠 且 循环节填满
这里用 [0,k] == [j,l]
表示 0~k
的字符与 j~l
的字符顺序一一匹配,[]
表示闭区间。
显然[0,j) == [j,j+j)
,即最长相等前后缀之间,“前缀的前缀”肯定与“后缀的前缀”相匹配。这里)
表示右侧开区间,用M
来表示这个情况。
1 | MMMmmmmmmmmmmmm--- |
而[j,j+j) == [j+j,j+j+j)
也是成立的,最长相等前后缀之间,前缀的任意一段,等于后缀对应位移的那段,这里用P
来表示
1 | MMMPPPmmmmmmmmm--- |
而显然,后缀的MMM
和前缀的PPP
是同一个区间,都是[j,j+j)
,后缀的PPP
又和前缀的PPP
之后的长度为j
的区间是同一个区间,可以连锁反应下去,直到字符串最后
1 | MMMPPPOOOmmmmmm--- |
情况1
最长相等前后缀重叠 但 循环节 未 填满
可能会是这个样子
1 | mmmmmmmmmmmmm--- |
看起来没什么区别?其实就是长度不能整除这个假定的循环节了。
连锁反应还和刚才一样,最后能得到:
1 | MMMPPPOOOAAAm--- |
到这里,连锁还可以继续,后缀的AAA
对应前缀的那段依然是循环,用BBB
表示
1 | MMMPPPOOOAAABBB- |
最后剩下的这个字符(或若干字符)
- 是后缀的末尾,那必然也与前缀的末尾匹配 ==>
- 现在前缀已经被循环串填满了,所以前缀的末尾必然是循环串的一个开头 ==>
- 则后缀的末尾(也是字符串的末尾)也是循环串的一个开头
情况2
最长相等前后缀 不 重叠 且 循环节填满
循环节填满字符串的话,是不可能出现情况2
的
循环两次时:
1 | AAAA---- |
最长相等前后缀是挨着的,用情况1
的连锁反应即可
循环大于两次时:
1 | AAAACCCC---- |
必然重叠
情况2
最长相等前后缀 不 重叠 但 循环节 未 填满
1 | mmmmm------------ |
按照公式,即总长度减去最长相等前后缀长度,循环节应该是[0,j)
,用M
表示为
1 | MMMMMMMMMMMM----- |
它当作需要补齐的循环节,是符合要求的。但是否是“最小循环节”呢?
反证 一下,假设有更短循环节P
1 | PPPmm------------ |
那么,我们得到的KMP匹配结果,将会是
1 | PPPAAABBBCCCDD--- |
这是情况1
而不是情况2
了,产生矛盾,故不存在更小的循环节P
。
小结
几种情况都已处理完,这样可以更感性地认知strLen - next[strLen]
为循环节长度的意义了。