关于KMP算法中next数组的一点理解

最近在看KMP算法相关的内容,一直对next数组的作用和代码求取方式有点混乱。在看了许多博客后,有了一点个人的感悟,希望能给同样有此疑惑的小伙伴提供一个参考思路。

关于next数组

KMP的核心在于求出next数组。然后设置两个指针,一个在我们的源字符串上,我们记为i;一个在要匹配的字符串上,记为j。i从头遍历到尾,每次走1,即使已经匹配了好几个字符,但后续匹配失败了,也不回头,所以他的时间复杂度能在O(n),只走一轮就出结果。

为什么他能不回头,一直固执地向前走1。假设这样一种情况,当源字符串和要匹配的字符串,匹配一个字符,就失败一个字符,就是开头第一个字符就不匹配,一直不匹配下去,这种情况就是i一直往下走,不回头。
而在KMP中,当发现不匹配时,i保持不动,j回溯到已匹配部分的最长公共前后缀长度(next数组中第j - 1位置上的值)的后一位,继续比较,直到j为0(即开头第一个字符就不匹配),i才向后移动。在每一次回溯中,虽然表面上i一直没有动,但以暴力搜索法的视角看,i其实一直在移动。

所以next数组的作用就是记录每个位置上,已经匹配了的字符串的最长公共前后缀的长度值。
当发现匹配了好几位后不匹配了,不用从头开始匹配,而是找有没有最长的公共前后缀,有就从前后缀后的一位继续匹配。
为什么能用这样的方式来匹配,为什么这样能不会出错。
我们可以这样想想。当我们使用暴力搜索来匹配的时候,我们已经匹配了好几个字符后,发现匹配失败了,这时,假如匹配失败的字符前面没有最长公共前后缀。那我们把i一位一位地从开始匹配时的位置移动到匹配失败的字符的位置的过程是多余的,因为必定都是以匹配失败结束。因为没有公共的前后缀。
举个例子:
从“abcdefabcdef”中匹配“abcdeg”。当匹配到第一个”f“的时候,发现不匹配。将i从”a“的位置一位一位地移动到”f”的过程中,再也找不到一个“a”能匹配“abcdeg”中的“a”,即没有“a”这个公共的部分。
再看一个例子:
从“abadefabadef”中匹配“abadeg”。当匹配到第一个”f“的时候,发现不匹配。将i从”a“的位置一位一位地移动到”f”的过程中,虽然能找到一个”a“,但是并没有什么用,因为继续往下我们需要匹配”b“,但这个过程中,我们没办法再找到一个”b“,即”abade”这个传中没有“ab”这个公共的部分。
所以我们才需要找最长公共前后缀,而且注意是前后缀,不是单纯有重复部分就行,必须要是在这个串的最前面和最后面有公共部分。
当我们得到这个公共部分后,一旦我们在匹配的行进过程中发现了不匹配的情况,直接在已经匹配的部分中找到这个最长的公共部分,然后把i跳到这里开始,而公共部分前面的可以看都不看一眼,那些没用,肯定失败。

next的求取

所以求这个next数组,就是KMP的关键所在,拿到这个数组就可以像套公式一样,写出KMP实现。
那这个数组怎么来咧。
比如一个要匹配的字符串“aabaac”,我们一样就可以看出来,next数组为[0, 1, 0, 1, 2, 0]。但怎么用编程语言实现呢,过程还挺抽象的,当时这里卡了我好久,不是难实现,而是我不能理解凭什么就是这样实现。
其实在计算这个数组的每个位置上的值时,又是一次字符串的匹配。把整个字符串“aabaac”当成源字符串,把要计算的位置上的后缀当成要匹配的字符串。例如。当匹配第五个位置上时。源字符串就是“aabaac”,要匹配的字符串就分别时“abaa”,“baa”,“aa”,“a”;然后我们发现,“aa”和“a”是符合要求的,但”aa“最长,所以取”aa“,即2。
所以在求next数组的过程中,后面的求取过程会用到前面的求取结果来辅助求值,即求第五位的值时,next前四位组成的子数组充当了这次字符串匹配中的next数组。

所以你看别人KMP的实现,求next数组的过程和拿到next数组后匹配字符串的过程其实很相像。这两个过程中,对j的回溯其实表面上是j的回溯,实际上也改变了i的实际起作用的位置。这里也就是理解 j = next[j - 1];这个回溯过程的关键。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值