抛开书本讲KMP,跟着思路你就会了,一位考研er的顿悟

抛开各种资料书上冗余的知识,一起去探索发现的过程

现在将你记住的全部的KMP内容都清空掉,跟着思路来一遍。

首先开始的是暴力匹配算法

如果你知道,直接跳过即可
现在假设我们有两个名字分别为P(长度为n)和Q(长度为m)的字符串(n>m),要求从P中找到Q。
首先我们每个人都能想到简单暴力匹配方法,即从每一个字母开始匹配,直到找到或者找不到。我用一个手写的图来表示过程。
在这里插入图片描述
即假设现在是第k个字母,如果从当前字母开始无法匹配成功,则再次从第k+1个字母开始。匹配时,在从第K个字母开始匹配时,此时i=k,j=1,匹配过程中i++,j++。假设匹配不成功,这个时候i要倒退到k+1,而j也要倒退到1。这个过程的时间复杂度为O(nm)。
当然我们可以去掉k这个变量,通过下标运算获得i的回溯的位置,但是这对于之后KMP算法的出现没啥意义,我们就先不考虑了。

接下来开始KMP的思路篇

简化KMP

现在我们思考这样一个问题,怎么简化这个过程呢?

如果在匹配的过程中我们控制其中一个变量不回溯一直往后走,我们就能降低复杂度,如果能找到两个变量都不回溯的方法的话就更好了,但是现在还是一步一步的来,先看一个变量不回溯。对于一个长的字符串,一个短的字符串,显然我们让长的那个字符串不回溯看起来更简单一点,因为即使短的回溯,由于它的长度较短,在回溯的时候也可以减少我们的计算量,好了,现在我们确定了,即使在匹配过程中发生不匹配的现象的时候,我们也不让i回溯,通过j回溯,来达到匹配的目的。

显然我们这个时候不再需要k变量了,因为i不会回溯,我们也不需要知道匹配的初始位置,但是为了引出KMP方便,我们保留这个变量,仅仅作为一个起始位置的标记。

这个时候会出现什么情况呢?
假设现在有这样两个字符串(这个例子比较明显):

P:  cabababcd
Q:  ababcd

显然k为1时不能匹配,所以我们从k等于2的时候开始看。此时:

P: cabababcd
Q:  ababcd

如上,前四个匹配成功了,但是第五个P中是a,Q是c匹配失败,现在我们脑子里就是有一个观念 “ i不能动!!! ”,我们只能让j回溯去进行匹配,但是j回溯多少是个问题,j能回溯到1吗?很显然不能!因为如果j回溯到1的时候,会导致原本可以匹配的字符串最后无法匹配,此时我们发现j回溯的位置不是越小越好,那会不会是越大越好呢?j回溯的越大,意味着我们可以更少的比较,显然对于时间复杂度来说是更有利的。而对于匹配来说,我们只需要比较更少的字符,也能在匹配上获得的效果。

那么…j的值回溯到多少是最好的呢???!!!

接下来我们引入了一个next数组,我们使用这个数组去保存j在当前位置上时,接下来应该回溯的点的位置。我把上面的例子复制过来。

P: cabababcd
Q:  ababcd

当前j=5,那么j应该回溯到多少呢?我们观察从j=1到j=5这一段字符串,即abab, 我们知道这是匹配成功的,即从k=2开始的p中的同样长度的字符串也是abab,这两个是相同的!!!虽然很显而易见,但是你一定要注意,正是因为他们是相同的,所以我们可以通过前后缀的方式去得到一个最优的回溯的位置。我们把这一段单独摘出来看:

P:abab
Q:abab

显然对于这个字符串来说,abab的前缀有{a, ab, aba}.后缀有{b, ab, bab}。即最长的相等的前后缀是ab,我们为啥要得到这个前后缀呢?最关键的地方来了。现在我们摘出来P的后缀和Q的前缀,其实j回溯的位置最好的是啥时候嘞,就是当P的后缀等于Q的前缀的时候,此时Q的最多的字符可以不用再去匹配,而且此时的i也不用回溯,因为P的后一部分跟Q的前一部分是相同的,i只需要继续i++就可以去继续完成匹配了。

故此时我们只需要将这两个字串的位置变成这样:

P:abab
Q:  abab

则原先的P和Q就会变成这样:

P: cabababcd
Q:    ababcd

注意此时i的位置没有变,j的位置变成j=3即可。我们就可以继续i++和j++进行匹配了。

你可以自己试试下面这个例子:

P:aaaab
Q:aaab

那么怎么求next数组呢???
通过上面的分析,我们知道,next即如果到当前位置的元素发生不匹配时,我们要让j回溯的位置,我们依旧使用最开始的Q字符串:

Q:ababcd

现在我们开始看哈:

假设第一个匹配了,第二个就不匹配了
这个时候那两个字符串的相同部分是这样的:

P:a
Q:a

假设前两个匹配成功,第三个不匹配了,这个时候是这样的:

P:ab
Q:ab

假设前三个匹配成功,第四个不匹配了,这个时候是这样的:

P:aba
Q:aba

发现没发现没!!!

其实就是Q的前几个字符,根本就不用看P。

还是这个Q字符串。

Q:ababcd

当第一个就不匹配的时候,例如此时的P中的字符是b,我们怎么办,没办法,因为Q已经是最开始的了,我们不能再回溯了,换句话说,当P以该字母开始匹配的话无论如何都不能与Q相匹配,那么没办法,P的指针往后挪就完了,即i++;j还是1。
当第二个不匹配的时候嘞,我还是复制过来:

P:a
Q:a

第二个不匹配了,我们有两种可以考虑的情况,即i不动,j跑到第一个位置,看看Q中第一个位置的字符跟P中当前位置的字符是不是匹配,要是匹配的话就i++,j++继续匹配就完了,但是如果不匹配嘞,又回到了第一个字符就不匹配的时候了呗,那i++吧。但是注意这个判断顺序是不能变的。

现在第三个不匹配了,这个时候是这样的:(这是个重点)

P:ab
Q:ab

我们现在有三种情况可以考虑了

  1. j回溯1个到达j=2
  2. 如果j=2时P和Q不匹配,那么j回溯2个到达j=1
  3. j回溯到j=1时还不匹配的话,那就i++就完了

这就两种情况,有的人可能会说加个if,else就完了,但是继续看

现在第四个不匹配了,这个时候是这样的:(重点来了)

P:aba
Q:aba

我们现在有四种情况可以考虑了

  1. j回溯1个到达j=3
  2. j回溯2个到达j=2
  3. j回溯3个到达j=1
  4. j回溯到j=1时还不匹配的话,那就i++就完了

好了,现在j回溯的位置有三种情况了,我们也可以加if,else,但是之后j回溯的位置的情况会越来越多,而我们就不可能去进行特判了,现在我们考虑一下如何快速得到当前位置j应该去哪呢??
有的同学肯定还记着前面我们是怎么判断j应该去哪的方法,对,没错,就是前后缀,我们现在直接看第四个不匹配的情况,假设P和Q是这样的:

P:abab....
Q:abac

(P后面的元素就不重要了,我们只需要根据当前位置确定j的值就好了)。
对于字符串aba来说,最长的前后缀就是a,长度为1,而且此时i=4(指向元素为b),j=4(指向元素为c),我们要根据前后缀获取j的位置的话很明显应该让j=2,此时变成:

P:abab....
Q:  abac

有的人会说,前后缀长度不是1嘛,你为啥让他变成2呢,其实这个原因我也想过,我们这个数组的下标是从1开始的,所以我们让最长相等前后缀长度加一就完了。
好了next数组的值就可以求完了。
我们举个例子来看:

Q: ababcd

在这里插入图片描述
注意当index=1时,为什么让next=0呢?其实还是因为我们这个下标是从1开始的,所以0代表啥也没有,作为一个特殊标记,同时也有利于后面的值的设置,这个时候j想说:我已经尽力了,实在是无法跟你匹配了,你就往后跑吧。如果我们下标是从0开始的话,这个表格就会这样:
在这里插入图片描述
这就跟最长的相等的前后缀是一样的了。

有的人发现为啥这个表格还空出来一个nextval列嘞,这就涉及到了kmp的优化了,我们其实在匹配的时候可能会发生j多次回溯的情况。

KMP的优化思路

当前位置不匹配了,j回溯一次,此时跟这个值还是不匹配,j需要再次回溯。

用我上面的那个样例:
在这里插入图片描述
假设在j=3的时候发生了不匹配,我们发现此时next[3]=1,所以我们让j等于1,但是此时我们发现,Q[1] = Q[3] = a, 所以这次回溯其实没有意义,回溯过来还是不匹配,所以我们是不是可以通过某个处理让next[3] 直接等于 0 就完了呗,直接就i++算了。这就是nextval数组需要做的。
就是判断如果value是一样的话就直接把前面的next复制给后面那个。我们同现往后扫描一遍就可以实现了。
如果next数组已知的话可以这样:
if (Q[next[j]] == Q[j]) nextval[j] = next[next[j]];
else nextval[j] = next[j];
如果next数组未知,可以在求next数组时加上特判直接求得nextval数组。

考研复习中,码字码累了。先不贴代码了,从大二上学期开始接触的kmp,现在才感觉真正懂了。惭愧惭愧!!分享出来大家一起学习。

原创不易,侵权必究

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JdiLfc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值