BM 算法包含两部分,分别是坏字符规则(bad character rule)和好后缀规则(good suffix shift)。
1.坏字符规则
按照模式串下标从大到小的顺序,倒着匹配的。
没有匹配的字符叫作坏字符(主串中的字符)。
拿坏字符 c 在模式串中查找,发现模式串中并不存在这个字符,可以将模式串直接往后滑动三位,再从模式串的末尾字符开始比较。
这个时候,坏字符 a 在模式串中是存在的,可以将模式串往后滑动两位,让两个 a 上下对齐,然后再从模式串的末尾字符开始,重新匹配。
滑动规律:当发生不匹配的时候,我们把坏字符对应的模式串中的字符下标记作 si。如果坏字符在模式串中存在,我们把这个坏字符在模式串中的下标记作 xi。如果不存在,我们把 xi 记作 -1。那模式串往后移动的位数就等于 si-xi。(注意,我这里说的下标,都是字符在模式串的下标)。
如果坏字符在模式串里多处出现,那我们在计算 xi 的时候,选择最靠后的那个
BM 算法在最好情况下的时间复杂度非常低,是 O(n/m)。
极端的例子:
主串是 aaaaaaaaaaaaaaaa,模式串是 baaa,移动的位数可能是负数
2.好后缀规则
当模式串滑动到图中的位置的时候,模式串和主串有 2 个字符是匹配的
把已经匹配的 bc 叫作好后缀,记作{u}。我们拿它在模式串中查找,如果找到了另一个跟{u}相匹配的子串{u*},那我们就将模式串滑动到子串{u*}与主串中{u}对齐的位置。
如果在模式串中找不到另一个等于{u}的子串,就直接将模式串,滑动到主串中{u}的后面(当模式串滑动到前缀与主串中{u}的后缀有部分重合的时候,移到重合地方)
我们可以分别计算好后缀和坏字符往后滑动的位数,然后取两个数中最大的,作为模式串往后滑动的位数。这种处理方法还可以避免我们前面提到的,根据坏字符规则,计算得到的往后滑动的位数,有可能是负数的情况。
好后缀的处理规则中最核心的内容:
- 在模式串中,查找跟好后缀匹配的另一个子串;
- 在好后缀的后缀子串中,查找最长的、能跟模式串前缀子串匹配的后缀子串;
BM 算法核心思想是,利用模式串本身的特点,在模式串中某个字符与主串不能匹配的时候,将模式串往后多滑动几位,以此来减少不必要的字符比较,提高匹配的效率。BM 算法构建的规则有两类,坏字符规则和好后缀规则。好后缀规则可以独立于坏字符规则使用。因为坏字符规则的实现比较耗内存,为了节省内存,我们可以只用好后缀规则来实现 BM 算法。
总体上,KMP算法是借鉴了BM算法本质思想的,都是遇到坏字符的时候,把模式串往后多滑动几位。
1.但是这里要注意一个细节,不然容易被前面学的BM算法给误导,导致难以理解。
BM算法是对模式串从后往前比较,每次是将主串的下标 ”i“ 往后移动多位。(这符合我们正常的思维,所以好理解)
KMP虽然也是往后移动多位,但是比较时,是对模式串从前往后比较;
对于主串已经比较过的部分,保持主串的 ”i“ 指针(即下标)不回溯。
而是通过修改模式串的”j“指针,变相地让模式串移动到有效的位置。(这里修改"j",是让"j"变小了,我们说的还是将模式串往后移动,所以不太好理解)
2.KMP算法中不好难理解的,构建next数组,其实很简单,要多下自己的脑筋,不要被带偏了,就好理解了。就是求nexti,前一个的最长串的下一个字符与最后一个相等,那next[i]就=next[i-1]+1;否则就需要找前一个的次长串,递归这个过程,直到找到或没有。