KMP算法

KMP算法

KMP算法作为字符串匹配的经典算法,对于许多同我一样的小白来说理解有点困难。这一次谈一谈我个人对于KMP算法的理解。

要想理解这个算法,我们不妨先想想我们最面对字符串匹配问题最简单的思路是什么样的:
我们最普通的思路就是对主串(即要进行查找的字符串)进行遍历,当检测到一个字符与模式串(即你要查找的字符序列)第一个元素相同时,再不断进行遍历直至找到一个与模式串不匹配的字符,然后进行回退,回退到之前第一个匹配字符的后一位继续重复之前操作,知道匹配成功或者主串遍历到结尾。

这是我们最正常最简单的思路。我们不妨思考一下这个思路的优点与缺点在哪里。优点不用说,自然就是不费脑子和头发。那么缺点呢?我们知道,对于这种匹配问题,主串无论如何都是至少要整个遍历一次的,也就是说,可以进行优化的部分在“回退”过程中。

我们可以举一个例子:
模式串T[]: A B A B A A
匹配串S[]:XXXXX A B A B A B XXXXX

当我们匹配到模式串的T[5]元素时,匹配会失败,这时候我们需要进行模式串和匹配串的回溯。一般的想法就是我们的匹配串要回到之前开始匹配的后一个位置,而模式串则要返回到首位重新匹配,那么这样真的是必须的吗?观察这个例子我们不难发现:由于前五个元素的成功匹配,那么匹配串中这一部分子串其实就相当于模式串的对应部分的子串。而如果我们按照“朴素”算法的方法进行回溯,当匹配串划到第二A的位置时,我们是可以完全预见到它前面几个元素能够匹配成功了。为什么呢?之前已经匹配成功的部分中,它们里面有两个“ABA”的子串!这样一个其实就是子串的子串的字符串,\

在这里插入图片描述
由于“ABA”是公共子串,观察两个“ABA”子串的位置,我们其实早就知道当匹配串指向该区域第二个B这里时,它的前三位能够匹配成功,实际上我们只要从匹配串的最后一个B的位置,模式串中第二个B的位置开始比较就可以了:

在这里插入图片描述

这种简化思想当然是可行的,但是如何用计算机去模拟我们的思想,并且让这种方法适用于所有的字符串匹配呢?方法就是KMP的核心思想了。

要模拟这种思想,首先我们要记录模式串里这样的“重复子串”以及它们各自的位置。事实上,子串的内容我们是并不关心的,我们只需要知道“它们”肯定能够匹配就好,况且公共子串那么多,我们要想记录内容而去花费大量空间,这种思想也就无法体现他的优越性了。KMP算法应用了一个next[]数组。这个数组是干什么用的呢?根据KMP算法的描述,它作为一个int型的数组每一个索引对应元素的意义是指:**在以这个索引位置为结尾的字符串下,这个字符串中最大公共前后缀子串的长度。**那么什么是前后缀公共子串呢?前缀是指最后一个元素前面部分所有子串的集合,比如字符串ABAB,它的前缀就是[ABA,AB,B],后缀就是第一个元素后面部分所有子串的集合,即[BAB,AB,B],而它的最大公共前后缀子串就是AB,长度即为2,这里提到的ABAB,就可以当作成一个以索引[3]为结尾所构成的字符串,即next[3] = 2。记录这个的意义是什么呢?我们不妨看看KMP算法拿这个数组做些什么。
我们假设,当这次匹配失败时,匹配串的指针不动,模式串的“指针”要回溯到位置k。而k = next[j-1] j代表匹配失败时模式串指针所指的位置。为什么要回到这里呢?这与我们之前的思想又有什么关联呢?我们不妨对照一下之前举的例子,发现按照这种算法竟然是吻合的。我们知道,j所指位置之前,匹配都是成功的,换句话说,next[j-1]对应的字符串就是已经匹配成功的字符串。我们可以通过定义算出next[j-1] = 3,即下一次模式串指针所指位置。我们仔细思考一下可以一下就明白:我们要规避的是对重复子串的多余匹配,而这个数组记录公共子串的长度即对应了重复子串的长度,而这又代表了位置,因为公共子串无论如何都是以第一个元素为开头的,根据数组下标以0开始的性质,用长度就可以直接定位到下一次要检测位置(假如数组以1为开头,那么定位的位置就是 长度+1 了)。

我们再思考一下这种算法的正确性。假如这个部分没有公共子串,即k = 0,那么我们的模式串指针就要回到开头再进行匹配了,毫无疑问这是正确的。其余的例子我们可以自己再尝试一下,但无论如何,这种算法对于模式串指针的优化是十分天才的。事实上,这样的推论过程更像是逆推,所以其中仍有许多细节是值得我们去仔细研究推敲的。

在我个人看来,整个算法的精妙之处是在这个最大公共前后缀子串的性质上。我无法考证这个定义是不是在他们三个卓越工程师想出这个算法之前就有的,就算是,我相信他们一定也从中得到启发过。正如我们上文提到的那样,最大公共前后缀子串的第一个元素一定是整个字符串的开头,而子串的最后一个元素一定是整个字符串的结尾,虽然它们俩并不在同一个子串。也正如我们上文提到的那样,为什么我们得到最大公共前后缀子串的长度以后就可以直接用这个长度代表下一次模式串开始匹配的位置,正是由第一个元素是整个字符串的开头带来的效果。而为什么匹配串的指针可以不动呢?这则是由最后一个元素一定是整个字符串的结尾带来的效果。换句话说,上一次匹配串匹配成功的部分中,末端的最大公共前后缀子串一定会与模式串指针前那头部的最大公共前后缀子串匹配成功,我们不需要再去一一匹配了!而这正是我们的简化思路。诚然,一个字符串的公共前后缀字串不一定只有一对,所以,我们的要求是最大公共前后缀子串,较小的那些子串会被涵盖于其中,我们不需要再考虑。

本文参考的是另外一篇文章:https://blog.csdn.net/helloworldchina/article/details/104465772

第一次写博客,其中不足之处欢迎大家理性讨论。
感谢!

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值