【算法无用系列】字符串匹配那些事——BM算法


前言

BF算法和RK算法思路比较简单,但是效率却不尽人意,适合较短的字符串匹配时使用,如果需要在较长的字符串匹配时,则需在算法上进行优化。


一、BM算法

       BF算法和RK算法总是挨个匹配。例如,在匹配DB的时候要从AC->CD->DB。其实在匹配到AC的时候,就可以发现AC这两个字符和DB都无法匹配,那既然如此,CD就没有必要再次匹配了,即可直接向后移动两位直接匹配DB。BM算法即是如此,寻找这种规律,跳过没有必要匹配的字符,来提高性能。
在这里插入图片描述

1.1、坏字符规则

在这里插入图片描述
我们从模式串的末尾往前倒着匹配,当我们发现某个字符没法匹配的时候。我们把这个没有匹配的字符叫作坏字符(主串中的字符)。

在这里插入图片描述
C在模式串中无法匹配,所以C为坏字符,如此发现,可以直接向后移动三位。
在这里插入图片描述
向后移动三位再次匹配,发现a为坏字符。但是这个时候却没有办法再次向后移动三位。因为主串中的a和模式串中a可以匹配,所以向后移动2位。
在这里插入图片描述
这样,对字符串adb完成匹配,先移动了3位,再移动了2位。
如此,当发生不匹配的时候,我们把坏字符对应的模式串中的字符下标记作si。如果坏字符在模式串中存在,我们把这个坏字符在模式串中的下标记作xi。如果不存在, 我们把xi记作-1。那模式串往后移动的位数就等于si-xi。(注意,我这里说的下标,都是字符在模式串的下标,下标从0开始)。
在这里插入图片描述
通过这种方式,即可对应,第一次移动3位是由si=2(模式串中坏字符的坐标位2),xi=-1(坏字符在模式串中不存在)得2-(-1) = 3 ; 第二次移动2位,是由于坏字符在模式串中存在,并且坐标为0,得出2-0=2
这样看来,BM算法可以非常高效的进行匹配。但是单单这种方式计算是不行的。
例如:
主串是aaaaaaaaaaaaaaaa,模式串是baaa。坏字符a的位置si=0,坏字符a在模式串的位置xi=1,得出si-xi=-1,这时不但不会向后滑动模式 串,还有可能倒退。所以,BM算法还需要用到“好后缀规则”。

1.2、好后缀规则

好后缀规则实际上跟坏字符规则的思路很类似。从模式串的末尾往前倒着匹配。发现模式串和主串有2个字符是匹配的,倒数第3个字符发生 了不匹配的情况。即可认为bc为好的后缀。
在这里插入图片描述
我们把已经匹配的bc叫作好后缀,记作{u}。我们拿它在模式串中查找,如果找到了另一个跟{u}相匹配的子串{u*},那我们就将模式串滑动到子串{u*}与主串 中{u}对齐的位置。
在这里插入图片描述
如果在模式串中找不到另一个等于{u}的子串,我们就直接将模式串,滑动到主串中{u}的后面,因为之前的任何一次往后滑动,都没有匹配主串中{u}的情况。
在这里插入图片描述
不过,当模式串中不存在等于{u}的子串时,我们直接将模式串滑动到主串{u}的后面,这么做也不是特别的合适,例如如下示例。
在这里插入图片描述
如此发现,我们要注意,主串中好后缀与模式串中的前缀有重叠的时候,也有可能存在完全匹配。
在这里插入图片描述
所以,针对这种情况,我们不仅要看好后缀在模式串中,是否有另一个匹配的子串,我们还要考察好后缀的后缀子串,是否存在跟模式串的前缀子串匹配的。 所谓某个字符串s的后缀子串,就是最后一个字符跟s对齐的子串,比如abc的后缀子串就包括c, bc。所谓前缀子串,就是起始字符跟s对齐的子串,比如abc的前缀子 串有a,ab。我们从好后缀的后缀子串中,找一个最长的并且能跟模式串的前缀子串匹配的,假设是{v},然后将模式串滑动到如图所示的位置。
在这里插入图片描述
这样分别计算出好后缀和坏字符向后滑动的位数,取得最大的那个进行滑动即可,可以解决反方向滑动的问题。

注:代码实现可在极客时间的课程获取。


参考文献:
BM算法维基百科
极客时间——数据结构与算法之美

猜你喜欢

转载自blog.csdn.net/qq_30285985/article/details/109202010
今日推荐