数据结构(邓俊辉)学习笔记】优先级队列 07——堆排序

news2024/11/13 16:18:18

1.算法

作为完全二叉堆的一个应用,这节来介绍堆排序算法。
在这里插入图片描述

是的,谈到优先级队列,我们很自然地就会联想到排序。因为就其功能而言,包括完全二叉堆在内的任何一种优先级队列都天生地具有选取功能,也就是选取其中的最大元。因此,很自然地就会联想起选择排序。

还记得这样一幅插图(上图中右上图),我们曾经用它来解释选择排序的原理及过程。不妨温习一下,在选择排序算法中,我们始终将整个序列分为两部分,也就是左侧的待排序部分以及右侧的已排序部分。而且前者中的任何一个元素都不大于后者中的任何一个元素。因此我们只需反复地遍历待排序元素,从中选取出最大者,并将它加入到已排序的子序列中。是的,正因为我们每次都需要对待排序的部分作遍历,从而导致每次选取都需要线性的时间,以致整个算法累计需要平方量级的时间。

究其原因,可以理解为我们当初对数据结构的理解和掌握还非常有限,以至于我们只能借助简单的数据结构来组织待排序的元素。而经过了一段时间的学习之后,我们已经今非昔比,我们可以运用更为高级的数据结构来组织这些待排序的元素,从而实现更高的选取效率。

比如,联想到我们刚刚学过的内容,很自然地就会想到用一个完全二叉堆来取代此前简单的线性结构。如果暂时忽略底层的具体实现,我们就会发现整个算法流程可以基本保持不变。

  1. 此前在与预处理阶段对整个线性序列的初始化,在这里则对应于建堆操作。我们刚刚介绍过,如果采用 Floyd 算法,这一步只需线性的时间。
  2. 接下来我们需要反复地取出最大元,也就是整个堆当前的堆顶。我们知道, delMax操作每次只需 log n 的时间。没错,log n 而不再是 n。因此,整个算法的复杂度,也就应该是 n log n,而不再是 n 2 n^2 n2

当然,此前的不变性依然保持,也就是待排序的部分始终都不超过已排序部分,因此算法的正确性依然可以得到保证。那么难道这个算法就是这样的平淡无奇?不是的,事实上堆排序的奥妙还不止于此,比如在空间复杂度方面,堆排序算法还可以做得更好。

2.就地

在这里插入图片描述
准确地讲,在空间复杂度方面,我们希望堆排序算法能够做到就地,也就是除了输入数据以外,只需要常数的辅助空间。 这种构思是有可能实现的,因为我们注意到,所谓的完全二叉堆在物理上就是不折不扣的向量。

因此我们或许可以令完全二叉堆与已排序的部分在同一个向量中和平共处,经过精巧的设计,我们的确可以将这一构想兑现为这种形式,具体来说,已排序部分依然居于向量的后端,而与之互补的前缀则恰好构成一个完全二叉堆,如果沿用大顶堆的惯例,我们可以立即发现,堆中的最大元必然始终都是0号元素,而接下来需要与之兑换的 X,则必然是相对于已排序元素而言秩为 -1 的那个元素。

按照以上堆排序的初始版本,我们首先需要取出这个最大的元素,然后用 x 来取而代之,然后再将备份的最大元植入 x 这个位置。当然,为了算法能够继续持续下去,我们还需要对新的根节点做下滤调整。

对于这样的连续4步操作,你能发现有什么可以优化的地方吗?
是的,这里的1、2、3完全可以整合起来,只需 m 和 x 之间的一次兑换即可等效地完成。因此,算法过程中的每一步迭代可以进一步的规整为两大步骤,也就是第一步交换,以及第二步下滤。是的,反复地交换下滤,交换下滤。直至堆变空。在整个算法的过程中,除了交需要用到常数个辅助空间之外,我们并不需要任何更多的辅助空间。

以上过程可以描述为这样的伪代码(上图最下面行),但是它又如何进一步地实现为真正的代码呢?

3.实现

在这里插入图片描述

在这里,给出堆排序的一个更为通用的算法版本,也就是说对于任何的向量,这个算法都可以对其中任意指定的区间进行排序。

  1. 作为初始化,首先需要调用完成二叉堆的建堆算法,在线性的时间内,将向量的这个区间整理为一个完全二叉堆。
  2. 此后进入反复迭代,请留意这个迭代所具有的不变性,也就是完全二叉堆当前所在的区间是由 lo 和 hi 界定的,按照我们一直以来采用的惯例,lo 所对应的是堆的首元素,而 hi 所指示的则是这个堆尾部的哨兵。

因此在每一步迭代中,我们都只需取出首元素位置处的最大元,并且将它与末元素交换,而新的堆顶元素的下滤功能则同样是由 delMax 在底层完成。

4.实例

在这里插入图片描述

看堆排序算法的一个具体实例,考察这样一个规模为 5 的向量,首先从逻辑上将它视作为一棵完全二叉树,其中有两个内部节点,也就是2和4,于是我们采用 Floyd 算法分别对 2 和 4分别做一趟下滤,即可最终得到这样一个完全二叉堆。至此,也就完成了算法的预处理步骤。
在这里插入图片描述
接下来我们进入算法的主体循环,请注意在一开始整个向量都是未排序的部分,而已排序的部分初始为空。即便如此,我们依然可以使用算法的基本策略,也就是令当前的堆顶与末元素互换位置。

  1. 破坏之后的瞬间应该是这样(上图上左二)。原来的末元素2成为了堆顶,而5则在逻辑上从原来的堆中分离出来,加入原本为空的 S。也就是说,已排序的序列此时已经拥有了第一个元素。

  2. 当然,我们还需要对新的堆顶做下滤调整,从而使得剩下的 4 个元素依然恢复为一个堆,尽管规模减少了一个单位。不出意外,接下来的最大元 4 也自然成为了新的堆顶(上图上左三)。

  3. 因此在下一轮迭代中,我们依然是将这个新的堆顶与当前的末元素互换位置,在逻辑上这等效于将堆顶摘出,并归入到有序序列中(上图下左一)。

  4. 同样,我们仍需对新的根节点做一次下滤调整,从而使得剩余的三个元素能够继续保持为是一个合法的大顶堆(上图下左三)。
    在这里插入图片描述

  5. 不出预料,当前尚未排序的元素中的最大者 3 此时也必然就是堆顶。因此,我们也只需令它与当前的末元素 2 做一次兑换。在逻辑上,这依然等同于将这个堆顶摘出并归入到有序列中。

  6. 此后再经过一轮下滤调整,剩余的元素也依然会恢复为一个合法的大顶堆,尽管此时已经只剩最后的两个元素了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2051693.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【微服务】SpringCloud 1-9章

1 从Boot和Cloud版本选型开始说起 1.1 Springboot版本选择 1.1.1 git源码地址 https://github.com/spring-projects/spring-boot/releases/ 1.1.2 官网看Boot版本 1.1.3 SpringBoot3.0崛起 https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Release-N…

SpringSecurity实现登录功能实战!!!

实现思路 登录 ①自定义登录接口 调用ProviderManager的方法进行认证 如果认证通过生成jwt 把用户信息存入redis中 ②自定义UserDetailsService 在这个实现类中去查询数据库 注意配置passwordEncoder为BCryptPasswordEncoder 退出登录,删除redis中的数…

vuex 辅助函数用法:mapGetters,mapMutations,mapActions

1 mapGetters:用法 <template> //普通用法 <p> {{ $store.getters.getCounter }} </p> //辅助函数用法 <p> {{ getCounter }} </p> </template> import { mapGetters } from vuex export default{ name:"homeView&q…

损失函数-回归模型|分类模型

损失函数 Loss Function 回归模型均方误差&#xff08;MSE: Mean Squared Error&#xff09;均方根误差&#xff08;RMSE&#xff1a;Root Mean Squared Error&#xff09;平均绝对误差&#xff08;MAE: Mean Absolute Error&#xff09;Huber损失 分类模型交叉熵损失&#xff…

MT6705 同步整流

MT6705B 是用于反激式变换器的高性能45V同步整流。MT6705B兼容各种反激转换器类型。支持 DCM、CCM 和准谐振模式。MT6705集成了一个40V功率MOSFET&#xff0c;可以取代肖特基二极管&#xff0c;提高效率。V SW <V TH-ON 时&#xff0c;内部 MOSFET 导通。V SW >V TH-OFF …

Qt/C++地图标注点的添加删除移动旋转/指定不同图标和动图/拿到单击信号

一、前言说明 标注点在地图开发中是最常见的应用场景之一&#xff0c;比如在地图上需要显示设备的位置&#xff0c;基本上都是添加标注点&#xff0c;指定图片和尺寸已经经纬度坐标位置。这个功能在每种地图内核中都提供的&#xff0c;这个并没有任何难点&#xff0c;在这个功…

类和对象 - 构造函数

文章目录 一、语言的向前兼容二、默认成员函数三、构造函数3.1 概念3.2 自己定义的构造函数3.2.1 有参和无参的构造函数3.2.1 有缺省参数的构造函数 3.3 默认构造函数3.3.1 几种默认构造函数3.3.2 默认构造函数的特点 3.4 编译器生成的默认构造函数3.4.1 函数初始化规则 四、构…

Android:动态更新app启动图标和应用名

一、需求背景 每逢重要佳节&#xff0c;很多应用启动图标会自动更新为对应佳节的图标&#xff0c;应用无需更新。 二、效果图 更新后的启动图标和应用名称 三、实现流程 Android app只能替换内置的icon&#xff0c;因此需要提前将logo图标放入App资源文件件里 实际项目App更新…

vue 路由用法 router-view

通过router-view 点击子路由显示子路由关于我们的内容&#xff0c;点击关于信息显示关于信息内容。

【Golang】火焰图空白部分是什么?

起因 被同事灵魂拷问&#xff1a;图中这块空白是什么东西&#xff1f; 豆包回答说是数据采样不完整&#xff0c;特定函数或代码段未被调用之类的原因&#xff0c;感觉都不太合理。 之前看过一篇文章说&#xff1a;Heap Profiling的采样是无时无刻不在发生的&#xff0c;执行…

【数据结构】——十大排序详解分析及对比

【数据结构】——十大排序详解分析及对比 文章目录 【数据结构】——十大排序详解分析及对比前言1. 排序的概念及其运用1.1 排序的概念1.2 排序的应用 2. 插入排序2.1 直接插入排序2.2 希尔排序 3. 选择排序3.1 选择排序3.2 堆排序 4 交换排序4.1 冒泡排序4.2 快速排序4.2.1 霍…

SOMEIP_ETS_047: echoUTF16FIXED_with_odd_number

测试目的&#xff1a; 验证设备&#xff08;DUT&#xff09;是否能够正确处理一个在终止符之后多出一个字节的echoUTF16FIXED字符串&#xff0c;并能够去除这个多余的字节。 描述 本测试用例旨在检查DUT在接收到一个不符合UTF16FIXED字符串规范&#xff08;即在终止符之后多…

Snipaste 的一款替代工具 PixPin,支持 gif 截图、长截图和 OCR 文字识别,功能不是一点点强!

Snipaste 的一款替代工具 PixPin&#xff0c;支持 gif 截图、长截图和 OCR 文字识别&#xff0c;功能不是一点点强&#xff01; PixPin 的名字来源于“Pixel Pin”&#xff0c;简单来说是一个截图、贴图的工具&#xff0c;但是 PixPin 以截图和贴图两大功能为核心做了大量的优…

高数4.3 分部积分法

1.性质 2.例题1-sin 有sinx ,cosx一定要想法变成一次方&#xff0c;有tanx,secx,cotx,cscx 要想法变换成偶次幂。 幂和三角函数在一次&#xff0c;要把三角函数放在d&#xff08;&#xff09;里面。 3. 例题2-tan 4. 例题3-反三角

树莓派开发笔记04-树莓派的PWM输出

github主页&#xff1a;https://github.com/snqx-lqh gitee主页&#xff1a;https://gitee.com/snqx-lqh 本项目github地址&#xff1a;https://github.com/snqx-lqh/RaspberryPiLearningNotes 本项目gitee地址&#xff1a;https://gitee.com/snqx-lqh/RaspberryPiLearningNote…

07结构型设计模式——装饰器模式

一、装饰器模式简介 装饰器模式&#xff08; Decorator Pattern&#xff09;又称包装模式。通过一种面向客户端透明的方式来扩展对象的功能&#xff0c;是继承关系的一个替换方案。装饰器模式就是把要添加的附加功能分别放在单独的类中&#xff0c;并让这个类包含它要装饰的对…

求前n项和5.3.1

求他的前n项和 #include <stdio.h>int main() {int n;int i;double sum0.0;scanf("%d", &n);for(i1; i<n; i){sum1.0/i;}printf("%f", sum);return 0; } 一个变量是输入的变量 一个变量要表达递增的 另外一个变量把他们加起来表示总和…

整数分解5.3.2

题 前面写过逆序的 最后一个数后面不要有空格 #include <stdio.h>int main() {int x;scanf("%d",&x);int d;do{dx%10;printf("%d",d);if(x>10){printf(" ");}x/10;}while(x>0);printf("\n");return 0; } 现在这个是…

0818---算法

牛牛快递 如何输入一个字符&#xff1f; java中Scanner类没有.nextChar import java.utol.Scanner; Scanner in new Scanner(System.in); char b in.next().charAt(0);思路&#xff1a; 创建一个Scanner对象&#xff0c;调用Scanner对象的next()方法获取控制台输入的字符串…

Java二十三种设计模式-备忘录模式(19/23)

本文深入探讨了备忘录模式&#xff0c;从定义、组成、实现到使用场景、优缺点、与其他模式的比较&#xff0c;以及最佳实践和替代方案&#xff0c;全面解析了如何在软件开发中有效地保存和恢复对象状态&#xff0c;以支持复杂的撤销操作和历史状态管理。 备忘录模式&#xff1a…