数据结构(邓俊辉)学习笔记】串 09——BM_BC算法:以终为始

2024-08-29 00:52

本文主要是介绍数据结构(邓俊辉)学习笔记】串 09——BM_BC算法:以终为始,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1. 不对称性
  • 2. 善待教训
  • 3.前轻后重
  • 4.以终为始

1. 不对称性

上一节所介绍的 KMP 算法计算时间,在最坏情况下也可以保证不超过线性。这的确是一个好消息。然而,倘若我们因此就停下继续优化的脚步,那就大错特错了。

在这里插入图片描述

实际上,串匹配问题与一般的搜索问题的确有着本质的区别。在我们此前所讨论的所有搜索算法中,每次比对都是一种一对一的模式,也就是一个目标与另一个候选者判定二者是否相等,的确只需常数的时间。而现在,虽然基本的数据对象是单个的字符,而所谓的串匹配则是相对于由若干个字符在局部组成的一个片段而言的,也就是说是由多个字符对多个字符,二者匹配,当且仅当每一对字符彼此相等。

然而,需要特别注意的是,反过来,一旦发现有一对字符不等,我们就立即可以判断串失配。于此可见,从计算成本的角度来看,判定一对串是否相等与判定它们是否不等并不是完全一样。

我们接下来将要介绍的 BM 算法,就充分地利用了这一性质,从而使得它匹配的效率得以进一步地提高。

实际上,这一算法同时采用了两种策略,在接下来的这一节,我们首先来讨论所谓的坏字符策略。

2. 善待教训

在这里插入图片描述

让我们将视线拉回到蛮力算法。应该记得,蛮力算法一次典型地运行过程,可以表示为这样一幅图。是的,如果将文本串固定在这里(灰色),那么这些就是模式串在不同对齐位置处的历史快照。事实上,模式串在每一个对齐位置的故事,都是类似的。

  1. 首先,要经过若干次成功的比对,在这里,用绿色的线条来表示。
  2. 而接下来,总是一次失败的比对。
  3. 之后,再下一位置的故事依然。该算法也需要经过若干次成功的匹配,然后终止于失败地比对。

而在接下来的第三次、第四次、第五次等等,各次对齐中,也同样都是要经过若干次成功的比对,并终止于一次失败的比对。因此,就局部的每一次对齐而言,我们都需要经过若干次的成功,并终止于一次失败。

请注意,在每次对齐中,尽管只有一次失败,但却足以确定相应的对齐位置是无效的。事实上,只有最后一次对齐位置才有可能是成功的。因此,就整体而言,恰好与刚才局部的模式相反。多次失败,以及至多一次的成功。

因此,如果我们着眼于改进蛮力算法。那么,我们的目标与其说是要加速匹配,不如说是要加速失败,或更准确地讲,要尽可能快速地、低成本地排除掉无效的对齐位置。

事实上,KMP 等算法就是这样。应该记得, KMP 会聪明地排除掉其中的若干个,甚至是大量的对齐位置,从而大大的节省计算的成本。甚至从某种意义上讲,我们或许能够做得比 KMP 还要出色。

正如我们刚才所指出的,就排除某个对齐位置而言,相应的那些成功的比对并不重要,而在这个意义上起实质作用的,反而是那些失败的比对。 如果真的像刚才说的那样,我们总是需要在排除多个无效的对齐位置之后,才能够确定最终有效的对齐位置,那么我们反倒应该更加期盼这种失败的比对出现得更早。

比如,按照这种思路的一种极端情况就是,我或许可能只做这些失败的比对,就足以排除掉相应的对齐位置。为了排除掉一个对齐位置,我们平均只需做常数次比对,而且在理想平均的情况下,甚至不必去关心,哪个字符更有可能首先失败,而只需按部就班地从前向后,自左而右地依次比对就可以了。

是的,如果我们的关注力只停留于单个的比对位置,那么固然如此。但是,倘若我们将所有的对齐位置作为一个整体来考虑,我们就会发现,实际上在每一个对齐位置处,按照什么样的次序去尝试对比各自符却十分敏感而关键。我们的建议是,你或许应该优先去比对那些靠后、乃至最靠后的字符。

也就是说,与我们常规的做法恰恰相反。或许你应该更多地去关注教训。是的,如果教训的确不能避免的话,或许我们应该让它更早地暴露出来,或者再准确地讲,应该让更大的教训更早的暴露出来。

那么,为什么就串匹配算法而言,在越靠后的位置所获得的教训所具有的价值越高呢?

3.前轻后重

在这里插入图片描述

依然来考察蛮力算法的一次典型执行过程。假定在依次排除了一系列的对齐位置之后,抵达了下一个对齐位置。此时我们有两种策略,或者优先去比对靠前的字符,或者反过来优先比对靠后的字符。

这里我们再次强调一下,只要字母表的规模不是很小,那么就每一次字符对字数的比对而言,成功的概率将远远小于失败的概率,也就是说我们更有可能获得教训,而不是经验。

果真如此,在前后这两个位置所获得的教训,其价值大小又有何区别呢?的确有所区别,而且很大,其背后的原因在于,根据这样的每一次教训,我们不仅可以排除一个对齐位置,而且可能排除掉多个对齐位置。

比如,根据在前一位置所获得的教训,我们或者可以同时排除掉三个对齐位置,而不是一个。类似地,在后一位置所获得教训,则有可能帮助我们排除掉更多的对齐位置,远远更多的对齐位置。

是的,如果就这类教训对我们提高计算效率的意义而言,的确呈现出前轻后重的特点。

4.以终为始

在这里插入图片描述
既然按照以上的分析,模式中越靠后的字符对算法性能的优化作用更大,那么或许我们应该把计算的方向和次序颠倒过来,以终为始。此话怎讲呢?

具体来说,在每一趟扫描中,我们都需要从末字符而不是首字符开始比对,也就是说扫描的方向将变成自后向前,而不再是自左向右。

来看这样一个具体的例子:这就是由12个字符所构成的一个文本串,而待匹配的模式串则由这三个字符组成。

  1. 为了启动第一趟扫描,我们首先要将它们的首字符彼此对齐。然而这里,我们将首先从末字符开始比对。可以看到,这是一次失败的比对。

    实际上,这种失败的比对必然是更容易发生的,因为你注意到,我们在这一所采用的字母表是由汉字构成的,即便是只记录常用的汉字,其规模也至少超过5,000,从多达5,000个的候选中任取2个,二者相同的概率自然是非常之低的。反过来不同的概率也就应该很高。

    所以对于这次失败,我们应该有足够的心理准备。因此接下来,或许我们应该静下心来,对这次失败好好地作一分析。或许你能从中悟出点什么。是的,表面上看这里是"名"与"道"之间的冲突,而实际上我们可以将这次教训进一步地概括为,如果需要在"道"的附近实现一次完整的匹配,那么至少与它对应的那个字符就应该也是"道",而非"名"。

  2. 现在,按照这一必要条件来反观我们的模式串,就会发现其实"道"根本就没有出现在其中,这一点非常重要,如果能够悟到这一点,我们就可以大胆地将这个模式串整体的移过这个位置,从而在这样一个新的位置对齐。

  3. 接下来的这趟比对与上一趟基本类似,依然是"名"与"道"不符。所以再一次地,我们同样可以将整个字符串移过这个位置。从而在这样一个新的位置继续对齐。

  4. 接下来的这趟扫描,依然起始于末字符。可以看到这是一次成功的比对,应该觉得非常幸运,因为正如刚才所言,这种成功比对出现的概率是极低的。于是接下来,我们再去进而比对与之在左侧相邻的那一对字符。这依然是一次大概率的失败比对,然而作为一次新的教训,它同样可能让我们悟到点什么。

    是的,依然是这个失配位置处的"可"字。它同样给出了一个能够局部完全匹配的必要条件。然而,如果按照这个必要条件,反观我们的模式串,就会发现其中同样没有出现这个字符。

  5. 因此接下来,我们可以将这个字符串同样整体的移过这个失配的位置,并在这样一个新的位置继续对齐,然后依然从末字符开始继续比对,同样,依然是大概率的失败。

    而此后,只要我们能够静下心来对这一教训作一分析,就同样能够针对完全匹配给出一个必要条件。我想,你已经看出这个必要条件来了。是的,如果能够在包括这个"常"字在内的局部,实现一次完全匹配,那么模式串中与之对齐的,就同样也应该是一个"常"字。

    这一回,我们依次反观模式串,就会发现的确存在一个"常"字。因此接下来,我们就不妨将整个字符串向右移动一个单位,从而让"常"字的确与"常"字相对。在满足的这样一个必要条件之后,我就可以来开始新一轮比对。

  6. 首先是末字符,这是成功的。而且接下来的各次比对也都是成功的。这意味着我们终于发现了一次完全的匹配,整个算法也就可以随即终止。

现在,我们来核算一下这个算法所花费的计算成本,也就是在期间所做的比对次数。按照我们这里标注的习惯,深色的都是成功的比对,灰色的则是失败的比对。而白色的,则是没有进行,从而节省下来的比对。

可以看到,我们累计做了4次成功的比对,以及4次失败的比对。没错,4次加4次,累计不过8次,这一结果意义非凡。因为我们注意到,即便忽略掉模式串,仅文本串也至少有12个字符,而我们在算法过程中所执行的比对次数居然要小于这一长度。换而言之,平均在每个字符上所消耗的比对居然不足一次。

这篇关于数据结构(邓俊辉)学习笔记】串 09——BM_BC算法:以终为始的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1116381

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]