字符串匹配算法-BM算法

836 阅读5分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

我们在字符串匹配算法(一)学习了BF算法和RK算法,那有没更加高效的字符串匹配算法呢。我们今天就来聊一聊BM算法。

BM算法

       我们把模式串和主串的匹配过程,可以看做是固定主串,然后模式串不断在往后滑动的过程。当遇到不匹配的字符时,BF算和RK算法的做法是,把模式串向后滑动一位,然后从模式串的第一位开始重新匹配。如下图所示。

      由于BF算法和RK算法,在遇到不匹配的字符时,模式串只是向后滑动一位,这样的话时间复杂度比较高,那有没有什么算法可以一下子多滑动几位呢?比如遇到主串A中的字符d,由于d不在模式串中,所以只要d和模式串有重合,那就肯定不能匹配。所以我们可以直接多滑动几位,直接滑到d的后面,然后再继续匹配,这样不就提高了效率了吗?

      今天要聊的BM算法,本质上就是寻找这种规律。借助这种规律,在模式串和主串匹配的过程中,当模式串和主串遇到不匹配的字符时,能够跳过一些肯定不匹配的情况,多往后滑动几位。

BM算法的原理

      BM算法包含2部分,分别是坏字符规则和好后缀规则。

1.坏字符规则

     我们在BF算法和RK算法中,在模式串和主串匹配的过程中,我们都是按模式串的下标从小到大的顺序依次匹配的。而BM算法的匹配顺序则相反,是从大到小匹配的。如下所示。

     从模式串的末尾倒着匹配,当发现主串中某个字符匹配不上时,我们就把这个字符称为坏字符。我们拿着坏字符d在模式串中查找,发现d不在模式串中。这个时候,我们可以将模式串直接滑动3位,滑动到字符d的后面,然后再从模式串的末尾开始比较。

        这个时候,我们发现主串中的字符串b和模式串的中的c不匹配。这个时候由于坏字符b在模式串中是存在的,模式串中下标为1的位置也是字符b,所以我们可以把模式串向后滑动1位,让主串中的b和模式串中的b相对齐。然后再从模式串的末尾字符开始重新进行匹配。

       从上面的例子中,我们可以总结出规律。当发生不匹配的时候,我们把坏字符对应的模式串中的字符下标记做Ai。如果坏字符在模式串中存在,我们把这个坏字符在模式串中的下标记做Bi(如果坏字符在模式串中出现多次,我们把靠后的那个位置记做是Bi,这么做是为了不让模式串向后滑动过多,导致可能匹配的情况错过)。那模式串向后滑动的位数就是Ai-Bi。

       不过单纯的使用坏字符规则是不够的。因为根据Ai-Bi计算出来的移动位数有可能是负数。比如主串是aaaaaa,模式串是baaa。所以,BM算法还需要用到“好后缀规则”。

2.好后缀规则

        好后缀规则和坏字符规则思路上很相似。如下图所示。

     当模式串滑动到图中的位置时,模式串和主串有2个字符是匹配的,倒数第三个字符发生了不匹配的情况。我们把已经匹配的ab叫做好后缀,记做{u}。我们拿它在模式串中进行寻找另一个和{u}相匹配的子串{u*}。那我们就将模式串滑动到子串{u*}和主串{u}对齐的位置。

     如果在模式串中找不到另一个等于{u}的子串,我们就直接将模式串,滑动到主串{u}的后面。因为之前的任何一次往后滑动,都没有匹配主串{u}的情况。不过,当模式串中不存在等于{u}的子串时,我们直接将模式串滑动到{u}的后面,这样是否会错过可能匹配的情况呢。如下所示,这里的ab是好后缀,尽管在模式串中没有另一个相匹配的子串{u*},但如果我们将模式串移动到{u}的后面,那就错过了模式串和主串相匹配的情况。

     所以,当模式串滑动到前缀与主串中{u}的后缀有部分重合的时候,并且重合的部分相等的时候,就有可能会存在完全匹配的情况。针对这种情况,我们不仅要看好后缀在模式串中,是否存在另一个匹配的子串。我们还要考察好后缀的后缀子串,是否和模式串的前缀子串相匹配。

      这里我们再来解释一下字符串的后缀子串和前缀子串。所谓字符串A的后缀子串,就是最后一个字符跟A对齐的子串,比如,字符串abc的后缀子串是c、bc。所谓的前缀子串,就是起始字符和A对齐的子串。比如,字符串abc的前缀子串是a、ab。我们从好后缀子串中,找一个最长并且能和模式串前缀子串匹配的,假如是{v}。然后滑动到如图所示的位置。

       到目前位置,我们的坏字符和好后缀就讲完了,我们接下来想这么一个问题。当模式串和主串中某个字符不匹配的时候,我们是选好后缀规则呢还是坏字符规则来计算向后滑动的位数呢?

       我们可以分别计算坏字符规则和好后缀规则向后滑动的位数,然后取两个数的最大的,作为模式串往后滑动的位数。

今天我们就先聊到这里,如果喜欢点个赞吧。更多硬核知识,请关注。