KMP算法解惑

简介

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。(—百度百科)

KMP是一个字符串匹配算法,由BF(Brute Force,暴力)算法改进而来,各种算法书或者相关博客中都会提及。但是通常是介绍一下公式然后用例子讲解怎么计算,或者使用图解的方式讲述匹配的过程,再引出具体实现代码。实际上看完图解的匹配过程,已经对KMP算法有个直观的认识了。不过在阅读的过程中有些地方会直接告诉结论或者怎么操作,而或略了为什么这样。所以当你看完一篇文章,以为自己搞懂了,想自己实现一个KMP算法的时候,常常会被卡住或者产生为什么要这样做的疑问。

《彻底搞懂KMP算法原理》是笔者看到的博客中讲得比较清楚的一篇,但是阅读过程中个别地方仍然会有疑问,下面就以此篇为基础,对阅读过程中产生的疑问进行解惑。如果读者直接读下面的分析感觉有些突兀可以先点击前面的链接查看。

问题

字符串的前缀和后缀
首先我们需要知道字符串的前缀和后缀:
对于字符串ababc来说,它的前缀有[a,ab,aba,abab],也就是以字符串第一个字符作为开头,同时不包括最后一个字符的所有子串,同理它的后缀有[c,bc,abc,babc],也就是以字符串最后一个字符作为结尾,同时不包括第一个字符的所有字串。
字符串的最长公共前后缀
了解了这个,我们再来说什么是字符串的最长公共前后缀,说白了,也就是前缀和后缀这2个集合中的相同部分,同时取最长的那个,就是这个字符串的最长公共前后缀。显然,在这个例子中,ababc是没有公共前后缀的。但是对于abab,它的前缀和后缀分别是[a,ab,aba]和[b,ab,bab],那么它的最长公共前后缀就是ab。

现在,我们的目标就是取得ababc所有子串[a,ab,aba,abab,ababc]的最长公共前后缀的长度,分别保存在next数组中,我们只要知道最大长度即可,并不用关心串具体是什么,而我们目前通过观察即可得出next数组这里是[0,0,1,2,0],我们先知道这个结果即可,此计算方法后续会说明。

上面是前文中KMP算法获得next数组的思路,后面用图解的方式给出了匹配过程和求解next数组的过程。但是笔者读完有个疑问:为什么文本串和模式串遇到不匹配的字符时,模式串用于比较的指针j移动的位置就是最大公共前后缀的长度。

解惑

这里涉及两个问题:

  1. 移动到公共前后缀的长度的位置;
  2. 最大的

为了说清楚,如下图,我们假设现有文本串a b a b a c a b a b a d c,模式串a b a b a d,用于表示当前比较字符位置的指针i和j都从0开始,相等就都后移一位,直到遇到不等的时候,此时i和j都在索引为5的位置,我们把前面相等的文本串部分称作’Te’,把前面相等的模式串部分称作’Pe’,实际上Te和Pe完全相等,这也是为什么计算next数组的时候只需要使用模式串的原因。
在这里插入图片描述
现在解释j为什么需要移动到公共前后缀长度的位置,我们换一种说法叫公共前后缀对齐,这样更形象一些。先假设只移动一步,这样Te和Pe的前后缀没有对齐,此时i和j左面的部分是不相等的,要想继续比较,只能i回到1的位置,j回到0的位置,这样只移动一步,虽然不会有遗漏的情况,但是效率是比较低的,而且i还发生了回退(KMP算法的一个初衷就是不想让i发生回退),这实际上是BF(暴力)算法的比较方式。
在这里插入图片描述

假如不想i回退,并且还能匹配上,需要i左面的部分和模式串移动之后左边的部分完全相等(假如移动之后前后缀对应的部分还不相等,此时模式串整体匹配的部分肯定不相等),即Te和Pe的前后缀能对齐。此时j所在模式串中的位置索引就等于公共前后缀的长度。下图中公共前后缀的长度为3,而j刚好也在索引为3的位置,这样就可以继续比较。
在这里插入图片描述
现在解释为什么需要最大的公共前后缀。就本例而言,Te和Pe的公共前后缀除了aba还有a,假设直接让公共前后缀a对齐,这样就把aba对齐的情况漏掉了。
在这里插入图片描述
可见Te和Pe最大的公共前后缀对齐时的移动距离是最小的,对于有公共前后缀的情况时模式串如果能完全匹配,则一定发生在公共前后缀对齐的情况下。为了不漏掉可能匹配的情况,需要先让最大公共前后缀对齐。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值