【以无克有】排序之堆结构及堆排序

embedded/2025/2/22 11:15:20/

未察凌云木本根,修枝剪叶自沉浮,终成玉宇最高层。

  • 堆结构
  • 堆排序
    • 堆排序步骤
    • 堆排序原理
    • 堆排序代码实现
      • 堆化(下滤)部分
      • 堆排序部分
  • 复杂度简要分析
  • 例题
  • 参考资料及推荐学习
  • 总结


堆结构

  1. 堆的本质
    完全二叉树:所有层除最后一层外都是满的,且最后一层节点靠左对齐。
  2. 数组存储:利用数组下标表示树结构:
    父节点索引 i → 左子节点 2i+1,右子节点 2i+2
    最后一个非叶子结点索引n/2-1,n为数组长度
  3. 大根堆:父节点值 ≥ 子节点值
  4. 堆的示例
    假设数组 [3, 8, 5, 1, 7],对应的完全二叉树结构为:()内为数组索引,外为数组实际存储值
        3(0)/   \8(1)   5(2)/ \1(3) 7(4)

构建大根堆后变为 [8,7,5,1,3],对应树结构:

        8(0)/   \7(1)   5(2)/ \1(3) 3(4)

堆排序

堆排序步骤

步骤操作目的
1构建大根堆使数组满足堆性质,根节点为最大值
2交换堆顶与末尾元素将当前最大值放到数组末尾
3缩小堆范围并重新堆化对剩余元素调整,恢复堆性质
4重复2-3直到排序完成逐步提取最大值,形成升序序列

  1. 关键操作详解
  • (1) 堆化(Heapify)——下滤操作
    核心思想:确保以 dad 为根的子树满足堆性质
    操作流程:
    比较父节点与左右子节点的值
    若子节点值更大,交换父节点与最大子节点
    递归调整被交换的子节点所在子树

示例:对节点0进行堆化初始树(非大根堆):

    3(0)/   \8(1)  5(2)/ \
1  7

堆化过程:
比较节点0(3)、左子1(8)、右子2(5) → 最大为8
交换节点0和1 → 新根为8
递归处理节点1:比较节点1(3)、左子3(1)、右子4(7) → 最大为7
交换节点1和4 → 子树调整完成
最终大根堆:

    8(0)/   \7(1)  5(2)/ \
1  3
  • (2) 构建大根堆
    起点选择:从最后一个非叶子节点开始(索引 n/2 - 1)
    推导:完全二叉树中,最后一个非叶子节点的索引为 ⌊n/2⌋ - 1
    原因:叶子节点本身已满足堆性质,无需调整

示例:数组 [3,8,5,1,7] 构建大根堆
最后一个非叶子节点索引 = 5/2 -1 = 1(节点1)
调整节点1:子树 [8,1,7] → 交换7和1 → [8,7,1]
调整节点0:子树 [3,8,5] → 交换3和8 → 递归调整节点1

  • (3) 排序阶段
    每次将堆顶(最大值)交换到当前范围的末尾,然后对剩余元素堆化。

示例:堆 [8,7,5,1,3] 排序过程
交换8和3 → 数组变为 [3,7,5,1,8],堆范围缩小为前4个元素
对节点0堆化 → 新堆 [7,3,5,1]
重复交换堆顶7与末尾1 → 数组 [1,3,5,7,8]
继续调整直到完成


堆排序原理

  • 构建最大堆:为什么构建堆要从后往前调整非叶子节点?因为堆化操作是向下调整,必须保证子树已经是堆。从最后一个非叶子节点开始,可以确保每次调整时,子树已经满足堆性质。

  • 堆化(Heapify):通过下滤操作维护堆性质。父节点若不满足堆性质,则与较大子节点交换,并递归调整子树。

  • 排序:每次将最大值交换到数组末尾,逐步缩小堆范围,最终得到升序数组。


堆排序代码实现

堆化(下滤)部分

// 堆化函数(下沉操作)
void heapify(int* arr,int n,int dad)
{int max_idx = dad;    //最大值索引初始为dadint left = 2*dad + 1; //左子int right = 2*dad + 2;//右子if(left < n && arr[left] > arr[max_idx]) max_idx = left;if(right < n && arr[right] > arr[max_idx]) max_idx = right;if(max_idx != dad) //如果最大值索引不是父节点说明三元组变动{swap(arr[max_idx],arr[dad]);  //变动三元组heapify(arr,n,max_idx);       //把变动过的子节点进行递归下滤直到三元组仅有一元素或者满足三元组不变动}
}

堆排序部分

void heap_sort(int* arr,int n)
{// 1. 构建大根堆(如果不先构建大根堆的话,每次下滤堆化操作找出来的不一定是最大值)//从最后一个非叶子结点(数学关系为n/2-1)到顶结点依次作为父节点进行三元组操作for(int i=n/2-1;i>=0;--i) heapify(arr,n,i);// 2.不断把大根堆的顶部,也就是剩余元素的最大值调转到数组末尾,再进行堆化下滤找出剩余元素次大的元素for(int i=n-1;i>0;--i) //i只用来控制末尾{swap(arr[0],arr[i]);heapify(arr,i,0);  //每次都是从头下滤到数组长度i,否则每次都是滤出最大值}
}

复杂度简要分析

  1. 初始构建大根堆部分的时间复杂度

每个节点的高度:假设树高度为h,节点在第k层(根为第0层),则高度为 h - k

总调整次数:Σ (每个节点的高度) = Σ_{k=0}^{h} [2^k * (h - k)]
数学推导:
令 S = 2^0*(h) + 2^1*(h-1) + ... + 2^{h-1}*1
2S = 2^1*(h) + 2^2*(h-1) + ... + 2^h*1
S = 2S - S = 2^{h+1} - h - 2
由于 h ≈ logn,故 S = O(n)

初始构建大根堆的时间复杂度为 O(n)


  1. 排序部分的时间复杂度

每次交换后堆化:需要进行 n-1 次堆化操作,所以时间复杂度为O(n)
每次堆化时间复杂度:O(log n)(递归树的高度)
总时间复杂度:O(n log n)


  1. 总体复杂度

时间复杂度:O(n) + O(n log n) = O(n log n)
空间复杂度:原地排序 → O(1)


例题

模板:【模板】排序


参考资料及推荐学习

【从堆的定义到优先队列、堆排序】 10分钟看懂必考的数据结构——堆

有点抽象的堆排序,一个三分钟的动画教会你

数据结构合集 - 堆与堆排序(算法过程, 效率分析, 稳定性分析)】

算法讲解025【必备】堆结构和堆排序】

【【强烈推荐】算法 - 印度大神图文讲解 - UP主翻译校对 (合计84集,进行中)- - -heapsort 】


总结

堆排序的每一次堆化与交换,皆是混沌与秩序的共舞,亦是算法与诗学的共振。它教会我们:真正的秩序绝非静止的完美,而是动态的平衡——正如叶子节点在沉浮中重构层级,最大值在陨落中让渡新生。拉丁文哲人卢克莱修在《物性论》中写道:“Ex nihilo nihil fit”(无中不能生有),而堆排序却以递归为笔,在虚无的数组中书写出严谨的层次,这恰似人类理性对无序世界的温柔驯服。

若将人生视作一棵不断堆化的树,或许我们终将领悟:每一次“下沉”并非坠落,而是为了在更深的维度扎根;每一次“交换”亦非失去,而是为了在系统熵增中守护局部的最优解。愿这算法的哲思,如维吉尔指引但丁穿越地狱般,带我们于代码的迷雾中窥见永恒的结构之美——Sic transit gloria informaticae(计算之荣,如是流转)。


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

相关文章

Windows 下 Ollama 安装deepseek本地模型

Windows 下 Ollama 安装deepseek本地模型 安装 Ollama 下载 Ollama 下载链接&#xff1a;https://ollama.org.cn/download/windows 下载完成后&#xff0c;按照提示进行安装。 安装过程 安装完成后&#xff0c;安装页面会自动关闭&#xff0c;这是正常现象。 接下来&#…

OpenCV中的边缘检测

边缘检测是图像处理和计算机视觉中的关键技术之一&#xff0c;旨在识别图像中像素强度发生显著变化的区域&#xff0c;这些区域通常对应于物体的边界或轮廓。边缘检测在机器视觉中具有重要的需求背景&#xff0c;主要体现在以下几个方面&#xff1a; 图像分割&#xff1a;边缘…

TypeScript - 属性修饰符

属性修饰符 属性的简写形式 protected 修饰符 private 修饰符 readonly 修饰符

服务搭建 ollama + Deepseek + Open WebUI + 硅基流动API

文章目录 一、ollamaollama 安装ollama 配置 二、 Deepseek 模型下载三、 Open WebUI1、安装 Docker2、安装 WebUI 镜像3、确认镜像安装成功4、客户端访问5、平台使用5、外部服务器6、外部接入硅基流动API I know, i know 地球另一端有你陪我 一、ollama 功能类似 dock…

什么是接口自动化测试?接口自动化测试的目的是什么?

1、什么是接口测试 接口测试是对系统或组件之间的接口的测试。主要用于检测外部系统与系统间以及内部各个子系统间的交互点。测试重点是检查数据交换、传递和控制管理过程&#xff0c;以及系统间的相互逻辑依赖关系等。 2、接口测试的目的 1> 尽早介入软件测试流程&#…

智能硬件-01智能停车场

行业背景 随着现代人们生活水平的提高&#xff0c;私家车辆在不断增加&#xff0c;小区将面临着临时车用户要多于固定车用户的窘境&#xff0c;尤其是在早晚高峰时段车辆出入拥堵&#xff0c;对小区的车辆管理难度越来越大&#xff0c;对停车场收费员的岗位要求越来越高&#…

android13修改系统Launcher不跟随重力感应旋转

android13系统中需要修改系统原生Launcher不跟随重力感应旋转。 通过代码查找发现packages/apps/Launcher3/src/com/android/launcher3/states/RotationHelper.java中存在一个函数getAllowRotationDefaultValue&#xff0c;用于获取是否允许旋转的默认值。 public static bo…

2025最新智能优化算法:鲸鱼迁徙算法(Whale Migration Algorithm,WMA)求解23个经典函数测试集,MATLAB

一、鲸鱼迁徙算法 鲸鱼迁徙算法&#xff08;Whale Migration Algorithm&#xff0c;WMA&#xff09;是2025年提出的一种新颖生物启发式元启发式优化方法&#xff0c;其灵感来源于座头鲸的协作迁徙行为。该算法通过模拟座头鲸的迁徙和捕食行为&#xff0c;实现了在优化过程中的…