堆排序(含证明)
引言
前面我们讲过堆的基本操作的实现,现在给定一个int类型的数组,里面存放的数据是无序的,我们如何利用堆的思想来实现数组内数据的升序排列或降序排列呢?
通过前面讲到的堆的实现,我们可以想到,我们再开辟一块空间来模拟堆,将给定数组里的数据一一插入堆中(pushHeap)。这样做可以是可以,但如果是代码量未免有点大,而且还要额外开辟空间。如果是在平时考试时碰到这种题,那实现起来会有点麻烦。
如果能在给定数组上直接调整并减少点代码量就好了,今天我们就来讲讲如何这样实现堆排序。
堆排序主要涉及到两个步骤:1.建堆 2.利用堆的思想排序(具体怎么排序后面会讲)
接下来我们以排升序为例进行讲解。
如何建堆?
以建大堆为例:
如果是在原数组的基础上进行建大堆,通过前面的堆的学习和实现,我们可以通过在原数组进行向上调整法或向下调整法达到目的。
向上调整法建堆
我们以这个数组为例:
int a[7]={30,60,56,80,70,10,15};
要将它建成大堆:
从下向上遍历:从数组的第二个元素(下标为1)开始,依次向上遍历到第一个元素(下标为0)。这是因为堆的第一个元素(根节点)在构建过程中不需要进行向上调整。
向上调整:对于遍历到的每个元素(假设当前元素的下标为i),执行以下步骤:
- 计算当前元素的父节点下标(parent = (i - 1) / 2)。
- 比较当前元素与其父节点的值。
- 如果当前元素的值大于其父节点的值,则交换这两个元素的位置。
- 交换后,将当前元素的下标更新为其父节点的下标,并重复上述比较和交换步骤,直到当前元素的值不大于其父节点的值,或者当前元素成为根节点。
- 完成建堆:当遍历完所有元素并完成所有必要的向上调整后,整个数组就形成了一个大顶堆。
附上代码:
void swap(dataType* x, dataType* y)
{dataType tmp = *x;*x = *y;*y = tmp;
}void adjustUp(dataType* a, int child)
{assert(a);int parent = (child - 1) / 2;while (child > 0) {if (a[child] > a[parent]) {swap(&a[parent], &a[child]);child = parent;parent = (child - 1) / 2;}else {break;}}
}void heapSort(int* a, int n) {for (int i = 1; i < n; i++) {adjustUp(a, i);}
}
向下调整法建堆
还是以原来的数组为例:
int a[7]={30,60,56,80,70,10,15};
将它建成大堆:
从最后一个非叶子节点开始,依次向上遍历每个节点(包括根节点)。对于每个节点,执行以下步骤:
- 将当前节点视为父节点。
- 比较其父节点与左右子节点的值(如果存在的话)。
- 如果父节点的值小于左右子节点中的较小值,则交换父节点与较小子节点的值。
- 将交换后的较小子节点视为新的父节点,重复上述比较和交换步骤,直到父节点的值大于或等于其所有子节点的值,或者父节点成为叶子节点。
当所有节点都按照上述步骤调整完毕后,整个数组就形成了一个大顶堆。
这里有一个问题,如何算最后一个出非叶子节点的下标呢?
在一个堆中,最后一个叶子节点的下标是n-1,那它父节点的下标就是(n-1-1)/2也就是(n-2)/2。
附上代码:
void swap(dataType* x, dataType* y)
{dataType tmp = *x;*x = *y;*y = tmp;
}
int getMax(dataType* a, int x, int y)
{if (a[x] > a[y]){return x;}else {return y;}
}
void adjustDown(dataType* a, int parent, int size)
{assert(a);while (parent < size){int lChild = 2 * parent + 1, rChild = 2 * parent + 2, swapChild;if (rChild < size) {swapChild = getMax(a, lChild, rChild);}else {swapChild = lChild;}if (swapChild < size && a[parent] < a[swapChild]) {swap(&a[parent], &a[swapChild]);parent = swapChild;}else {break;}}
}
void heapSort(int* a, int n) {//向下调整法for (int i = (n - 2)/2; i >= 0; i--) {adjustDown(a, i, n);}}
时间复杂度
向上调整法建堆
向下调整法建堆
总结
对比之下,向下调整法建堆的时间复杂度更低,所以我们更推荐用向下调整法建堆。
一个误区:排升序就是直接将原来无序的数组构建成小堆就可以了
说到建堆,无非就两种:要么建大顶堆,要么建小顶堆。但如果是排升序,很多人可能第一反应是直接建小顶堆就可以了。但是这样真的合适吗?我们以这个数组为例:
int a[5]={30,60,56,70,10};
把它建成小顶堆会是这样:
对应的数组就是这样了:int a[5]={10,30,56,70,60};
我们发现这样调整后,数组里面的数据并不是完全按照升序排列的。为什么会这样呢?
我们需要知道,在小堆中,堆顶元素确实是最小的。但是,这并不意味着从堆顶到堆尾的所有元素都按升序排列。小堆的结构只保证了从根节点到每个叶子节点的路径上,元素是递增的。然而,不同路径上的元素之间并没有这样的保证。
所以,直接将原来无序的数组构建成小堆是不可行的办法。
排升序建大堆,排降序建小堆
那我们该怎么办呢?
答案是:排升序建大堆,排降序建小堆。
竟然跟我们最初的想法背道而驰,接下来我会讲解一下为什么要这么做以及具体如何实现。
我们以这个数组为例:
int a[5]={4, 10, 3, 5, 1}
具体步骤
1.我们先把将它建成大堆:
int a[5]={10,5,3,4,10};
2.交换堆顶元素与末尾元素:将堆顶元素(当前最大值)与序列末尾元素交换位置。
这一步骤将最大值“沉”到底部,同时保证了剩余元素组成的子序列仍满足堆的性质。
3.调整剩余元素:将交换后的剩余元素(除去已排序的堆顶元素)重新调整为一个堆。
由于堆顶元素已被移到末尾,此时需要对剩余元素重新执行向下调整操作,使得新的堆顶元素成为剩余元素中的最大值。
4.重复交换与调整:重复步骤2和步骤3,每次都将当前堆顶元素(即剩余部分的最大值)与末尾元素交换,并对剩余元素重新调整为堆。
① ② ③
④ ⑤ ⑥
随着每一次交换和调整,有序序列逐渐扩大,堆的大小逐渐减小。
5.终止条件:当堆的大小减小到1时,整个序列已经有序,堆排序过程结束。
代码
void swap(dataType* x, dataType* y)
{dataType tmp = *x;*x = *y;*y = tmp;
}
int getMax(dataType* a, int x, int y)
{if (a[x] > a[y]){return x;}else {return y;}
}
void adjustDown(dataType* a, int parent, int size)
{assert(a);while (parent < size){int lChild = 2 * parent + 1, rChild = 2 * parent + 2, swapChild;if (rChild < size) {swapChild = getMax(a, lChild, rChild);}else {swapChild = lChild;}if (swapChild < size && a[parent] < a[swapChild]) {swap(&a[parent], &a[swapChild]);parent = swapChild;}else {break;}}
}
void heapSort(int* a, int n) {//向下调整法for (int i = (n - 2)/2; i >= 0; i--) {adjustDown(a, i, n);}int cnt = n - 1;while (cnt) {swap(&a[0], &a[cnt]);adjustDown(a, 0, cnt);--cnt;}}
时间复杂度
前面我们知道,通过向下调整法构建初始堆的时间复杂度为O(n),因为需要对n个元素进行一次向下调整操作。
交换堆顶元素与末尾元素并重新调整堆的过程需要重复n-1次,每次调整的时间复杂度为O(log n)。
因此,总的时间复杂度为O(n log n)。
如果排升序建小堆
在堆排序中,如果目标是进行升序排序,通常我们会选择构建大顶堆,而不是小顶堆。这是因为大顶堆的堆顶元素是最大的,通过不断地将堆顶元素与末尾元素交换并调整堆,可以逐步将序列排序为升序。
然而,如果尝试使用小顶堆来进行升序排序,步骤和弊端如下:
具体步骤
1.建堆:给定一个无序数组int a[5]={4, 10, 3, 5, 1},通过向下调整法将其构建为小顶堆int a[5]={1, 3, 4,5,10};
1
/ \
3 4
/ \
5 10
2.交换与调整:将小顶堆的堆顶元素(当前最小值)与数组末尾元素交换位置。
堆顶元素(1)与数组末尾元素(10)交换:
10
/ \
3 4
/ \
5 1
由于交换后的堆顶元素可能不再是最小值,因此需要对剩余元素重新调整为小顶堆。
对应的数组表示变为:[10, 3, 4, 5, 1],此时,1已经被放到了数组最后的位置,不再参与后续堆化。
但是注意了,剩余元素[10, 3, 4, 5]需要重新调整为小顶堆。但是,不能直接通过下沉操作将3放到堆顶,因为这里的adjustDown函数是从堆顶开始与较大的子节点交换,直到找到合适的位置。在这个情况下,堆顶是10,它是当前堆中的最大值,下沉操作会将它与孩子几点钟较大的那一个孩子节点交换,也就是4,并不能把3交换上去。
正确的重新堆化过程:实际上,我们需要从最后一个非叶子节点开始(在这个例子中是4),向上逐个节点进行堆化,确保每个子树都满足小顶堆的性质。
然后,我们再将根节点(10)与其子节点中较小的一个进行交换,直到整个堆满足小顶堆的性质。
这个过程比简单地下沉操作要复杂得多,因为它可能涉及到多次交换和比较。
3.重复步骤:重复步骤2,每次都将新的堆顶元素(当前最小值)与数组末尾元素交换,并重新调整剩余元素为小顶堆。
弊端(与大顶堆相比)
一、效率低下
- 小顶堆:
- 每次交换到末尾的是当前最小值。
- 需要对整个堆进行重新调整以得到下一个最小值。
- 迭代过程中每次都需要进行调整,导致整体效率低下。
- 大顶堆:
- 每次交换到末尾的是当前最大值。
- 剩余元素自然形成新的堆,只需对堆顶元素进行向下调整。
- 每次迭代的时间复杂度相对较低。
二、稳定性问题
- 小顶堆升序排序时,每次交换最小值可能破坏相同元素的相对顺序,稳定性问题更突出。
三、逻辑复杂性
- 小顶堆用于升序排序反直觉。
- 升序排序目标为从小到大排列,大顶堆堆顶为当前最大值,更符合需求。
- 小顶堆需每次交换后进行复杂调整操作。
ps:在第一次学习数据结构的时候,我并没有学过堆排序。这篇博客也是本人到目前为止写的最心累的一篇博客!其实如果掌握好了前面堆的实现的内容,堆排序理解和实现起来并不难。所以前面的内容一定要理解到位!
相关文章:

堆排序(含证明)
引言 前面我们讲过堆的基本操作的实现,现在给定一个int类型的数组,里面存放的数据是无序的,我们如何利用堆的思想来实现数组内数据的升序排列或降序排列呢? 通过前面讲到的堆的实现,我们可以想到,我们再开…...

蓝桥杯模拟题不知名题目
题目:p是一个质数,但p是n的约数。将p称为是n的质因数。求2024最大质因数。 #include<iostream> #include<algorithm> using namespace std; bool fun(int x) {for(int i 2 ; i * i < x ; i){if(x % i 0)return false;}return true; } int main() …...
C#中的工厂模式
在C#中,工厂模式(Factory Pattern) 是一种常见的设计模式,它属于创建型模式,主要用于定义一个用于创建对象的接口,让子类决定实例化哪一个类。通过使用工厂模式,客户端代码不需要直接实例化具体…...

深度学习与持续学习:人工智能的未来与研究方向
文章目录 1. 持续学习与深度学习1.1 深度学习的局限1.2 持续学习的定义 2. 目标与心智2.1 奖励假说2.2 心智的构成 3. 对研究方法的建议3.1 日常写作记录3.2 中立对待流行趋势 1. 持续学习与深度学习 1.1 深度学习的局限 深度学习注重“瞬时学习”,如ChatGPT虽在语…...

OGRE 3D----4. OGRE和QML共享opengl上下文
在现代图形应用开发中,OGRE(Object-Oriented Graphics Rendering Engine)和QML(Qt Modeling Language)都是非常流行的工具。OGRE提供了强大的3D渲染能力,而QML则用于构建灵活的用户界面。在某些应用场景中,我们需要在同一个应用程序中同时使用OGRE和QML,并且共享OpenGL…...
【Umi】常用配置
具体见:alias 1. 基础配置 1)配置别名alias 2)配置sourcemap devtool 配置项 3)添加hash 4)图片转base64 inlineLimit 配置项 5)设置JS压缩方式 jsMinifier (webpack) 、jsMinifierOptions 配置项 6)设置umi插件 plugins 配置项 7)设置打包后资源导入的路…...
Windows加固脚本
echo off REM 清屏 cls title 安全策略设置批处理 color f0 echo **************************************** echo write by afei echo https://www.jianshu.com/u/ea4c85fbe8c7 echo **************************************** pause cls color 3f echo ********************…...

玩游戏常常出现vc++runtime library error R6025 这是什么意思,该怎么解决?
当玩游戏时常常出现“vc runtime library error R6025”错误,这通常表明微软C开发运行库组件存在问题。以下是对该错误及其解决方法的详细解释: 错误含义 “vc runtime library error R6025”是一个与Visual C运行时库相关的错误,该错误表明…...

AGX orin下电控制
AGX orin下电主要有两种,一种通过软件控制下电,另一种通过按键强制关机。下电流程和电脑关机流程类似。 AGX orin核心板与扩展板 AGX orin核心板由英伟达生产,不提供原理图,通过下图所示连接器与扩展板连接。 AGX orin扩展板&am…...
flutter 报错 error: unable to find git in your path.
项目issue:WIndows: "Unable to find git in your PATH." if terminal is not in admin mode Issue #123995 flutter/flutter 解决办法, 方法一:每次想要运行flutter的时候以管理员方式运行,比如以管理方式运行vsco…...

芯科科技率先支持Matter 1.4,推动智能家居迈向新高度
Matter 1.4引入核心增强功能、支持新设备类型,持续推进智能家居互联互通 近日,连接标准联盟(Connectivity Standard Alliance,CSA)发布了Matter 1.4标准版本。作为连接标准联盟的重要成员之一,以及Matter标…...

C语言数据相关知识:静态数据、越界与溢出
1、静态数组 在 C 语言中,数组一旦被定义后,占用的内存空间就是固定的,容量就是不可改变的,既不能在任何位置插入元素,也不能在任何位置删除元素,只能读取和修改元素,我们将这样的数组称为静态…...

文本分析之余弦相似度
余弦相似度(Cosine Similarity)是一种用于衡量两个非零向量之间相似度的指标,尤其常用于文本分析和自然语言处理领域。其核心思想是通过计算两个向量的夹角余弦值来评估它们的相似性。具体而言,余弦相似度的值范围从-1到1,其中1表示两个向量完全相同,0表示它们之间没有相…...
【VUE3】【Naive UI】<n-button> 标签
【VUE3】【Naive UI】<n-button> 标签 **type**- 定义按钮的类型,这会影响按钮的颜色和样式。**size**- 设置按钮的大小。**disabled**- 布尔值,控制按钮是否处于禁用状态。**loading**- 布尔值,表示按钮是否处于加载状…...
css使盒子在屏幕的地点固定
在 CSS 中,要将一个元素固定在页面的某个位置,可以使用 position: fixed 属性。以下是详细的代码示例和中文解释: <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta n…...
Transformers快速入门代码解析(六):注意力机制——Transformer Encoder:执行顺序解析
Transformer Encoder:执行顺序解析 引言执行顺序解析1. 设置模型检查点和分词器2. 输入预处理操作说明: 3. 加载模型配置configconfig 包含的主要参数常见配置(BERT-base) 4. 初始化 TransformerEncoder5. Transformer Encoder 的…...
图像小波去噪与总变分去噪详解与Python实现
目录 图像小波去噪与总变分去噪详解与实现1. 基础概念1.1 噪声类型及去噪问题定义1.2 小波去噪算法基础1.3 总变分去噪算法基础2. 小波去噪算法2.1 理论介绍2.2 Python实现及代码详解2.3 案例分析3. 总变分去噪算法3.1 理论介绍3.2 Python实现及代码详解3.3 案例分析4. 两种算法…...

【深度学习基础】预备知识 | 微积分
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上,结合当代大数据和大算力的发展而发展出来的。深度学习最重…...
CTF-PWN glibc源码阅读[1]: 寻找libc中堆结构的定义(2.31-0ubuntu9.16)
源代码在这里下载 来到malloc/malloc.c 在980行发现这段代码 // 定义最大 mmap 值为 -4 #define M_MMAP_MAX -4// 如果没有定义 DEFAULT_MMAP_MAX,则将其定义为 65536 #ifndef DEFAULT_MMAP_MAX #define DEFAULT_MMAP_MAX (65536) #endif// 引…...

宏集eXware物联网网关在水务管理系统上的应用
一、前言 水务管理系统涵盖了对城市水网、供水、排水、污水处理等多个环节的监控与管理。随着物联网(IoT)技术的快速发展,物联网网关逐渐成为水务管理系统中的关键组成部分。 宏集物联网网关以其高效的数据采集、传输和管理功能,…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...