串的模式匹配——KMP算法理解

模式匹配算法——KMP算法

如图,L为字符串,L1为已经匹配了的字符子串,红色部分表示L1的下一位出现不匹配。假如在已经匹配了的子串中从某个位置开始,出现了新的匹配,即这里的L2。那么下一次对字符串的匹配只需要从L2开始进行匹配就可以了(如果子串中出现新的匹配,它必然在L2中,L2代表这最大的新匹配),而不需要从L1首位置的下一位开始。
在这里插入图片描述
由观察可以发现,L2为L1的后缀,而L2又是新的匹配,说明它必然和L1的前一部分相同,即它也是L1的前缀。因此要定位下次重新匹配的位置,我们只需从找到L1前缀和后缀相同最长的部分的长度。
这里我们引进一个数组去记录字符子串的每一位作为终点时,前面的子串最长公共前后缀长度,即部分匹配值(Partial Match,PM)(如这里L1的最后一个元素,它的最长公共前后缀长度为L2的长度)。当在某个位置出现不匹配时,查找它前一位的PM值,通过将子串向后移动若干位,即可找到新的匹配起始位置。移动公式为:

移动位数 = 已匹配的字符数 - PM值

Move = ( j - 1) - PM [ j - 1 ]

如图中的例子,将子串向后移动(L1 - L2)位,即可与L2重叠。
对于子串‘abcac’,它的PM值如下表:

编号12345
Sabcac
next00010

KPM算法的改进

因为每次不匹配时都要去找前一个元素的PM值,很不方便,因此把每一个PM值向后移动一位。

Move = ( j - 1) - next [ j ]

编号12345
Sabcdc
next-10001

当第一个元素不匹配时,只需向后移动一位,因此不需要计算子串移动的位数。

KPM算法再改进

在每次匹配时都要去计算移动的位数,而实际上在每一个位置需要移动的位数是已知的,我们可以提前算好。已知 j 是最后匹配的字符,它是子串的比较指针。每次不匹配时,只需要将 j 回退若干位,在该位置重新进行匹配。这里新的比较指针的位置为:

j = j - Move = j -(( j - 1) - next [ j ] )= next [ j ] + 1

为了公式更简洁,将next整体+1。
如下表:

编号12345
Sabcdc
next01112

KMP算法的进一步优化

模式‘aaaab’在和主串‘aaabaaaab’进行匹配时(这里的next [ j ] 指的是PM值)

主串aaabaaaab
模式aaaab
j12345
next [ j ]01234
nextval [ j ]00004

在j=4时出现不匹配,子串将向后移动 j - 1 - next [ j - 1 ] 位,即1位,此时第 j - 1位到达了 j 的位置与 b 进行匹配,但因为子串在该位的值与之前的相同,都是a,因此必然导致失配。
聪明的我已经看出了问题所在,也就是当子串后移 j - 1 - next [ j - 1 ] 位时,原先的第next [ j - 1 ] 位到达 j 位,如果原先的第next [ j - 1 ] 位的值与失配时的值一样,必然导致重复失配。重复失配后,我们又要将向后移动 j - 1 - next [ j - 1 ] 位,第二次的第next [ j - 1 ] 位到达 j 位。如此反复重复,直到最终next [ j - 1 ]位的值不等于 j 位的主串的值。
因此这里我们引入了一个新的数组nextval,提前记录重复匹配的终点,即最终的next。
按这个例子,在 j = 4 时发生失配,需要移动的个数不再是1,而是 j - 1 -nextval [ j - 1],即3位。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值