美文网首页
学习BM算法的姿势

学习BM算法的姿势

作者: Wu杰语 | 来源:发表于2019-03-10 21:52 被阅读0次

    BM算法简介

    BM是字符串搜索算法。

    字符串搜索单模式下有BF、RK、BM、KMP算法,其中BF是暴力搜索,RK是利用hash的一种算法,BM和KMP是最常用的字符串匹配算法,假定模式串长度为m,文本长度是n,则BM最大算法复杂度在O(3n),KMP算法复杂度在O(m+n)。

    学习姿势

    BM算法恐怕我们一般都不会去写,为何要去学习这种算法呢?算法能训练人的逻辑能力,当然要学。但是以什么样的姿势来学这种算法呢,平心而论,这种算法学习复杂度算是比较高的,怎样去学习比较合适呢?

    就我个人的体会来说,按照算法思想和算法实现的套路来学习能显著的降低学习复杂度。怎么理解这个套路呢?

    • 算法思想属于高层一点的抽象,这里的算法思想结合上算法主流程来理解,互相印证,算法的主流程也算是算法思想这个层面
    • 算法实现,算法主流程已经放在算法思想中了,那么算法实现指什么呢?看过《编程珠玑》,第二部分强调的就是算法写好以后的优化,没错,这里指的就是优化。对于这部分,如果能理解就理解,理解不了就当训练自己的算法思维了。

    一旦以这种方式去理解这两个算法,就会发现学习曲线降低了很多。

    BM算法

    BM = (坏字符规则 + 好后缀规则) (算法思想) + (坏字符hash数组实现 + 好后缀巧算)(优化)

    BM的算法思想

    BM算法与两条规则有关,理解了这两条规则就理解了BM的算法思想。BM文本串和模式串是 从后向前 比较(特别注意一下这点)。

    坏字符规则
    - c是坏字符

    第一个不匹配的就是坏字符,如图就是文本串的c。不考虑好后缀的情况下,你会怎么办,就是直接把模式串移动到坏字符的后面。

    • 移动到坏字符后面

    如果说坏字符在模式串种存在, 直接移动到后面,可能移动过头了。例如说如下情形,直接移动到坏字符c后面就过头了。

    • 移动过头了

      这时就需要结合好后缀来匹配。

    好后缀规则

    好后缀有两种情况

    • 情形一 好后缀在模式串种可以找到
      从后向前匹配,在遇到坏字符之前,这些匹配的串就是好后缀,如下橙色的ab就是好后缀。
    • 好后缀

    好后缀可以在模式串种找到,直接就移动到模式串种的匹配处。

    • image.png
    • 情形二 好后缀在模式串种可以找不到

    • image.png

      这种情况下找不到,应该怎么移动呢,就要找好后缀的后缀子串和模式串的前缀子串的最大匹配串

    • image.png

      如图,找到ab的后缀子串和bb的前缀子串最长匹配串时b,因此移动到b的位置。

    算法思想讲完了,就是这样的,再配合上主算法代码就完成了算思想的学习

    class Solution:
        def search(self, inputStr, pattern):
            if len(inputStr) < len(pattern):
                return -1
            bc = self.generateBC(pattern)
            suffix, prefix = self.generateGoodSuffix(pattern)
    
            i = 0
            patternLength = len(pattern)
            while i <= len(inputStr) - patternLength:
                j = patternLength - 1
                while j >= 0:
                    if inputStr[i+j] != pattern[j]:
                        break
                    j -= 1
                if j < 0: # match
                    return i
                # exist bad chracter, i + bad c
                x = j - bc[ord(inputStr[i + j]) - ord('a')]
                #exist good suffix
                y = 0
                if j < patternLength - 1:
                    y = moveByGoodSuffix(j, patternLength, suffix, prefix)
                i = i + max(x, y)
            return -1
    
        def moveByGoodSuffix(j, length, suffix, prefix):
            k = length - 1 - j # length of good suffix
            if suffix[k] != -1:
                return j - suffix[k] + 1
            r = j + 2
            while r < length - 1:
                if prefix[length - r + 1]:
                    return r
            return j + 1
    

    BM的算法实现(优化)

    对于坏字符位置以及好后缀的位置的计算属于优化部分,就不直接多说了,上代码

    • 坏字符优化
        def generateBC(self, m):
            bc = [-1] * 26
            for i in range(len(m)):
                bc[ord(m[i]) - ord('a')] = i        
            return bc
    ···
    这里直接利用hash数组,以空间换时间。比较容易理解,就不再赘述了。
    
    - 好后缀计算
    
    def generateGoodSuffix(self, m):
        length = len(m)
        suffix = [-1] * length
        prefix = [False] * length
        for i in range(length - 1):
            j = i
            k = 0
            while j>=0 and m[j] == m[length - 1 - k]:
                j -= 1
                k += 1
            if k: suffix[k] = j + 1
            if j == -1: prefix[k] = True
        return (suffix, prefix)
    

    这个计算是个难点,但也不再赘述了,可以到网上查一下。关键想说的就是,按照算法原理和实现的套路分开理解后,对于实现,可以不再往下,学习到第一步算法原理就行了。算法实现就是优化中的预先计算存储的优化思路。

    小结

    按照算法原理和算法实现(优化)的套路,可以降低学习的复杂度,对于字符串搜索的KMP算法,也可以用同样的套路来进行学习。KMP算法的算法实现(优化)可以说是非常难以理解的,是一种动态规划的算法,但是如果抓住这个套路,即使理解不了算法实现(优化),我们理解了算法思想也是可以的。

    相关文章

      网友评论

          本文标题:学习BM算法的姿势

          本文链接:https://www.haomeiwen.com/subject/ttyppqtx.html