数据结构(邓俊辉)学习笔记】优先级队列 09——左式堆:合并算法

embedded/2024/9/23 17:07:24/

文章目录

  • 1. LeftHeap模板类
  • 2. 算法
  • 3. 实现
  • 4. 实例

1. LeftHeap模板类

接下来这节,来讨论左式堆的合并算法。再给出具体算法之前,首先要给出左式堆模板类的定义。
在这里插入图片描述

比如这就是一种可能的实现方式,可以看到,我们这里再次利用了 C++的多重继承,只不过与完全二叉堆不同,既然左式堆已经不再满足结构性,所有元素在物理上也不可能继续保持紧密的排列,因此继续从向量进行派生已经不合时宜,而实际上在这样的场合中,灵活地改用树形结构作为派生的基类则是一种更加高效的方法。同样地,这里依然需要以优先级队列接口为"神",而取代向量的二叉树则扮演着"形"的角色。

既然同样的派生自 PQ, 左式堆也自然地应该提供优先级队列的三个标准接口,而根据这里的实现方式,最大元总是始终对应于根节点。因此,为了取出最大元,我们只需将根节点的数据域返回即可。

接下来我们就可以通过外部函数的形式给出将两个左式堆合并的具体算法。

2. 算法

实际上,采用递归的模式,左式堆合并算法可以非常简明地描述并实现。
在这里插入图片描述
来看一个一般的场景,假设待合并两个堆,分别以 a 和 b 为根,并且假设在抵达递归基之前,它们的左右子堆都是存在的。

我们可以借助递归将 a、b两个堆合并的问题转化为这样一个问题:具体来说也就是我们需要将 a 的右子堆取出,并且递归的与刚才的堆 b 完成合并,合并所得的结果继续作为 a 的右子堆。

当然,为了保证 a 在此后继续满足左倾性,在这次合并返回之后,我们还需比较 a_L 与合并之后这个堆的 NPL 值,如果有必要,我们还需另二者互换位置。

没错,整个算法就是这样的简单明了,尽管它的实现还需要破费一些功夫。

3. 实现

现在,我们就来将刚才的算法原理兑现为具体的代码。
在这里插入图片描述
比如这就是一种可能的实现方式,可以看到这是一个递归式的算法。

作为递归基,总共有两种情况,对应于待合并的堆中至少一个为空的情况。事实上只要其中一个为空,我们就直接返回另一个即可。

因此,当算法执行到这一句的时候(第三句)可以确认两个堆都不是空的,此时我们要比较两个根节点在数值上的大小,如果有必要,应将二者互换名称。从而保证在数值上 a 总是不小于 b,以便在后续递归的合并过程中将 b 作为 a 的后代。

接下来是核心的一步,我们需要递归地将 a 的右子堆与 b 进行合并。得益于递归的机制,接下来我们就可以假设这次合并的确完成。

因此接下来我们要在 a 及其新的右子树之间建立起一个正确的连接。

在完成了这样的拓扑连接之后,我们还需要进一步的确认 a 满足左倾性。也就是说就 NPL 而言,如果当前的左子堆要小于已右子堆,则需要将二者互换位置,

最后我们还需要根据 NPL 的定义及时地更新 a 的 NPL 值。

至此算法方可顺利返回。

4. 实例

以下,就来通过一个具体的实例加深对左式堆合并算法的理解和领悟:
在这里插入图片描述
假设在这里,我们需要将一个规模为4的堆与另一规模为3的堆合并起来。

  1. 首先通过比较,我们能确认前者的树根在数值上要大于后者的树根,因此二者无需互换,我们分别称之为 a 和 b。

  2. 相应的,a 的右孩子也自然就是12,于是按照算法,我们将原先的问题转化为 a 的右子堆与 b 堆的合并问题。

  3. 在新的这个递归层次,我们依然需要比较两个子堆的根节点,因为在数值上15更大,所以此时我们应该将它们互换名称,将前者记作 b,而将后者记作 a。于是问题进而转化为这样的形式,也就是说将15这个堆与12这个堆进行合并。

  4. 既然此时的 a 是15,所以 a 的右子树也自然就是8,是按照算法的流程,问题进一步转化为子堆8与子堆12的合并问题。

  5. 同样,在经过一次数值上的比较之后,我们确认应该将二者互换名称。也就是说接下来我们应该将12作为 a,而将8作为 b。

  6. 此时 a 的右孩子为空,因此在再接下来的递归层之上,将直接返回节点8,并且将8作为12的右孩子。也就是说在此局部应该是这样。

    请特别注意,在这层能递归返回之前,还有一项非常重要的任务,你还记得吗?

是的,我们需要确认12 满足左倾性,实际上它这个时候恰恰并不满足,因为我们注意到,它当前的左子树为空,当然,只要留意了这个问题,它的解决并不困难,你也应该记得是怎么处理的——没错,令他的左右子堆互换位置,这是为什么我们会得到这样一个局部结构。

  1. 接下来,我们的递归返回到节点15,同样地,在此我们也需要核对这个节点的左倾性。那么此时它的两个孩子 NPL 值各是多少呢?是的,都应该是1。因此左倾性在这个节点上并没有受到破坏。
  2. 因此我们可以继续逆行而上,递归返回至全堆的根,也就是节点17。此时的17是否满足左倾性呢?我们查验一下它的左右孩子 NPL 值各是多少,你也不妨心算一下。没错,左孩子的 NPL 值为1,而右孩子为 2。也就是说此时的节点 17 恰恰违反了左倾性。
  3. 同样地,关键在于发现问题、解决问题并不困难,在这个情况下,我们也只需经过一次兑换,交换17这个节点的左右孩子,在节点17的左右孩子召唤之后,这个数据结构也就从整体上恢复成一个左式堆。

不要忘了,构成这个堆的成员不多不少,恰好都来自于最初待合并的两个子堆。也就是说,我们已经顺利地完成了这样一个合并的任务。

当然,通过这个实例也可以验证我们最初的设计目标:也就是整个的合并过程的确是围绕着右侧链来进行的。因此整个算法的时间复杂度也自然就不超过右侧链的长度,我们此前已经就此做出过界定,也就是说它在渐进意义下绝对不会超过 log(n), 这个结果再好不过了。


http://www.ppmy.cn/embedded/101797.html

相关文章

【MySQL数据库管理问答题】第6章 管理 MySQL 用户

目录 1. 对于验证和授权,它们有什么区别,各自发生在用户访问过程的哪个阶段? 2. MySQL 用户账户的定义信息保存在数据库的什么地方? 3. 在定义用户时,要避免在主机名中使用通配符,除非绝对必要。请给出如…

【JS】localeCompare实现中文排序

如何对两个中文进行字典顺序排序&#xff0c;如’本’拼音首字母’b’&#xff0c;‘初’拼音首字母’c’&#xff0c;所以’本’<‘初’。 JS默认根据编码顺序排序 使用localeCompare即可&#xff0c;如 ‘本’ < ‘初’ 则返回负数 使用方法 referenceStr.localeComp…

在linux上如何发现跟文件系统中占用比较多空间的文件或者目录

在linux上 由于还存在其它的文件系统 所以如果我想发现那些文件或者目录占用比较大空间 想释放根文件系统的一些空间 不知道该使用什么命令来查找哪里占用了比较大的空间。 一种就是使用ncdu这个命令 这个命令需要单独安装 yum install ncdu [rootstbm000019-vm16 oracle]# …

Excel的使用总结3

1、选择一列数据&#xff0c;除了表头 点击表头下的第一个数据&#xff0c;点击快捷键CTRLSHIFT ↓ 2、如何将00001&#xff0c;这样的数据前面的0去掉&#xff08;前提是单元格格式已经是文本了&#xff09; 可以直接使用text公式

选择排序(直接选择排序和堆排序)

一、直接选择排序 1.基本思想 每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;直到全部待排序的数据元素排完。 2.动图展示 3.思路讲解 ①在元素集合array[i]—array[n-1]中选择关键码最大&…

【C语言】深入理解指针3(附转移表源码)

深入理解指针3 1.字符指针变量2.数组指针变量2.1是什么2.2应用 3.二维数组传参的本质4.函数指针变量4.1函数指针变量的创建和使用4.2 typedef关键字 5.函数指针数组6.转移表 1.字符指针变量 上⾯代码的意思是把⼀个常量字符串的⾸字符 h 的地址存放到指针变量 pstr 中。 《剑指…

闲鱼IP属地地址:去外地会自动变化吗?解析实时更新机制

在数字化时代&#xff0c;网络交易平台如闲鱼已成为我们日常生活中不可或缺的一部分。在进行二手交易时&#xff0c;了解对方的地理位置信息成为许多买家和卖家的关切点。那么&#xff0c;去外地闲鱼IP会变吗&#xff1f;闲鱼IP属地地址是实时更新吗&#xff1f;本文将深入探讨…

Git提交错误代码如何回退代码

1&#xff0c;找到需要回退的提交行&#xff0c;点击右键&#xff0c;点击重置当前分支到此次提交 2,选择强行合并 3&#xff0c;执行git pull -f 强行推送 4&#xff0c;如果当前账号没有开启本分支强推权限 需要去git开启 5&#xff0c;如果没有推送&#xff0c;处于待推…