本人被\(KMP\)已经折磨许久。五战KMP。方知之前理解确实浅。故写此篇。
这是之前那篇,实在是太浅,不过对代码做了注释。https://www.acwing.com/solution/content/131255/
本篇重点说明\(KMP\)的原理,而非过程。过程相信其他博客已经写的十分完善了。
\(KMP\) 算法(\(Knuth-Morris-Pratt\) 算法)是一个著名的字符串匹配算法,效率很高,但是确实有点复杂。
暴力及举例可以去看别的博客。本处不赘述。
匹配
观察暴力,相信也知道,每次我们漏掉了些信息。什么信息呢?
假设当前到这就匹配不成功了。
暴力做法:
直接从头开始匹配。
假设移动如这样:
不断向右移动。。。发现了什么?
好像能不能匹配和最上面串没关系吧。由于前半段已经匹配啦,上面等于下面)
更准确的讲,是不是只跟串的前后缀有关系。
因此,我们考虑至少向后移动到什么位置可以继续匹配上面绿圈呢
假设到这。
黄色部分相等。至少移动多少呢?就是使前后缀相等的最大长度。(前后缀不等无法匹配, 又因为至少)
假设我们当前已经求出这个东西。叫做next。
那就容易了。如果蓝圈红圈不匹配,考虑回退,那就回退到next就行了。中间隔下的一定不行,我们知道前后缀不等的,那么一定没法匹配。如果next不行,那就重复这个过程。
代码
\(Next\)
求使前后缀相等的最大长度。略变一下。
强调一下:\(next[i]\) 存储的就是使子串 s[0…i] 有最长相等前后缀的前缀的最后一位的下标。
\(next\) 数组的含义就是当 j + 1 位失配时,j 应该回退到的位置。
首先,假设现在j在的位置是\(next[i - 1]\)的位置,next[1 ~ i-1]已经求出。
观察\(next[i]\)的性质:
- 使1~i前后缀相等的最大长度 <= 1~ i-1前后缀相等的最大长度+1。这是因为你可以画一下图,如果更大,那就一定不等(1~i-1前后缀已最大),最多扩展i,j+1嘛。
- 其实还是和匹配一样了吧。next[i]肯定要在带上i==j+1的前后缀相等的长度 取最大, 按照暴力思路,你当然会从大到小枚举所有前后缀长度,取第一个行的。实质就是不断右移的过程。
当si!=sj+1时,考虑后退,这个时候又在向右移动了。使前后缀相等,那么回退取值一定只有j,next[j],next[next[j]]....
这是因为你可以任取不在其中的一个数k,那么前后缀就不等了,根本没法匹配。(根据next定义,不在这些不断“最大前后缀相等长度”中,那肯定不等啊)
匹配成功后,j=next[i],重复上述过程。可不就求出来了?
好了那这样就愉快解决了。