当前位置: 首页 > article >正文

详解堆排序(超详细)

堆排序

  • 一、 堆的基本概念
  • 二、 堆排序的主要步骤:
    • 1. 构建大顶堆(Max-Heap)
    • 2. 排序过程
    • 3. 堆化的详细过程
    • 4. 堆排序的完整步骤总结
    • 5. 堆排序的时间复杂度
    • 6. 堆排序的空间复杂度
  • 三、 举例讲解
    • 示例数组
    • 第一步:构建大顶堆
      • 1.1 找到最后一个非叶子节点
      • 1.2 从索引 1 开始堆化
      • 1.3 接下来堆化索引 0
      • 1.4 检验并判断是否需要重新堆化
      • 1.5 构建大顶堆结果
    • 第二步:排序过程
      • 2.1 第一次交换
        • 堆化过程:
      • 2.2 第二次交换
        • 堆化过程:
      • 2.3 第三次交换
        • 堆化过程:
      • 2.4 第四次交换
    • 最终排序结果
  • 四、 Python 实现示例
  • 总结

一、 堆的基本概念

堆是一种完全二叉树,可以分为两种类型:

  • 大顶堆(Max Heap):每个结点的值都大于或等于其左右子结点的值。堆顶(根节点)元素是整个堆中最大的。
  • 小顶堆(Min Heap):每个结点的值都小于或等于其左右子结点的值。堆顶(根节点)元素是整个堆中最小的。

堆排序通常使用大顶堆来实现升序排序。排序时,首先构建大顶堆,然后将堆顶元素与数组最后一个元素交换,再对剩余的部分重新构建大顶堆,如此循环,直到整个序列有序。


二、 堆排序的主要步骤:

1. 构建大顶堆(Max-Heap)

堆排序首先需要将输入的无序数组调整为一个大顶堆(Max-Heap)。大顶堆的特点是每个父节点的值都大于或等于其左右子节点的值。通过这个过程,我们可以确保堆顶(根节点)的元素是当前堆中最大的。

  • 具体步骤
    • 从最后一个非叶子节点开始,对每个节点进行“堆化”(heapify)操作,直到根节点。堆化的作用是确保每个子树的结构满足堆的性质。
    • 为什么从最后一个非叶子节点开始?
      因为叶子节点本身就是堆,只有非叶子节点可能不满足堆的性质,因此从最后一个非叶子节点开始堆化,可以确保堆化时每个子树都已经是有效的堆。

假设数组的长度是 n,那么最后一个非叶子节点的索引为 (n // 2) - 1。我们从该节点开始,依次进行堆化,直到堆化到根节点。

  • 堆化(Heapify)
    • 堆化的目标是,确保一个父节点大于其子节点。
    • 对于某个父节点,比较其左右子节点,选出较大的子节点,将父节点与较大的子节点交换位置,递归继续向下堆化,直到堆的性质满足。

2. 排序过程

构建好大顶堆后,我们进入排序阶段。此时,堆顶的元素是当前数组中的最大元素。我们将堆顶元素与数组的最后一个元素交换位置,将最大元素“移出”堆外。然后缩小堆的范围,并对新的堆顶元素执行堆化操作,保证堆顶元素仍然是最大值。重复这个过程,直到堆的大小缩小到 1。

  • 具体步骤
    1. 将堆顶元素(最大元素)与数组的最后一个元素交换。
    2. 将堆的大小减 1(即排好序的部分不再参与堆化)。
    3. 对堆顶元素执行堆化操作,保证剩下的部分仍然是大顶堆。
    4. 重复步骤 1 至步骤 3,直到堆的大小为 1。

3. 堆化的详细过程

堆化是堆排序中非常重要的一步。它的目标是从某个节点开始,调整树的结构使其满足堆的性质(即父节点的值大于或等于子节点的值)。堆化的过程可以分为以下几个步骤:

  1. 比较当前节点和其左右子节点的值,找到三者中最大的一个。
  2. 如果当前节点的值已经大于或等于其左右子节点的值,则堆化过程结束。
  3. 如果当前节点的值小于其子节点,则将当前节点与最大子节点交换。
  4. 交换后,需要继续对交换后的子树进行堆化操作,确保堆的性质。

4. 堆排序的完整步骤总结

  1. 构建大顶堆
    • 从最后一个非叶子节点开始,依次对每个节点进行堆化,构建大顶堆。
  2. 排序过程
    • 将堆顶元素与数组的最后一个元素交换,减小堆的大小。
    • 对新的堆顶元素执行堆化,恢复堆的性质。
    • 重复上述过程直到堆的大小为 1。

5. 堆排序的时间复杂度

  • 构建堆:需要进行 n / 2 次堆化,每次堆化的时间复杂度为 O(log n),所以构建大顶堆的时间复杂度为 O(n)。
  • 排序过程:每次将堆顶元素与最后一个元素交换后,都需要对剩余的部分执行堆化。每次堆化的时间复杂度为 O(log n),一共需要执行 n - 1 次交换,因此排序过程的时间复杂度为 O(n log n)。

因此,堆排序的总时间复杂度为 O(n log n),这是一个较为稳定的时间复杂度。

6. 堆排序的空间复杂度

堆排序是原地排序算法,除了原始数组外,不需要额外的存储空间。所以堆排序的空间复杂度为 O(1)


三、 举例讲解

示例数组

假设初始数组为:

[4, 10, 3, 5, 1]

我们目标是使用大顶堆来排序,使得排序后得到升序排列的数组。

第一步:构建大顶堆

1.1 找到最后一个非叶子节点

对于长度 n=5 的数组,最后一个非叶子节点索引为

floor(n/2) - 1 = floor(5/2) - 1 = 2 - 1 = 1

所以从索引 1 开始往前(包括索引 0)依次堆化。

1.2 从索引 1 开始堆化

索引 1 的元素是 10

  • 左子节点索引:2 * 1 + 1 = 3,对应元素 5
  • 右子节点索引:2 * 1 + 2 = 4,对应元素 1

比较:
10 与 5、1 比较,10 已经大于其两个子节点,无需交换。
堆化后子树保持:[10, 5, 1](索引 1 及其子节点)不变。

1.3 接下来堆化索引 0

索引 0 的元素是 4

  • 左子节点索引:2 * 0 + 1 = 1,对应元素 10
  • 右子节点索引:2 * 0 + 2 = 2,对应元素 3

比较:

  • 当前节点 4,与左子节点 10 和右子节点 3 比较,最大的值为 10(索引 1)。
  • 交换:将 4 与 10 交换,数组变为:
    [10, 4, 3, 5, 1]
    

1.4 检验并判断是否需要重新堆化

交换后,以原索引 1 的位置(现在值为 4)的子树需要重新堆化:

对于索引 1(当前值 4):

  • 左子节点索引:2 * 1 + 1 = 3,对应元素 5
  • 右子节点索引:2 * 1 + 2 = 4,对应元素 1
    比较:
  • 在 4、5、1 中,最大值是 5(索引 3),所以交换 4 与 5。
  • 交换后数组变为:
    [10, 5, 3, 4, 1]
    

索引 3(值 4)是叶子节点,无需继续堆化。

1.5 构建大顶堆结果

经过上述堆化过程,整个数组已经构建成大顶堆,数组状态为:

[10, 5, 3, 4, 1]
  • 根节点 10 为最大值
  • 对应的二叉树结构为:
            10/  \5    3/ \4   1
    

第二步:排序过程

排序过程的目标是不断把堆顶(最大值)交换到数组末尾,然后对剩余部分重新堆化。

2.1 第一次交换

  • 交换堆顶与最后一个元素
    当前数组:[10, 5, 3, 4, 1]
    交换索引 0 与索引 4(最后一个元素):
    交换后数组变为:

    [1, 5, 3, 4, 10]
    
  • 说明:此时最大值 10 固定在最后位置,不参与后续堆化。

  • 对剩余堆(索引 0 到 3)重新堆化
    子数组为:[1, 5, 3, 4],堆的大小为 4。

堆化过程:
  • 从索引 0 开始堆化。
    索引 0 的元素:1

    • 左子节点索引:1,值为 5
    • 右子节点索引:2,值为 3
      比较:最大值为 5(索引 1)。
      交换:将 1 与 5 交换,数组变为:
    [5, 1, 3, 4, 10]
    

    继续堆化索引 1(值为 1):

    • 左子节点索引:3,值为 4
    • 右子节点索引:4,不在堆的范围内(堆大小为 4)
      比较:最大值为 4(索引 3)。
      交换:将 1 与 4 交换,数组变为:
    [5, 4, 3, 1, 10]
    

    索引 3 为叶子节点,堆化结束。

排序后,堆调整完成,堆为:[5, 4, 3, 1],根节点为最大值 5。

2.2 第二次交换

  • 交换堆顶与堆的最后一个元素
    当前数组:[5, 4, 3, 1, 10]
    交换索引 0 与索引 3:
    交换后数组变为:

    [1, 4, 3, 5, 10]
    
  • 说明:当前最大值 5 固定在正确位置。

  • 对剩余堆(索引 0 到 2)重新堆化
    子数组为:[1, 4, 3],堆大小为 3。

堆化过程:
  • 对索引 0(值 1)堆化:

    • 左子节点索引:1,值为 4
    • 右子节点索引:2,值为 3
      最大值为 4(索引 1)。
      交换:将 1 与 4 交换,数组变为:
    [4, 1, 3, 5, 10]
    

    对索引 1(值 1):

    • 左子节点索引:3,不在堆范围内(堆大小为 3)
      堆化结束。

排序后,堆调整完成,堆为:[4, 1, 3]。

2.3 第三次交换

  • 交换堆顶与堆的最后一个元素
    当前数组:[4, 1, 3, 5, 10]
    交换索引 0 与索引 2:
    交换后数组变为:

    [3, 1, 4, 5, 10]
    
  • 固定最大值 4 在位置索引 2。

  • 对剩余堆(索引 0 到 1)重新堆化
    子数组为:[3, 1],堆大小为 2。

堆化过程:
  • 对索引 0(值 3)堆化:
    • 左子节点索引:1,值为 1
      3 大于 1,无需交换。
      堆化结束。

排序后,堆调整完成,堆为:[3, 1]。

2.4 第四次交换

  • 交换堆顶与堆的最后一个元素
    当前数组:[3, 1, 4, 5, 10]
    交换索引 0 与索引 1:
    交换后数组变为:

    [1, 3, 4, 5, 10]
    
  • 此时只剩下一个元素,不需要再堆化。

最终排序结果

经过上述所有交换和堆化,数组最终变为:

[1, 3, 4, 5, 10]

这就是堆排序完成后的结果,数组按升序排列。


四、 Python 实现示例

以下是一个简单的 Python 实现示例,帮助更好地理解具体实现步骤:

# 堆调整函数
def heapify(arr, heap_size, i):# 初始化最大值为当前节点索引largest = i# 计算左子节点的索引left = 2 * i + 1# 计算右子节点的索引right = 2 * i + 2# 如果左子节点存在(索引小于堆大小),且左子节点的值大于当前最大值if left < heap_size and arr[left] > arr[largest]:# 更新最大值索引为左子节点largest = left# 如果右子节点存在(索引小于堆大小),且右子节点的值大于当前最大值if right < heap_size and arr[right] > arr[largest]:# 更新最大值索引为右子节点largest = right# 如果最大值不是当前节点(说明需要调整)if largest != i:# 交换当前节点和最大值节点arr[i], arr[largest] = arr[largest], arr[i]# 递归调整交换后的子树,以保持堆的性质heapify(arr, heap_size, 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)# 示例用法
if __name__ == "__main__":array = [12, 11, 13, 5, 6, 7]  # 定义一个待排序数组print("原始数组:", array)  # 输出原始数组heap_sort(array)  # 调用堆排序函数对数组进行排序print("排序后的数组:", array)  # 输出排序后的数组

总结

我们可以总结堆排序的关键步骤:

  1. 构建大顶堆

    • 从最后一个非叶节点开始,依次对节点进行堆化,构建一个大顶堆。
  2. 排序过程

    • 将堆顶(最大值)与数组末尾交换,把最大值固定到正确位置;
    • 缩小堆的有效范围,对新的堆顶进行堆化,确保剩余部分仍然是大顶堆;
    • 重复以上过程直到排序完成。

相关文章:

详解堆排序(超详细)

堆排序 一、 堆的基本概念二、 堆排序的主要步骤&#xff1a;1. 构建大顶堆&#xff08;Max-Heap&#xff09;2. 排序过程3. 堆化的详细过程4. 堆排序的完整步骤总结5. 堆排序的时间复杂度6. 堆排序的空间复杂度 三、 举例讲解示例数组第一步&#xff1a;构建大顶堆1.1 找到最后…...

思库拉水厂开业庆典千人大会回顾

近日,思库拉离子水厂在广州隆重举办了开业盛典,现场汇聚了逾千名嘉宾。此次盛会不仅是对思库拉离子水厂正式投产的庆祝,更是对思库拉品牌未来蓝图的一次展示。 现场氛围热烈,洋溢着浓厚的喜庆气息。参与者来自五湖四海,既有思库拉的忠实拥趸,也有对思库拉产品充满兴趣的潜在消费…...

react 大屏根据屏幕分辨率缩放

记录&#xff0c;以防忘记 const DataLargeScreen () > {const layoutRef useRef<any>();// ui稿宽度const width useRef(1920).current;// ui稿高度const height useRef(1080).current;const [scaleValue, setScaleValue] useState(1);const useWhichScaleValu…...

JAVA学习*Object类

Object类 Object类是所有类的父类 类中有一些方法&#xff08;都需要掌握&#xff09; toString()方法 在学习类的对象的时候有介绍过了&#xff0c;当我们重新给此方法就会打印类与对象的信息 equals()方法 在Java中的比较&#xff0c; 如果左右两侧是基本类型变量&#…...

基于python脚本实现的打砖块小游戏

目录 1. 打砖块游戏 2. 初始化 Pygame 和设置屏幕 3. 定义游戏对象 3.1 定义玩家操作的paddle 3.2 定义球&#xff08;Ball&#xff09; 3.3 砖块&#xff08;Bricks&#xff09; 4. 游戏主循环 4.1 事件处理 4.2 板子移动 4.3 球移动和碰撞检测 4.4 绘制游戏对象 …...

20250317-vue-Prop4

运行时类型检查 校验选项中的 type 可以是下列这些原生构造函数&#xff1a; StringNumberBooleanArrayObjectDateFunctionSymbolError 另外&#xff0c;type 也可以是自定义的类或构造函数&#xff0c;Vue 将会通过 instanceof 来检查类型是否匹配。例如下面这个类&#xf…...

地理信息系统(GIS)在智慧城市中的40个应用场景案例

在智慧城市发展进程中&#xff0c;地理信息系统&#xff08;GIS&#xff09;作为关键技术之一&#xff0c;正扮演着不可或缺的角色&#xff0c;堪称智慧城市的神经中枢。通过空间数据分析优化城市管理&#xff0c;GIS技术为智慧城市的构建提供了强大的支持。 本文分享了GIS在智…...

XSS Game(DOM型) 靶场 通关

目录 靶场网址 Ma Spaghet! 分析 解题 Jefff 分析 解题 方法一 方法二 Ugandan Knuckles 分析 解题 Ricardo Milos 分析 解题 Ah Thats Hawt 分析 解题 方法一 方法二 Ligma 分析 解题 ​ Mafia 分析 解题 方法一&#xff1a;构造函数 方法二&#xf…...

【大模型基础_毛玉仁】3.5 Prompt相关应用

目录 3.5 相关应用3.5.1 基于大语言模型的Agent3.5.2 数据合成3.5.3 Text-to-SQL3.5.4 GPTs 3.5 相关应用 Prompt工程应用广泛&#xff0c;能提升大语言模型处理基础及复杂任务的能力&#xff0c;在构建Agent、数据合成、Text-to-SQL转换和设计个性化GPTs等方面不可或缺。 . …...

《Python全栈开发》第12课:RESTful API设计 - 构建现代化接口

🌟 课程目标 理解REST设计原则掌握Flask-RESTful开发实现JWT认证接口构建标准化API文档一、REST是什么?(餐厅点餐系统比喻) 1.1 REST核心原则 #mermaid-svg-0rLbveAhUdJCLKTy {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;…...

深入解析libsunrpc:构建分布式系统的核心RPC库

深入解析libsunrpc&#xff1a;构建分布式系统的核心RPC库 引言 在分布式系统开发中&#xff0c;远程过程调用&#xff08;Remote Procedure Call, RPC&#xff09; 是连接不同节点、实现跨网络服务调用的关键技术。作为SUN公司开源的经典RPC实现&#xff0c;libsunrpc 凭借其…...

deepseek搭建本地私有知识库dify安装介绍docker compose图文教程

按照官方介绍&#xff0c;打开教程安装。下载源码&#xff0c; # 假设当前最新版本为 0.15.3 git clone https://github.com/langgenius/dify.git --branch 0.15.3 进入docker目录&#xff0c; cd dify/docker 网络科学的直接执行命令就可以了。 docker compose up -d 镜…...

C语言动态内存管理深度解析与嵌入式开发实战

C语言动态内存管理深度解析与嵌入式开发实战 &#xff08;高级嵌入式软件开发工程师视角&#xff09; ​一、动态内存函数原理与差异 ​malloc ​核心机制&#xff1a;从堆区分配指定字节的未初始化内存&#xff0c;返回void*指针。失败时返回NULL&#xff0c;必须检查返回值…...

右击没有Word、PPT、Excel功能

右击没有Word、PPT、Excel功能 导航 文章目录 右击没有Word、PPT、Excel功能导航一、问题描述二、事情经过三、解决方案其他思路分享 一、问题描述 ​ 在安装并激活了office之后&#xff0c;业务反馈右击没有出现新建Word功能&#xff0c;仅有Word文档 二、事情经过 ​ 按道…...

無人機高空收集地形之linux server 的應用部署

如何在Linux服务器上部署无人机高空地形测量应用&#xff1f; 一、技术实现步骤 系统环境搭建 操作系统与ROS安装 在Linux服务器&#xff08;推荐Ubuntu LTS版本&#xff09;上安装ROS&#xff08;机器人操作系统&#xff09;&#xff0c;例如ROS Noetic或ROS2 Humble1。ROS提…...

DeepSeek R1 本地部署指南 (6) - Windows 本地部署使用 GPU 运行

DeepSeek R1 本地部署指南 (1) - Windows 本地部署 上一篇&#xff0c;安装好 Windows 本地步骤后&#xff0c;如果发现在任务管理器中 GPU 显示 0%。 1.在命令行中输入&#xff1a; ollama ps 显示&#xff1a; PROCESSOR CPU 2.安装 CUDA Toolkit CUDA Toolkit Downloads htt…...

鸿蒙进行视频上传,使用 request.uploadFile方法

一.拉起选择器进行视频选择&#xff0c;并且创建文件名称 async getPictureFromAlbum() {// 拉起相册&#xff0c;选择图片let PhotoSelectOptions new photoAccessHelper.PhotoSelectOptions();PhotoSelectOptions.MIMEType photoAccessHelper.PhotoViewMIMETypes.VIDEO_TY…...

婚姻的解构与重构 | 一场关于选择与责任的探索

注&#xff1a;本文为 “婚姻的解构与重构” 相关文章合辑。 未整理。 明明渴望爱情 为何反感催婚&#xff1f; 原创 常 晋 人民日报评论 2024 年 04 月 22 日 12:29 北京 没有催促指责&#xff0c;也毫无批评之意。面对单身、失业的 30 岁女儿&#xff0c;只是鼓励孩子&…...

jangow靶机攻略

配置网卡 VMware需要配置&#xff0c;不配置扫不到ip,VirtualBox正常打开ip会直接显示出来 网卡配置都改成NAT 打开虚拟机&#xff0c;第一个框选第二行&#xff0c;回车 选第二个&#xff0c;按e键 进入下一个框后&#xff0c;将ro 后面的修改为 rw signin init/bin/bash 按…...

自动化测试框架维护成本高怎么办

自动化测试框架维护成本高&#xff0c;可以通过优化测试用例设计、引入持续集成&#xff08;CI&#xff09;策略、强化代码规范和审查机制、建立明确的维护计划、定期进行技术债务清理等方式来降低成本。 其中&#xff0c;优化测试用例设计尤其关键&#xff0c;它不仅能提高测试…...

日事清在敏捷开发中的实战应用:SCRUM框架下可视化项目管理+高效沟通机制驱动灵活迭代

一、行业背景 在快速发展的互联网行业中&#xff0c;软件开发模式经历了显著的演变。传统的瀑布式开发模式&#xff0c;以其线性和阶段性的特点&#xff0c;曾长期占据主导地位。然而&#xff0c;随着市场对软件迭代速度和灵活性的要求日益提高&#xff0c;敏捷开发模式应运而…...

Buildroot 增加系统启动项并解决后台无法获取输入(串口)

Buildroot 增加自启动项 概述增加模块源码结构编写测试程序编译测试增加系统自启动一个问题解决方案&#xff1a;显式指定输入设备 其他/etc/init.d 目录下的 SXXxxx 文件作用解析‌ 概述 Buildroot 是一款轻量级、高度可定制的开源工具集&#xff0c;专为嵌入式系统打造。它通…...

【Javaweb】b站黑马视频学习笔记

Javaweb学习导览 1.Mysql...

使用ThreadLocal可能导致内存泄漏的原因与其底层实现机制

学海无涯&#xff0c;志当存远。燃心砺志&#xff0c;奋进不辍。 愿诸君得此鸡汤&#xff0c;如沐春风&#xff0c;事业有成。 若觉此言甚善&#xff0c;烦请赐赞一枚&#xff0c;共励学途&#xff0c;同铸辉煌&#xff01; 首先&#xff0c;ThreadLocalThreadLocal的基本原理。…...

OpenHarmony和HarmonyOS到底有什么区别?

HarmonyOS 与 OpenHarmony差异化剖析 背景介绍 HarmonyOS 是华为的闭源商业操作系统&#xff0c;旨在为智能手机、平板和 IoT 设备提供统一的用户体验。而 OpenHarmony 是其开源版本&#xff0c;适合开发者定制各种设备系统。两者共享部分代码&#xff0c;但 API 差异反映了各…...

HTML5 MathML 学习笔记

一、什么是MathML MathML&#xff08;Mathematical Markup Language&#xff09;是一种数学标记语言&#xff0c;用于在互联网上书写数学符号和公式。MathML是一种基于XML的标准&#xff0c;可以用来描述复杂的数学公式和符号&#xff0c;使其能够在网页上正确显示。 MathML的…...

数据库取证分析

目录 一.多表关联 1.一对多联结 2.子查询 二.数据库示例分析 1.多表关联 三.选择SQL分析的原因 四.数据库概述 五.SQL语言 一.多表关联 1.一对多联结 2.子查询 二.数据库示例分析 1.多表关联 三.选择SQL分析的原因 四.数据库概述 五.SQL语言 1.select 字段...

MATLAB 批量移动 TIF 文件至分类文件夹

文章目录 前言一、步骤二、代码 前言 本代码用于从指定的源文件夹 (sourceFolder) 中筛选所有 .tif 文件&#xff0c;并根据文件名的特定关键词&#xff08;Daynight 和 FDI&#xff09;将其分类移动到相应的目标文件夹 (targetDaynightFolder 和 targetFDIFolder)。 一、步骤…...

【深度技术揭秘】 Android SystemUI锁屏界面动态布局重构:横竖屏智能适配指南

1. 问题背景与需求拆解 在Android 13系统定制中&#xff0c;发现平板横屏锁屏界面存在两大视觉问题&#xff1a; 时钟控件尺寸过大&#xff0c;与竖屏样式不统一 解锁图标位置异常&#xff0c;横向居中而非顶部居中&#xff08;如图示&#xff09; 需实现&#xff1a; 横竖屏…...

ESG评级认可性及市场现状分析

ESG评级的认可性是指评级结果在市场上的接受程度和权威性&#xff0c;它直接影响投资者、企业、监管机构等利益相关方对ESG表现的信任和依赖程度。以下是影响ESG评级认可性的关键因素及当前市场现状的分析&#xff1a; 1. 评级机构的权威性 ESG评级的认可性首先取决于评级机构…...