数据结构(邓俊辉)学习笔记】串 13——BM_GS算法:好后缀

1. 兼顾经验

以上介绍的 BC 策略并非 BM 算法家庭中的唯一成员,它还有一个名为 GS 好兄弟,也就是所谓的好后缀策略。
在这里插入图片描述

gs 策略的作用与 bc 策略恰好互补,它着眼于充分利用此前的比对所提供的经验。我们首先来通过一个实例看看, bc 策略为什么没能有效地利用经验。而如果能利用经验,我们又能做得多好?

  1. 来看这样一个文本串,其中标注为 x 字符,内容都是无所谓的。接下来是我们的模式串,假设按照常规,我们首先将它们在首字符处对齐,然后按照 BM 算法的策略,从末字符开始比对,可以看到第一次比对成功,第二次比对也成功,实际上我们会经过四次成功的比对,直到 C 和 H 失配,此时的 H 就是所谓的坏字符。

  2. 为了与主串中的 C 相配,我们接下来应该尽可能地在模式串中找到字符 C,并试图与之对齐。然而,尽管这里总共有三个 C,应该记得我们最终选用的应该是最靠右侧的那个。然而很遗憾,最右端的这个 C 位置过于靠右。以至于,如果我们需要将它与文本串中的这个 C 对齐,将会导致整个模式串的左移而不是右移。你应该记得 BC 策略在此时会如何处置。没错,它会简明地让模式串向右侧移动一个字符

  3. 接下来继续从末字符开始,启动新一轮的扫描比对。可以看到这一轮比对在经过了一次成功之后,将会再一次的失败于 H 和 C 失配。此时的坏字符为 C。于是接下来, bc 策略为了能够使得文本串中失配的这个 H 能够恢复匹配,将会在模式串中找出某个 H,并试图将其与文本串中的 H 对齐,从而使这个 H 能够得到匹配。但是,刚才的那个特殊情况又再一次出现了,因为我们注意到,模式串中的这三个 H 中最靠后的这个 H 同样过于靠后,从而使得,如果将它与文本串中的这个 H 对齐,将同样会导致模式串的左移而不是右移。可想而知,bc 策略在此时将如何处理?没错,依然是将模式串向后移动一个字符。

  4. 然后再一次的从末字符开始启动新一轮的比对。这一回第一次比对即告失败。于是, bc 策略同样会试图从模式串中找出一个适当的 A,并试图将它与文本串中的 A 对齐,从而使得这个 A 能够得以匹配。

  5. 这一回我们的运气好一些,因为模式串中最靠后的 A 还不至于过于靠后,我们可以放心地移动模式串,从而使得这个 A 与这个 A 彼此对齐。的确,经过适当地右移之后,模式传中的这个 A 的确可以与文本传中的 A 彼此对齐而且匹配。而在接下来的一轮扫描比对中,我们会高兴地发现,所有字符都是匹配的。于是算法至此提高结束。

反观算法刚才的这一次执行过程,我们对它的性能的确很难满意,因为我们注意到,在前后的三次右移中,居然有两次只移动了一步。而进一步的观测之后,我们或许会发现,如果能够借助一些其它的策略,我们本来应该可以移动得更快。

就以刚才的第一轮比对为例,当我们失败于主串的 C 时,我们不仅可以获得坏字符之类的负面信息,同时还可以获得更多的正面信息。你能看出来吗?没错,就是在这次失败的比对之前,我们刚刚做过的四次成功比对

通过这些信息我们能够获得什么帮助和启示呢?作为事后诸葛亮,我们会发现,根据刚才的这些正面信息,我们完全可以断定接下来的两次对齐都是不必要的。原因在于按照这两个对齐位置,在刚才这四个成功比对的位置处都是失配的。

自然地,这也就注定了这两次对齐位置都必然是徒劳无功的。而在进一步观察之后,或许你应该能够发现一个更好的消息。是的,如果将此类成功的信息依然称作经验,那么我就可以与 KMP 算法一样对他们加以利用,而且相应的准备工作也可以通过预处理的方式提前完成

比如就针对这样一段区间,我们应该能够以某种方式事先从模式上中将与之匹配的部分提取出来,从使得我们在确定下一对齐位置的时候可以保证此前的这次对齐能够继续得以延续。

果真如此,我们就可以在第一轮比对失败之后,直接将模式串一步移送到位,从而有望进一步地提高 BM 算法的效率。仿照坏字符的命名方式,我们也不妨将这种匹配的后缀称作好后缀。

2. 好后缀策略

在这里插入图片描述
我们来将刚才的实例一般化,也就是说,在 BM 算法的任何一个时刻,如果当前的这套扫描失败于 X 不等于 Y 者,那么也同时意味着模式串中相应的后缀必然是完全匹配的,也就是我们所说的好后缀。因此,如果接下来希望通过右移重新在某个位置上对齐,就应该至少使得文本串中刚才匹配的部分能够继续得以匹配。

当然,还有一条:既然我们刚才已经发现 Y 不能与 X 匹配,所以在经过了这次移动之后,对应的这个新的字符,也至少不能依然是 Y。就像我在改进 KMP 算法时所举的那个例子一样,既然已经发现鸡蛋不如石头硬,我们接下来就至少应该换一个不是鸡蛋的东西去与石头相碰。

当然,至此只是最为基本的情况。与 bc 策略一样,这里同样也有若干特殊的情况。

  1. 比如在模式串中,足以与刚才文本串中匹配的部分继续匹配的子串有可能会有多个。此时,我们又当选择其中的哪一段呢?

    这里的取舍原则与 bc 策略也是一样的,也就是要在安全的前提下,避免回溯。为此,我们同样倾向于让位移量尽可能的小。是的,位移量要尽可能的小,这也就意味着如果的确存在多个这样的字串,我们应该从中选择最靠后者。

  2. 当然还有一种反过来的极端情况,也就是说在模式串中不存在任何一个这样的子串,能够足以与刚才匹配的部分继续匹配。当然,这条信息也就意味着我们此后的对齐位置,至少应该越过当前失配的这个字符。

    也就是说,移动的距离将会更大。从计算效率的角度来看,这反而是一个好消息。当然我们同样的需要保证,所有值得对齐的位置都不至被遗漏掉。因此在这种情况下,我们还是应该尽可能地从模式串中去找到这样一个子串,或者在这种情况下更准确地讲是一个前缀指的,使得它至少能与刚才匹配部分的相应后缀继续匹配。

现在,纵观以上一般的情况以及各种特殊情况。我们会发现,无论如何,位移量只会取决于 j 以及模式串 p 本身,而与文本串无关。这就意味着我们可以仿照 KMP 算法的模式,提前将相应的位移量计算出来,并将这些信息整理为一张表,以便算法执行过程中能够快速地查询。

3.实例体验

为了体会好后续表的妙用,我们方来看这样一个实例:
在这里插入图片描述

这里的文本串由13个汉字组成,而模式串 由 8 个汉字组成。通过预处理为每个字符所计算出来的 gs 表项也列在这里。

  1. 接下来,算法依然首先将二者在首字符位置处对齐,并且从末字符开始比对。我们可以看到这是一次失败的比对,相应地,此时的好后缀为空,所以它并不能给我们提供任何经验,因此我们只能采用 bc 策略,借助这次教训,将模式串整体的向后移动一个字数。
  2. 在接下来的这轮比对中,我们将成功两次,并失败于第三次。尽管此时的好后缀只有两个字符,但它也足以帮助我们快速地移动。既然当前适配的字符为"故"字,我们也就可以从 gs 表中找出它所对应的那个表项,也就是4,也就说在这种情况下,gs 策略建议我们可以直接向后移动 4 个字符。没错,4个字符。
  3. 可以看到,接下来的这轮比对将完全匹配。算法也因此顺利地结束。

那么接下来很自然也是必须要解决的一个问题,就是对于任何的模式串应当如何计算出它所对应的 gs 表呢?而且更重要的时,为此需要花费多少时间成本呢。

  • 15
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值