文章目录
- 1.坏字符
- [2. 特殊情况](#2. 特殊情况)
1.坏字符
实际上,刚才的实例中我们所展示的那样一个计算过程,就是所谓 BM 算法所采用的策略之一,而这一策略,将我们刚才所说的教训称作坏字符。
在这里,不妨改为基于蛮力算法的第二个版本来进行改造。也就是说模式算中当前参与比对的如果是字符 P[j],那么文本串中对应的就是 T[i + j], 这幅图画出的就是这样一个一般性的场景。
请注意,当前这趟扫描如果的确已经抵达 P[j] 或 T[i + j],则说明在这对字符的右侧,有一段足够长的匹配部分。而相对于模式串而言,这个匹配部分是一个后缀。既然这两个字符失配,不妨就分别将它们记作 X 和 Y。
延续我们在刚才的分析中所采用的逻辑,如果我们将这次失配当做一次教训,那么其原因就可以解释为:在 Y 这个位置,我本来期望是一个 X,然而这个字符Y的出现,却使得我们在此局部获得一次完整匹配的希望成了泡影,因此 BM 算法也将这个字符 Y 称作坏字符。
正是因为这个坏字符的出现,当前这个对齐位置,也就自然地可以被排除掉了。那么,更重要的是,这次教训能够为我们带来什么启示呢?具体的能为我们后续的比对提供什么有益的借鉴呢?
将刚才我们所举的实例推越广之,如果我们还希望能够在这个字符 X 附近,实现一次完整的匹配,那么作为一项必要条件,与这个字符 X 对准的也应该至少是一个 X。是的,由刚才所举的实例推而广之,我们也同样可以得出此时的处置方法。
具体来说,就是试图从模式上中去找出这样的一个字符 X,并且通过相应的移动,使得这个 X 能与文本串中的那个 X 彼此对齐。而一旦完成了这样的对齐,我们又可以继而从最右端开始,启动下一轮的扫描比对。
那么对应于每一个这样的场景,相应的位移量又应该是多少呢?
稍加观察我们不难发现,这个位移量仅取决于刚才失配的位置 j 以及对应的字符 X 在模式传中的位置。而与文本串以及当前的对齐位置没有任何关系。这就意味着我们可以与 KMP 算法一样通过预处理事先计算出每种情况下对应的位移量。
也就是说,我们可以将每一个这样的字符所对应的位移量制成一个表,也就是所谓的 bc 表。通过再进一步观察,我们不能发现,这个表只需记录每一个字符 X 在模式串中所对应的秩,在图中也就对应于这段距离(bc['X'])。
我们知道,这段前缀的长度恰好就应该是 j,而对应的位移量也自然就应该是这两段前缀的长度之差。
2. 特殊情况
当然,实际情况要更为复杂一些。不妨来看看还有哪些特殊情况需要考虑并且处置。
- 首先需要考虑的一种情况就是,在模式串中有可能同时存在多个 X,那在这种情况下,我们究竟应该用哪个 X 来替换哪个坏字符 Y 呢?与 KMP 算法一样,当同时存在多个候选时,我们并不倾向于向数学上那样,逐一地去尝试它们,而应该在安全的前提下,选择其一,从而使得我们不必回溯。
也就是说,如果有多个候选,那么我们倾向于选择其中对应的位移量尽可能小的那个。按照刚才的分析,任何一个字符所对应的位移量都与这个字符在模式串中的秩呈反比。因此,为了使得位移量尽可能的小,所对应字符的秩就应该尽可能地大。换而言之,当有多个候选时,我们应该首先选用其中的最靠后者,或者等价的,在相对于这个字符 X 的那个后缀中,必须不包含任何 X。
-
那么与此相反的另一种极端情况就是,在模式串中,可能没有任何这样的 X 可供选择。
回忆一下,在我们此前所举的那个实例中,的确就有这种情况。比如其中文本串中的那个"道"字,在模式串中根本就没有出现过。你应该还记得我们当时的处置方法,没错,我们只需将模式串完整的移过这个对齐的位置。那么在相应的算法描述以及代码实现的过程中,我们又应该如何统一地来处置这种情况呢?没错,哨兵。
与 KMP 算法一样,我们可以为每一个模式串在最左侧增设一个假想的通配哨兵。于是所谓的将整个模式串移过这个位置,也就等效于用这个通配的哨兵与刚才失配的字符对齐。既然是通配字符,那么此前适配的这个字符在此后就可以被忽略掉。
实际上,除了以上所介绍的两种,还有一种更为特殊的情况需要处理,你能看出来吗?是的,在这种情况下,模式串中的确至少存在一个候选的字符 X。然而,其中最靠右的那个,位置又过于靠右。也就说,它所对应的那个 bc 表项,或者说他的秩太大了,高过了 j,以至于通过二者之差所计算出来的位移量,既然是一个负数。显然,这样的回溯是没有道理的,也是不必要的。
因为作为这个算法的一条不变性,相对于任何时候的当前对齐位置,此前的所有对齐位置,都已经被淘汰掉了,因此根本没有必要再回过头去重新考虑。既然以前的所有对齐位置都已被淘汰掉,而当前的这个对齐位置也刚刚被否定掉了。所以再直接而简洁不过的处置方法就是,将整个模式串向后移动一个字符,并进而考察下一个对齐位置。
至此,我们已经兼顾到了可能发生的所有情况,包括特殊情况。我知道 KMP 算法的整个策略,完全是融入并体现于其中的 next 表。而这里的 BM 算法也类似,其策略也将最终融入并体现于 bc 表,以及稍后要介绍的 gs 表。
那么首先一个问题就是 bc 表又当如何构造出来呢?相应的我们又需要花费多长时间呢?