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

【算法精解】堆排序(Heap Sort)原理、实现与深度解析(C++版)

1. 引言

在众多的O(n \log n)排序算法中,堆排序(Heap Sort)以其不需要额外辅助空间(原地排序)和稳定的最坏时间复杂度而著称。它巧妙地利用了“完全二叉树”的逻辑结构来管理数组元素。


2. 堆排序的核心思想

堆排序主要分为两个阶段:

  1. 建堆(Build Heap):将一个无序序列调整为一个大顶堆(或小顶堆)。

  2. 排序(Sort Iteration):不断将堆顶元素(当前最大值)交换到数组末尾,并重新调整堆。


3. 代码实现

以下是基于 C++ 的堆排序实现,包含详细的注释说明:

C++
#include <iostream> #include <cstdlib> #include <ctime> using namespace std; /** * 堆的下沉调整(siftDown) * @param arr 待调整数组 * @param i 当前需要下沉的节点索引 * @param size 当前有效堆的大小 */ void siftDown(int arr[], int i, int size) { int val = arr[i]; // 暂存当前节点值 while (2 * i + 1 < size) { // 只要有左孩子,就尝试下沉 int child = 2 * i + 1; // 左孩子索引 // 如果有右孩子,且右孩子的值比左孩子大,则选择右孩子 if (child + 1 < size && arr[child + 1] > arr[child]) { child++; } // 如果孩子节点比父亲节点大,则将孩子上移 if (arr[child] > val) { arr[i] = arr[child]; i = child; // 更新索引,继续向下比较 } else { break; // 满足大顶堆性质,跳出 } } arr[i] = val; // 将最初的节点值放入最终正确位置 } /** * 堆排序主函数 */ void HeapSort(int arr[], int size) { // 阶段一:建堆 // 从最后一个非叶子节点 (size-2)/2 开始向上遍历 for (int i = (size - 2) / 2; i >= 0; i--) { siftDown(arr, i, size); } // 阶段二:排序 // 每次将堆顶(最大值)交换到末尾,并缩小堆范围重新调整 for (int i = size - 1; i > 0; i--) { swap(arr[0], arr[i]); // 交换堆顶和当前末尾 siftDown(arr, 0, i); // 重新对堆顶进行下沉调整,此时堆大小为 i } } int main() { int arr[10]; srand(time(0)); cout << "排序前: "; for (int i = 0; i < 10; i++) { arr[i] = rand() % 100; cout << arr[i] << " "; } cout << endl; HeapSort(arr, 10); cout << "排序后: "; for (int v : arr) { cout << v << " "; } cout << endl; return 0; }

4. 关键点深度解析

为什么建堆从 开始?(size - 2) / 2

在完全二叉树的顺序存储中,若索引从 开始:0

  • 最后一个元素的索引是 。size - 1

  • 它的父节点索引是 ,即 。(i - 1) / 2(size - 1 - 1) / 2 = (size - 2) / 2

  • 叶子节点本身已经满足堆的性质,所以我们从最后一个非叶子节点开始调整,效率更高。

复杂度分析
  • 时间复杂度

    • 建堆:O(n)

    • 排序:O(n \log n)

    • 总体复杂度始终保持在O(n \log n)。

  • 空间复杂度:O(1),属于原地排序(In-place)。

  • 稳定性不稳定排序(在交换堆顶和末尾元素时可能会破坏相同元素的相对顺序)。


5. 总结

堆排序在处理海量数据的Top-K 问题时表现非常出色。如果你需要在一个不适合大量内存占用的环境下进行快速排序,堆排序是一个比快速排序更稳健的选择,因为它不会退化到$O(n^2)$。

http://www.jsqmd.com/news/532079/

相关文章:

  • 5分钟高效配置:Markdown语法高亮让Notepad++编辑体验飙升
  • 实战指南:如何用STORM系统高效生成学术级研究报告
  • Kubernetes 探针与滚动更新实战:从原理到生产配置
  • 多模态实践:Qwen3-ForcedAligner-0.6B与图像识别联合分析
  • Docker镜像拉取终极指南:无需Docker环境也能轻松获取镜像
  • 实测腾讯 QClaw:3 分钟部署,微信远程操控电脑,打工人狂喜
  • 5大维度掌握Unity语音交互:从技术原理到跨平台落地实践
  • 从Mask R-CNN到SAM:实例分割模型怎么选?我的项目实战经验与避坑指南
  • GBase 8a数据库运维管理系统GDOM核心功能备份恢复介绍
  • SitemapGenerator深度解析:Ruby企业级网站地图生成架构揭秘
  • tao-8k入门必看:零基础部署8K Embedding模型,支持中文长文本向量化
  • 从零到大师:用Awesome Claude Skills打造专业AI设计工作流
  • 计算机毕业设计:基于Python与协同过滤的美食推荐系统 Django框架 可视化 协同过滤推荐算法 菜谱 食品 机器学习(建议收藏)✅
  • Qwen3多风格字幕展示:科技感、简约风、手写体效果对比
  • N10 ARM中断
  • AI也开始“说谎”了?3·15曝光的“投毒”黑产,正在操控你的每一次提问
  • 信创生态下的国产存储技术路径:从CPU到数据库的全链路验证
  • 【MCP连接器接入黄金标准】:基于127个生产环境案例总结的7类典型失败场景与对应诊断命令集
  • Python内存泄漏检测失效?:揭秘CPython 3.11+新增的__tracing__机制与自定义GC钩子实战(含GitHub Star 2.4k工具链深度集成)
  • 哔哩下载姬进阶指南:从高效下载到专业处理的全方位解决方案
  • 3种突破限制的MTK设备控制方案:MTKClient全场景应用指南
  • 杰理之短距离滑动触摸逻辑如下【篇】
  • 像素幻梦创意工坊案例分享:为开源RPG引擎生成全系像素道具图标集
  • 中国典型城市建筑物实例数据集:高精度遥感影像标注与应用指南
  • Android APK安装失败全攻略:从错误代码到机型适配
  • LangChain实战:用SQLite为AI对话系统添加持久化记忆(附完整代码)
  • Qwen-Image-2512-Pixel-Art-LoRA 惊艳案例:生成社交媒体像素风海报与头像
  • 基于Phi-3-mini-4k-instruct的MySQL数据库智能查询优化
  • cv_unet图像抠图WebUI效果展示:高清人像抠图作品集,边缘自然流畅
  • Abaqus自动化仿真进阶:如何用Python+批处理打造“无人值守”仿真工作流