字符串匹配算法的思考

最近刚好回看了一下KMP算法,对KMP算法有了更清晰的理解,于是在这里记录一下,将自己的理解记录归纳。

前置

待搜索串: Sn=S0S1S2...Sn。如:abbaabbaaba
待匹配串:Pm=P0P1P2...Pn。如:abbaaba

从暴力匹配讲起

暴力匹配: 对 待搜索串每个位置,都进行待匹配串的搜索,当前位置不匹配,则到下一个位置进行查找。即,从S0开始查找Pm,查不到则在S1再查Pm,直到 S(n-m)的位置Pm每次都从P0开始匹配。
最差时间复杂度:遍历 n-m 的位置,每个位置进行 m 次比较。 即, (n-m) * m
例如: Sn= aaaaaaaaab,Pm=aab
说明:暴力匹配思路比较清晰简单,因此代码实现也比较简单,在 子串很小的时候是有用的,例如m=1(即查询单个字符)。

优化方向:减少已经做过的事。如:走过的S位置不要再走(在前部分匹配时)即不回溯S,匹配过的不要每次都从P0位置开始匹配。

第一次优化----不回溯S

在暴力匹配中,如 Sn= aaaaaaaaab,Pm=aab,可以发现,一开始从S的位置,每次匹配完,匹配到P的最后一个位置才不匹配。此时假设S的位置为x,S已经走了x+m了,但是还是会回到x+1处重新匹配(即发生了回溯)。
这是一个浪费的地方,P越长,前面匹配得越多越浪费。
假设P已经匹配的位置为y ,我们能不能从 S的x+y处位置接下去比较,即S不再发生回退。
顺着S不发生回退的方向,就可以进行第一次的优化。

考虑一下:其实,S 从 x+y 位置的字符是什么我们已经知道了的即子串P的0到y的位置,所以S即使不回退,我们也有了 S(x+y)的知识,我们接下来就应该利用这些知识让S不回退。

再考虑一下:假设S从x+1的位置开始匹配了P,最后还是需要比较Sx+y的位置的。
那么此时,匹配S(x+y)应该与P(y-1)进行比较,那么,我们可以先进行 S(x+y)与P(y-1) 的比较
如果 S(x+y)与P(y-1) 不匹配,那么 S(x+1)的位置已经不可能匹配子串(因为最后一个字符不等),
同样的方式判定S(x+2),S的x到y的位置都可以这样子判定,如果匹配,就要进行另外的展开判断。
如果 S(x+y)与P(y-1)匹配,如果我们能通过已有的条件进行验证S(x+1)到S(x+y)和P(0)到P(y-1)接下来是否匹配,那么,我们就确实不再需要进行 S的回退了。
那么,我们能够验证吗?还知道我们前面考虑吗,我们是拥有 Sx到S(x+y)的知识的。即接下来要知道的Sx到S(x+y)的知识我们都有,所以我们是可以验证。
为了便于理解,我们假设n=10,m=5,x=0,y=4,由于我们不回退,假设S1是可能是合法匹配的开始(即x=1),并验证了 S4=P3,那么如果 S1S2S3=P0P1P2,即 可得 S1S2S3S4=P0P1P2P3
而验证 S1S2S3=P0P1P2 是否正确,我们是有办法的,因为我们上一次匹配的知识里面已经知道了S1S2S3 (即 P1P2P3)

那么如果这一段确实匹配,我们就可以接下去比较S5P4了,而这部分是属于常规的必须的比较操作了。

如果不匹配,那么就是接下去的尝试了,即x=2时的情况,直到尝试完。最后确实不匹配,S的位置为S(x+y+1)P的位置从0开始。

通过这个验证,我们将不用再回退S,进行了一次优化,虽然现在还需要通过反复比较才能确定不回溯,但是将 S与 P 进行了分离(验证前面的部分是否匹配不再与S有关)。

第二次优化----子串验证加速

由于第一次优化我们已经把 S 和 P 进行了分离(验证前面的部分是否匹配不再与S有关)(后半部分的常规比较是否匹配这一部分是没法少的,不是优化的方向),所以,接下来的优化也与S无关。
在我们操作的过程中,我们发现,可能要重复验证 P 某个范围的值是否相等。
例如:还是上面的例子。由于上一次匹配的x=0,y=4, 可得: S0S1S2S3 = P0P1P2P3,即可得 S1S2S3 = P1P2P3。 而当前要从x=1匹配要验证 S1S2S3=P0P1P2 是否正确。 即 S1S2S3=P1P2P3 = P0P1P2
那么,下次y为另外值的时候,又用到这样的比较,即可能 P1P2P3=P0P1P2经常在重复比较,并且有些比较是多余的,因为我是可以 提前 知道 P1P2P3 !=P0P1P2的。
所以我们可以
提前处理P
,产生一个位置对照表从0到m的最长重复串位置,我们后面再说一下这个对照表怎么产生,现在先跳过)。
这样,下次就可以从y位置 通过对照表得到另一个位置 y'直接比较 S(x+y)与S(y’) 就可以直接知道前面是否匹配,而不用P(y-1)一次次比较,如果不匹配,则y’ 可能有 y’’ ,如果没有,那么说明前面都不可能匹配,进入后续S(x+y+1)常规流程。这样,通过预处理对照表,大大减少了子串的比较

对照表的产生:我把这个表理解成0-m每个位置从0开始的最长重复子串的位置。即:m=y时,存在i,使 P0....Pi = P(m-i) P(m-i-1)...Py,当然,i不能和y相等,对照表存放的就是每个位置的i值。举几个例子。如下,此处i不存在时记为*
aab 对照表为 *1*
abcabcacab 对照表为 ***0123*01
这里找y位置 i 的值简单直接一点的算法思路

y=0时,*
y>0时,假设 i=y-1,那么 Py-1 =Py,Py-2=Py-1...
假设i=y-2,那么 Py-2=Py,Py-3=Py-1..
最后可以得到 i 的确定值。

通过对照表,我们把子串匹配的验证也进行了优化

KMP算法

KMP算法的基本基本原理即为上面的两次优化。即:
不回溯S,将前部分匹配判断留给子串
用预处理对照表,减少子串判断
空间复杂度O(m) 存放对照表。
时间复杂度O(n+m)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值