转: 堆排序算法 讲解的比较清晰

本文深入解析堆排序算法,从大根堆和小根堆的概念出发,详细介绍堆排序的基本步骤,包括构造堆和固定最大值再构造堆的过程。通过具体示例,清晰展示如何将无序数组转化为有序数组。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u010452388/article/details/81283998
堆排序的时间复杂度O(N*logN),额外空间复杂度O(1),是一个不稳定性的排序

目录

一 准备知识

1.1  大根堆和小根堆

二 堆排序基本步骤

2.1 构造堆

2.2 固定最大值再构造堆

三 总结

四 代码

 

一 准备知识
堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序,下面先来看看什么是大根堆和小根堆

1.1  大根堆和小根堆
性质:每个结点的值都大于其左孩子和右孩子结点的值,称之为大根堆;每个结点的值都小于其左孩子和右孩子结点的值,称之为小根堆。如下图

 

 我们对上面的图中每个数都进行了标记,上面的结构映射成数组就变成了下面这个样子

 

还有一个基本概念:查找数组中某个数的父结点和左右孩子结点,比如已知索引为i的数,那么

1.父结点索引:(i-1)/2(这里计算机中的除以2,省略掉小数)

2.左孩子索引:2*i+1

3.右孩子索引:2*i+2

所以上面两个数组可以脑补成堆结构,因为他们满足堆的定义性质:

大根堆:arr(i)>arr(2*i+1) && arr(i)>arr(2*i+2)

小根堆:arr(i)<arr(2*i+1) && arr(i)<arr(2*i+2)

二 堆排序基本步骤
基本思想:

1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端

2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1

3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组

2.1 构造堆
将无序数组构造成一个大根堆(升序用大根堆,降序就用小根堆)

假设存在以下数组

 

主要思路:第一次保证0~0位置大根堆结构(废话),第二次保证0~1位置大根堆结构,第三次保证0~2位置大根堆结构...直到保证0~n-1位置大根堆结构(每次新插入的数据都与其父结点进行比较,如果插入的数比父结点大,则与父结点交换,否则一直向上交换,直到小于等于父结点,或者来到了顶端)

插入6的时候,6大于他的父结点3,即arr(1)>arr(0),则交换;此时,保证了0~1位置是大根堆结构,如下图:

 

                                     (友情提示:待交换的数为蓝色,交换后的数为绿色)

 插入8的时候,8大于其父结点6,即arr(2)>arr(0),则交换;此时,保证了0~2位置是大根堆结构,如下图

 

插入5的时候,5大于其父结点3,则交换,交换之后,5又发现比8小,所以不交换;此时,保证了0~3位置大根堆结构,如下图 

 

插入7的时候,7大于其父结点5,则交换,交换之后,7又发现比8小,所以不交换;此时整个数组已经是大根堆结构 

 

 

2.2 固定最大值再构造堆
此时,我们已经得到一个大根堆,下面将顶端的数与最后一位数交换,然后将剩余的数再构造成一个大根堆

 

                                    (友情提示:黑色的为固定好的数字,不再参与排序) 

 此时最大数8已经来到末尾,则固定不动,后面只需要对顶端的数据进行操作即可,拿顶端的数与其左右孩子较大的数进行比较,如果顶端的数大于其左右孩子较大的数,则停止,如果顶端的数小于其左右孩子较大的数,则交换,然后继续与下面的孩子进行比较

下图中,5的左右孩子中,左孩子7比右孩子6大,则5与7进行比较,发现5<7,则交换;交换后,发现5已经大于他的左孩子,说明剩余的数已经构成大根堆,后面就是重复固定最大值,然后构造大根堆

 

如下图:顶端数7与末尾数3进行交换,固定好7,

 

剩余的数开始构造大根堆 ,然后顶端数与末尾数交换,固定最大值再构造大根堆,重复执行上面的操作,最终会得到有序数组

 

 

三 总结
到这里,大家应该对堆排序都有了自己的见解,我们对上面的流程总结下:

1、首先将无需数组构造成一个大根堆(新插入的数据与其父结点比较)

2、固定一个最大值,将剩余的数重新构造成一个大根堆,重复这样的过程

四 代码
代码中主要两个方法:

1、将待排序数组构造成一个大根堆(元素上升)

2、固定一个最大值,将剩余的数再构造成一个大根堆(元素下降)

//堆排序
public static void heapSort(int[] arr) {
//构造大根堆
heapInsert(arr);
int size = arr.length;
while (size > 1) {
//固定最大值
swap(arr, 0, size - 1);
size--;
//构造大根堆
heapify(arr, 0, size);

}

}

//构造大根堆(通过新插入的数上升)
public static void heapInsert(int[] arr) {
for (int i = 0; i < arr.length; i++) {
//当前插入的索引
int currentIndex = i;
//父结点索引
int fatherIndex = (currentIndex - 1) / 2;
//如果当前插入的值大于其父结点的值,则交换值,并且将索引指向父结点
//然后继续和上面的父结点值比较,直到不大于父结点,则退出循环
while (arr[currentIndex] > arr[fatherIndex]) {
//交换当前结点与父结点的值
swap(arr, currentIndex, fatherIndex);
//将当前索引指向父索引
currentIndex = fatherIndex;
//重新计算当前索引的父索引
fatherIndex = (currentIndex - 1) / 2;
}
}
}
//将剩余的数构造成大根堆(通过顶端的数下降)
public static void heapify(int[] arr, int index, int size) {
int left = 2 * index + 1;
int right = 2 * index + 2;
while (left < size) {
int largestIndex;
//判断孩子中较大的值的索引(要确保右孩子在size范围之内)
if (arr[left] < arr[right] && right < size) {
largestIndex = right;
} else {
largestIndex = left;
}
//比较父结点的值与孩子中较大的值,并确定最大值的索引
if (arr[index] > arr[largestIndex]) {
largestIndex = index;
}
//如果父结点索引是最大值的索引,那已经是大根堆了,则退出循环
if (index == largestIndex) {
break;
}
//父结点不是最大值,与孩子中较大的值交换
swap(arr, largestIndex, index);
//将索引指向孩子中较大的值的索引
index = largestIndex;
//重新计算交换之后的孩子的索引
left = 2 * index + 1;
right = 2 * index + 2;
}

}
//交换数组中两个元素的值
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
                                                              友情提示:手机观看,可以左右滑动 
————————————————
版权声明:本文为CSDN博主「阿顾同学」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u010452388/article/details/81283998

转载于:https://www.cnblogs.com/swing07/p/11444312.html

6/2025 MP4 出版 |视频: h264, 1280x720 |音频:AAC,44.1 KHz,2 Ch 语言:英语 |持续时间:12h 3m |大小: 4.5 GB 通过实际 NLP 项目学习文本预处理、矢量化、神经网络、CNN、RNN 和深度学习 学习内容 学习核心 NLP 任务,如词汇切分、词干提取、词形还原、POS 标记和实体识别,以实现有效的文本预处理。 使用 One-Hot、TF-IDF、BOW、N-grams 和 Word2Vec 将文本换为向量,用于 ML 和 DL 模型。 了解并实施神经网络,包括感知器、ANN 和数学反向传播。 掌握深度学习概念,如激活函数、损失函数和优化技术,如 SGD 和 Adam 使用 CNN 和 RNN 构建 NLP 和计算机视觉模型,以及真实数据集和端到端工作流程 岗位要求 基本的 Python 编程知识——包括变量、函数和循环,以及 NLP 和 DL 实现 熟悉高中数学——尤其是线性代数、概率和函数,用于理解神经网络和反向传播。 对 AI、ML 或数据科学感兴趣 – 不需要 NLP 或深度学习方面的经验;概念是从头开始教授的 描述 本课程专为渴望深入了解自然语言处理 (NLP) 和深度学习的激动人心的世界的人而设计,这是人工智能行业中增长最快和需求最旺盛的两个领域。无论您是学生、希望提升技能的在职专业人士,还是有抱负的数据科学家,本课程都能为您提供必要的工具和知识,以了解机器如何阅读、解释和学习人类语言。我们从 NLP 的基础开始,从头开始使用文本预处理技术,例如分词化、词干提取、词形还原、停用词删除、POS 标记和命名实体识别。这些技术对于准备非结构化文本数据至关重要,并用于聊天机器人、翻译器和推荐引擎等实际 AI 应用程序。接下来,您将学习如何使用 Bag of Words、TF-IDF、One-Hot E
内容概要:本文全面介绍了虚幻引擎4(UE4)的功能、应用场景、学习准备、基础操作、蓝图系统、材质与纹理、灯光与渲染等方面的内容。UE4是一款由Epic Games开发的强大游戏引擎,支持跨平台开发,广泛应用于游戏、虚拟现实、增强现实、建筑设计等领域。文章详细阐述了学习UE4前的硬件和软件准备,包括最低和推荐配置,以及Epic Games账户创建、启动器安装等步骤。接着介绍了UE4的界面组成和基本操作,如视口、内容浏览器、细节面板等。蓝图系统作为UE4的可视化脚本工具,极大降低了编程门槛,通过实例演练展示了蓝图的应用。材质与纹理部分讲解了材质编辑器的使用和纹理导入设置,灯光与渲染部分介绍了不同类型的灯光及其应用,以及后期处理和高质量图片渲染的方法。最后推荐了一些学习资源,包括官方文档、教程网站、论坛社区和书籍。 适合人群:对游戏开发感兴趣、希望学习UE4的初学者和有一定编程基础的研发人员。 使用场景及目标:①掌握UE4的基本操作和界面认知,为后续深入学习打下基础;②通过蓝图系统快速创建游戏逻辑,降低编程门槛;③学会材质与纹理的创建和设置,提升游戏画面的真实感;④掌握灯光与渲染技术,营造逼真的游戏氛围;⑤利用推荐的学习资源,加速UE4的学习进程。 阅读建议:本文内容详尽,涵盖了UE4的各个方面,建议读者按照章节顺序逐步学习,先从基础操作入手,再深入到蓝图、材质、灯光等高级功能。在学习过程中,结合实际项目进行练习,遇到问题时参考官方文档或社区论坛,不断积累经验和技能。
<think>嗯,用户想了解堆排序的实现和详解。首先,我需要回忆一下堆排序的基本原理。堆排序是基于二叉堆的一种排序算法,主要分为建堆和排序两个阶段。建堆的时候,需要将数组调整成最大堆或最小堆,这里通常用的是最大堆,因为排序时每次把堆顶的最大元素放到后面。 接下来,我得考虑用户可能需要详细的步骤说明。比如,如何构建最大堆,如何进行堆调整(heapify)。堆调整是关键,需要比较当前节点和左右子节点,找到最大的那个,如果当前节点不是最大的,就交换,然后递归调整受影响的子树。建堆的过程是从最后一个非叶子节点开始,向前遍历,对每个节点进行堆调整。 然后,用户可能需要代码示例。根据引用里的资料,Python和Java的实现都有。用户之前的问题提到Python的快速排序代码,所以可能更倾向于Python示例。不过引用4里已经有一个Python的堆排序代码,可以借鉴。需要确保代码正确,语法正确,比如heapify函数里的比较和交换逻辑。 还要注意解释代码的每一步,比如建堆时为什么从n//2 -1开始,因为这是最后一个非叶子节点的索引。排序阶段,每次把堆顶元素(最大值)和数组末尾交换,然后缩小堆的大小,重新调整堆。 用户可能还想知道时间复杂度和空间复杂度。堆排序的时间复杂度是$O(n \log n)$,空间复杂度是$O(1)$,因为原地排序。但需要解释为什么是不稳定的排序,比如相同元素可能在交换过程中改变相对顺序。 另外,优化方法也是用户可能关心的,比如如何优化堆的构建,或者在实际应用中的使用场景。引用里提到堆排序适合数据量大且对内存有限制的情况,因为它是原地排序。还可以提到堆排序在优先队列中的应用。 可能用户还会想知道堆排序的优缺点。比如优点是不需要额外空间,时间复杂度较好;缺点是稳定性差,缓存不友好,因为每次调整堆的时候访问的数据比较分散。 生成相关问题的时候,要考虑用户可能的进一步疑问,比如时间复杂度分析、优化方法、应用场景,或者和其他排序算法比较。比如,堆排序和快速排序的区别,或者堆排序在实际中的使用例子。 需要确保回答结构清晰,分步骤讲解,代码部分正确且注释详细。同时,引用对应的参考资料,比如引用4中的Python代码可以作为示例,引用1和2提到的优化和应用场景可以作为补充。</think>### 堆排序算法实现及详解 #### 一、算法原理 堆排序是基于**完全二叉树结构(堆)**的排序算法,核心思想是通过构建最大堆(或最小堆)实现元素的逐层筛选。时间复杂度为$O(n \log n)$,空间复杂度为$O(1)$,是原地排序算法但不稳定[^1][^4]。 #### 二、算法步骤 1. **构建最大堆** 从最后一个非叶子节点开始(索引为$n//2-1$),自底向上调整堆,确保父节点值≥子节点值[^3]。 $$ \text{父节点索引:} i \rightarrow \text{左子节点:}2i+1,\ \text{右子节点:}2i+2 $$ 2. **排序阶段** 重复交换堆顶元素(最大值)与当前堆末尾元素,缩小堆范围并重新调整堆结构。 #### 三、Python代码实现 ```python def heapify(arr, n, i): largest = i # 标记当前节点为最大值 left = 2 * i + 1 # 左子节点索引 right = 2 * i + 2 # 右子节点索引 # 比较左子节点与父节点 if left < n and arr[left] > arr[largest]: largest = left # 比较右子节点与当前最大值 if right < n and arr[right] > arr[largest]: largest = right # 若最大值位置变化,交换并递归调整 if largest != i: arr[i], arr[largest] = arr[largest], arr[i] heapify(arr, n, largest) def heap_sort(arr): n = len(arr) # 构建最大堆(从最后一个非叶子节点开始) for i in range(n//2-1, -1, -1): heapify(arr, n, i) # 逐次提取最大值并调整堆 for i in range(n-1, 0, -1): arr[0], arr[i] = arr[i], arr[0] # 交换堆顶与末尾元素 heapify(arr, i, 0) # 调整剩余元素为最大堆 return arr ``` #### 四、关键分析 1. **时间复杂度** - 建堆阶段:$O(n)$(通过数学推导证明) - 排序阶段:每次调整堆$O(\log n)$,共$n-1$次 → 总时间$O(n \log n)$[^4] 2. **稳定性** 元素交换可能破坏相同值的相对顺序,例如序列$[5_a, 5_b]$可能变为$[5_b, 5_a]$[^2]。 3. **优化方向** - 减少递归调用:将`heapify`改为迭代实现 - 堆构建优化:Floyd算法加速建堆[^1] #### 五、应用场景 1. 内存受限的大数据排序(如嵌入式系统) 2. 需要实时获取最大/最小值的场景(如优先队列) 3. 混合排序算法中的子模块(如Introsort)[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值