浅谈 KMP 算法
浅谈 KMP 算法
真的是浅谈。
前言
众所周知,字符串算法的种种操作都是在已经得到的数据的基础上对于以后的求解过程进行优化的过程,那么 KMP 算法也有异曲同工之妙。他的功能是在 \(O(n)\) 的优秀时间复杂度内完成两个字符串的匹配问题,本文所要讲解的就是 KMP 算法的原理以及使用方式。
参考文献
个人认为以下两位大佬的文章写的非常好,本人就是从中学会了 KMP 算法这一并不是很简单的算法。
SYC大佬 和 yyb大佬
思维过程
对于两个普通的串,我们可以在 \(O(n^2)\) 的时间复杂度内轻松求出一个字符串对于另一个字符串的匹配,具体的实施方式是,只要一个串在某一个位置失配了的话,那么头指针就往前面走一格,然后再继续匹配,直到匹配成功。(具体图示可以看 SYC大佬 的博客,其中有具体的gif图示解释)
但是我们发现,如果说一个串有很大一块都被访问过了,那么就是说这两个串之间还是有很多共同点的,那么这个时候我们就可以优化这个过程。
我们引入两个概念,字符串的前缀和后缀。(其实我的 后缀数组 学的比 KMP 早)
前缀:表示一个字符串从一开始到当前位置的子串。
后缀:表示一个字符串从当前位置到结尾的子串。
有了这两个概念之后我们就可以开始理解 KMP 算法的具体应用了。
不用管怎么才能思考到 KMP 算法的实质,我假设你之前从没有听说过 KMP 算法,有一天你突然醒悟了这个算法的实质,现在你只是想要实现这个算法而已。
对于一个重复的前缀,很明显我们需要一个数组去存储这个前缀,这就是我们下面要提到的 nxt 数组。
这个数组可以帮助我们快速的跳跃到前面重复的前缀的位置,然后迅速地得到我们需要跳到的位置。
如果说 KMP 算法真的十分难以理解其本质的话,那么我相信 KMP 的代码不会让你觉得很困惑,因为他的实质真的只有不超过十行的代码。
nxt[0] = nxt[1] = 0;
for(int i = 2, j = 0; i <= len2; i++) {
while(j && s2[j + 1] != s2[i]) j = nxt[j];
if(s2[j + 1] == s2[i]) ++j;nxt[i] = j;
}
for(int i = 1,j = 0; i <= len1; i++) {
while(j && s2[j + 1] != s1[i]) j = nxt[j];
if(s2[j + 1] == s1[i]) j++;
if(j == len2) printf("%d\n",i - len2 + 1),j = nxt[j];
}
我们需要一行一行看这个代码的实现,因为我是个务实的人。
第一行:表达 nxt 数组的边界情况。(注意,我的字符串是从1开始遍历的,并不是从0)
第二行:由于第一个字符串根本不需要处理 nxt 数组,所以从第二个开始处理,如果你第一个都失配了,还有什么理由继续留在这个位置。j 表示当前字符所连续的最长前缀其所在的位置(可能有点拗口,但是如果看了两位大佬的图示解释就很容易理解了)。
第三行:如果说 j 不等于 0 且当前匹配到的位置并不和 j 指向的前缀的下一个相同,那么 j 就往前跳。
第四行:这里的意思是如果说是因为匹配成功而跳出的话,那么 j 就可以 ++ 了,如果说 j 没有的话, 那么 j 将会跳到 0,所以此时 nxt 就等于 j。
第七行:与第三行的解释差不多。
第八行:与第四行的解释差不多。
第九行:如果说当前位置已经是最后一个了,那么就可以输出这个位置了,我这个地方是因为题目限制所以说搞到了字符串首, 这里的输出视题目的具体内容而定。
以上就是 KMP 算法的全部内容。

浙公网安备 33010602011771号