字符串匹配———KMP算法的个人理解

    字符串匹配是计算机的基本任务之一,而Knuth-Morris-Pratt算法(简称KMP)是最常用的之一。它以三个发明者命名,起头的那个K就是著名科学家Donald Knuth之前课堂老师讲过KMP算法的原理,但是半年过去忘的也差不多了,这次重新复习希望以后可以把它记住。话不多说,进入正题。

---------------------------------------------------------------------------------------------------------------------------

    在说KMP算法之前,先说一下BF算法。

    我们给定两个字符串: 主串 “ababcabcabd”     模式串:“abcabd”

    BF算法的匹配如下:

    ——>——>——> ——>——>

      我们可以看到,当匹配开始进行,模式串与主串的前两个字符匹配成功,但第三个字符匹配失败。这时就需要将主串后移,并让模式串回到第一个字符的位置。让模式串的第一个字符从主串本次匹配位置的下一位开始匹配,直到匹配成功或者匹配失败。这就是BF算法的过程。

       我们再来观察主串和模式串的关系,我们看第三张匹配的图片,图片中“abcab”字符都已匹配成功,当匹配到第6个字符‘c’时匹配失败,在BF算法中,它下次进行的操作是将主串从开始匹配位置+1,模式串重置。但是我们看图片就可以知道,通过本次匹配我们已经知道在主串不匹配位置‘c’字符之前字符为“abcab”,其最后两位字符“ab”与模式串前两位“ab”是匹配的,那么我们下次是不是可以不让模式串重置,继续让模式串后移,直接让模式串的第三位‘c’与主串中的‘c’进行匹配,这样就可以少进行两次匹配,这就是KMP算法的思想。

---------------------------------------------------------------------------------------------------------------------------

下面是我在百度百科上面找到的关于KMP算法的介绍:

        KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。       

设主串(下文中我们称作T)为:a b a c a a b a c a b a c a b a a b b
模式串(下文中我们称作W)为:a b a c a b
用暴力算法匹配字符串过程中,我们会把T[0] 跟 W[0] 匹配,如果相同则匹配下一个字符,直到出现不相同的情况,此时我们会丢弃前面的匹配信息,然后把T[1] 跟 W[0]匹配,循环进行,直到主串结束,或者出现匹配成功的情况。这种丢弃前面的匹配信息的方法,极大地降低了匹配效率。
而在KMP算法中,对于每一个模式串我们会事先计算出模式串的内部匹配信息,在匹配失败时最大的移动模式串,以减少匹配次数。
比如,在简单的一次匹配失败后,我们会想将模式串尽量的右移和主串进行匹配。右移的距离在KMP算法中是如此计算的:在 已经匹配的模式串子串中,找出最长的相同的 前缀后缀,然后移动使它们重叠。
在第一次匹配过程中
T: a b a c a  a b a c a b a c a b a a b b
W: a b a c a  b
在T[5]与W[5]出现了不匹配,而T[0]~T[4]是匹配的,现在T[0]~T[4]就是上文中说的 已经匹配的模式串子串,现在移动找出 最长的相同的前缀和后缀并使他们重叠:
T: a b a c a a b a c a b a c a b a a b b
W: a  b a c a  b
然后在从上次匹配失败的地方进行匹配,这样就减少了匹配次数,增加了效率。
然而,如果每次都要计算 最长的相同的前缀反而会浪费时间,所以对于模式串来说,我们会提前计算出每个匹配失败的位置应该移动的距离,花费的时间就成了常数时间。比如:
j012345
W[j]abacab
F(j)001012
当W[j]与T[j]不匹配的时候,设置j = F(j-1)

---------------------------------------------------------------------------------------------------------------------------

下面就是我个人对于KMP算法的理解:

        在模式串中头部后面的部分是有重复的字符的,比如上述例子“abcabd”中“ab”就是重复的。我们还需要了解两个概念,“前缀”和“后缀:"前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。我们需要找的就是前缀和后缀最长的共有字符串。比如上面的例子“abcabd”中最长相同字符串就为‘ab’。

       当主串中出现不匹配字符时,就从主串不匹配位置的上一位开始,寻找这个最长相同字符串,如果找到了,那么下次这些匹配过的就不用再进行匹配了。比如上面的例子“abcabd”中最长相同前缀就为‘ab’,因此在第三张图片的过程结束后,我们可以直接进行下面的步骤:   也就是直接进行上面例子中最后一张图片中的内容。

        对于模式串的每个字符,都需要有一个值来表示如果在此位置出现不匹配了,那么下次应该用模式串中的哪一位字符来与主串的不匹配位置进行比较,也就是next()函数的值,这个值只是模式串本身的属性,与主串无关。通过上述的解释我们很容易就能得出这个函数的值的表了。

        对于上述例子中的模式串我们可以得到它的next()表为: 

        -1:第一位就匹配失败,返回-1意思为主串向后+1。其余数字均为下标。

       当主串中出现不匹配时,只需要让模式串中不匹配的字符跳转到next(),然后用next()对应的字符继续与主串的不匹配字符比较即可。比如上述例子:当主串中的‘c’与模式串中的‘d’不匹配时,通过next()我们知道,只需要让‘c’继续与模式串的next(2),也就是‘c’比较即可。

        再举几个例子:

        

        

        


参考:

http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html


        

    


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值