news 2026/8/3 13:58:05

堆排序算法及大小堆区别

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
堆排序算法及大小堆区别

堆排序分两步:

  1. 把数组变成「最大堆」(就像堆苹果,最上面的苹果是最大的);
  2. 把最大的苹果(堆顶)放到数组最后,剩下的苹果重新整理成最大堆,重复这个过程,直到所有苹果排好序。

#include <stdio.h> // 交换两个元素(你的这部分是对的) void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } // 最大堆排序的堆化函数(补全核心逻辑) // n是数组的长度 void heapify(int arr[], int n, int i) { int largest = i; // 初始化最大值为根节点 int left = 2 * i + 1; // 左子节点索引 int right = 2 * i + 2; // 右子节点索引 // 左子节点存在且大于当前最大值 → 更新最大值 if (left < n && arr[left] > arr[largest]) { largest = left; } // 右子节点存在且大于当前最大值 → 更新最大值 if (right < n && arr[right] > arr[largest]) { largest = right; } // 如果最大值不是根节点,交换并递归堆化 if (largest != i) { swap(&arr[i], &arr[largest]); // 交换根节点和最大值节点 heapify(arr, n, largest); // 递归堆化被影响的子树 } } // 堆排序主函数(构建最大堆 + 逐个取出堆顶元素排序) void heapSort(int arr[], int n) { // 1. 构建最大堆(从最后一个非叶子节点开始堆化) for (int i = n / 2 - 1; i >= 0; i--) { heapify(arr, n, i); } // 2. 逐个从堆顶取出元素,放到数组末尾 for (int i = n - 1; i > 0; i--) { swap(&arr[0], &arr[i]); // 把堆顶(最大值)移到数组末尾 heapify(arr, i, 0); // 对剩下的元素重新堆化(堆大小变为i) } } // 打印数组(方便看排序结果) void printArray(int arr[], int n) { for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); } // 关键缺失3:main函数(程序入口,测试堆排序) int main() { int arr[] = {12, 11, 13, 5, 6, 7}; // 测试数组 int n = sizeof(arr) / sizeof(arr[0]); // 计算数组长度 printf("原始数组:"); printArray(arr, n); heapSort(arr, n); // 执行堆排序 printf("排序后数组:"); printArray(arr, n); return 0; }

逐部分讲解代码(按执行顺序来)

第一部分:交换函数(swap)—— 交换两个苹果的位置

c

运行

// 交换两个元素(你的这部分是对的) void swap(int *a, int *b) { int temp = *a; // 先把a的值存到临时盒子temp里 *a = *b; // 把b的值放到a的位置 *b = temp; // 把临时盒子里的a值放到b的位置 }

💡 大白话:比如 a 是 12,b 是 5,执行完 swap 后,a 变成 5,b 变成 12—— 就像把两个苹果互换位置,temp 是临时放苹果的盘子。

第二部分:堆化函数(heapify)—— 把一堆苹果整理成 “最大堆”

这个函数是核心,作用是:给定一个根节点(比如第 i 个苹果),把它和左右子节点比,确保根节点是这一堆里最大的。

c

运行

// 最大堆排序的堆化函数(补全核心逻辑) // n是数组的长度,i是当前要整理的根节点位置 void heapify(int arr[], int n, int i) { int largest = i; // 先假设当前根节点i是最大的(最大的苹果先放顶上) int left = 2 * i + 1; // 左子节点的位置(比如i=0,左子节点就是1;i=1,左子节点就是3) int right = 2 * i + 2; // 右子节点的位置(i=0→2,i=1→4) // 第一步:比左子节点——如果左子节点存在,且比当前最大的大 if (left < n && arr[left] > arr[largest]) { largest = left; // 把“最大”的标签给到左子节点 } // 第二步:比右子节点——如果右子节点存在,且比当前最大的大 if (right < n && arr[right] > arr[largest]) { largest = right; // 把“最大”的标签给到右子节点 } // 第三步:如果最大的苹果不是根节点,就交换位置,再整理被影响的子堆 if (largest != i) { swap(&arr[i], &arr[largest]); // 把根节点和最大的节点互换(最大的苹果放顶上) heapify(arr, n, largest); // 递归整理被交换的子堆(确保子堆也是最大堆) } }

💡 举例子:比如数组是[12,11,13,5,6,7],i=0(根节点是 12):

  • 左子节点是 1(值 11),右子节点是 2(值 13);
  • 比完发现右子节点 13 比根节点 12 大,所以 largest=2;
  • 因为 largest≠i(2≠0),交换 arr [0] 和 arr [2],数组变成[13,11,12,5,6,7]
  • 再递归整理位置 2 的子堆(确保它的子节点也符合最大堆)。
第三部分:堆排序主函数(heapSort)—— 整体整理所有苹果

这个函数分两步:先建最大堆,再逐个取最大的苹果放最后。

c

运行

// 堆排序主函数(构建最大堆 + 逐个取出堆顶元素排序) void heapSort(int arr[], int n) { // 第一步:构建整个数组的最大堆(从最后一个非叶子节点开始整理) // n/2 -1 是最后一个非叶子节点的位置(比如n=6,6/2-1=2,就是位置2的节点) for (int i = n / 2 - 1; i >= 0; i--) { heapify(arr, n, i); // 从后往前整理每个节点,让整个数组变成最大堆 } // 第二步:逐个把堆顶的最大元素放到数组末尾,再重新整理剩下的元素 for (int i = n - 1; i > 0; i--) { swap(&arr[0], &arr[i]); // 把堆顶(最大的苹果)和当前最后一个苹果交换位置 heapify(arr, i, 0); // 剩下的i个苹果重新整理成最大堆(堆大小变成i,排除已经放好的最大苹果) } }

💡 大白话拆解:

  1. 建最大堆:比如数组[12,11,13,5,6,7],建完后变成[13,11,12,5,6,7](堆顶 13 是最大的);
  2. 第一次交换:把 13 和最后一个元素 7 交换,数组变成[7,11,12,5,6,13],13 就固定在最后了;
  3. 重新整理前 5 个元素成最大堆,变成[12,11,7,5,6,13]
  4. 第二次交换:把 12 和倒数第二个元素 6 交换,数组变成[6,11,7,5,12,13],12 固定在倒数第二;
  5. 重复这个过程,直到所有元素排好序。
第四部分:打印数组(printArray)—— 把苹果摆出来看看

c

运行

// 打印数组(方便看排序结果) void printArray(int arr[], int n) { for (int i = 0; i < n; i++) { // 从第一个到最后一个元素 printf("%d ", arr[i]); // 打印每个元素,中间加空格 } printf("\n"); // 打印完换行,看得清楚 }

💡 比如数组是[12,11,13,5,6,7],打印出来就是:12 11 13 5 6 7。

第五部分:主函数(main)—— 程序的 “总开关”

所有代码都要从 main 函数开始执行,就像咱们先打开灯,再整理苹果。

c

运行

// main函数(程序入口,测试堆排序) int main() { int arr[] = {12, 11, 13, 5, 6, 7}; // 准备要排序的苹果(测试数组) int n = sizeof(arr) / sizeof(arr[0]); // 计算有多少个苹果(数组长度) // sizeof(arr)是数组总字节数,sizeof(arr[0])是一个元素的字节数,相除就是元素个数 printf("原始数组:"); printArray(arr, n); // 先打印没排序的苹果 heapSort(arr, n); // 执行堆排序(核心操作:整理苹果) printf("排序后数组:"); printArray(arr, n); // 打印排好序的苹果 return 0; // 程序正常结束(就像整理完苹果,关灯) }

💡 运行结果:

plaintext

原始数组:12 11 13 5 6 7 排序后数组:5 6 7 11 12 13

最大堆 vs 最小堆(用苹果举例)

类型核心特点(大白话)排序效果
最大堆堆顶(最上面)是最大的苹果升序排序(从小到大)
最小堆堆顶(最上面)是最小的苹果降序排序(从大到小)

咱们的代码最终输出是5 6 7 11 12 13(从小到大),就是靠「最大堆」实现的~

再看代码:哪里能看出来是最大堆?

咱们的heapify函数里,核心判断是「找更大的数当堆顶」,这就是最大堆的标志:

c

运行

// 找左子节点里更大的 if (left < n && arr[left] > arr[largest]) { largest = left; } // 找右子节点里更大的 if (right < n && arr[right] > arr[largest]) { largest = right; }
  • 这里用的是>(大于号),意思是 “谁大谁当堆顶” → 最大堆;
  • 如果是最小堆,这里要改成<(小于号),意思是 “谁小谁当堆顶”。

补充:如果想改成最小堆(实现降序排序)

要是您想排成13 12 11 7 6 5(从大到小),只需要改heapify里的两个符号,我把改好的核心代码给您看:

c

运行

// 最小堆的堆化函数(只改两个>为<) void heapify(int arr[], int n, int i) { int smallest = i; // 现在找最小的,变量名也改成smallest更易懂 int left = 2 * i + 1; int right = 2 * i + 2; // 找左子节点里更小的 if (left < n && arr[left] < arr[smallest]) { // 改>为< smallest = left; } // 找右子节点里更小的 if (right < n && arr[right] < arr[smallest]) { // 改>为< smallest = right; } // 如果最小的不是根节点,交换并递归 if (smallest != i) { swap(&arr[i], &arr[smallest]); heapify(arr, n, smallest); } } // 堆排序主函数不用大改,只是现在堆顶是最小的,交换后放最后,得到降序 void heapSort(int arr[], int n) { // 构建最小堆(从最后一个非叶子节点开始) for (int i = n / 2 - 1; i >= 0; i--) { heapify(arr, n, i); } // 逐个把最小的堆顶放到数组末尾 for (int i = n - 1; i > 0; i--) { swap(&arr[0], &arr[i]); heapify(arr, i, 0); } }

改完后运行,结果就是13 12 11 7 6 5(降序),这就是最小堆的效果啦~

总结(

  1. 最大堆,用>找更大的数当堆顶,最终排成「从小到大」;
  2. 最小堆要把>改成<,找更小的数当堆顶,最终排成「从大到小」;
  3. 核心区别就在于heapify里的比较符号 ——>是最大堆,<是最小堆。

最大堆本身不是 “数组从大到小”,而是堆的结构特征;咱们是用最大堆的结构,最终实现数组从小到大排序

第一步:先分清「堆的结构」和「数组的排序结果」

1. 最大堆的本质:“堆结构”≠“数组顺序”

最大堆是一种「树形结构」(咱们用数组存,但逻辑上是树),核心规则只有一个:

每个父节点的苹果,都比它的左右子节点苹果大(堆顶是最大的苹果)。

比如咱们的测试数组[12,11,13,5,6,7],构建成最大堆后,逻辑上的树是这样的

plaintext

13(堆顶,最大) / \ 11 12 / \ / 5 6 7

数组里的存储顺序还是[13,11,12,5,6,7](不是从大到小,只是符合堆的规则)。

2. 为啥用最大堆能排 “从小到大”?

咱们做了关键一步:把堆顶的最大苹果,放到数组最后面 “固定”,再重新整理剩下的苹果成最大堆。用苹果例子一步步走:

  • 第一步:最大堆堆顶是 13(最大),把它和数组最后一个苹果 7 交换 → 数组变成[7,11,12,5,6,13](13 固定在最后,是最大的数);
  • 第二步:把前 5 个苹果[7,11,12,5,6]重新整理成最大堆,堆顶变成 12 → 把 12 和倒数第二个苹果 6 交换 → 数组变成[6,11,7,5,12,13](12 固定在倒数第二);
  • 第三步:把前 4 个苹果整理成最大堆,堆顶是 11 → 交换到倒数第三 → 数组[6,5,7,11,12,13]
  • 重复下去,最后小苹果都跑到前面,大苹果都固定在后面 → 数组就从小到大了。

第二步:如果想让数组 “从大到小”,该怎么做?

用「最小堆」就行!最小堆的规则是 “每个父节点都比子节点小(堆顶是最小的)”,然后把堆顶的最小苹果放到数组最后,最终数组就从大到小了:比如测试数组用最小堆处理:

  • 堆顶是 5(最小),交换到最后 → 数组[12,11,13,7,6,5](5 固定在最后);
  • 再整理前 5 个成最小堆,堆顶是 6 → 交换到倒数第二 → 数组[12,11,13,7,6,5]→ 最终变成[13,12,11,7,6,5](从大到小)。

总结(老奶奶记 3 个关键点)

  1. 最大堆:堆顶是最大的(结构特征),用它排序 → 数组「从小到大」;
  2. 最小堆:堆顶是最小的(结构特征),用它排序 → 数组「从大到小」;
  3. 核心逻辑:把堆顶的 “极值”(最大 / 最小)放到数组末尾固定,重复操作就得到相反顺序的排序结果。

简单记:“最大堆挑大的往后放,越放越大 → 整体从小到大;最小堆挑小的往后放,越放越小 → 整体从大到小”❤️。

总结

  1. swap:交换两个数的位置,就像互换两个苹果;
  2. heapify:整理一堆苹果,确保最上面的是最大的;
  3. heapSort:先把所有苹果整理成 “最大堆”,再把最大的苹果放最后,重复整理剩下的,直到排好序。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/25 15:07:14

Qwen2.5-7B如何稳定推理?RMSNorm归一化部署解析

Qwen2.5-7B如何稳定推理&#xff1f;RMSNorm归一化部署解析 1. 引言&#xff1a;为何Qwen2.5-7B需要稳定的推理架构&#xff1f; 随着大语言模型&#xff08;LLM&#xff09;在实际应用中的广泛落地&#xff0c;推理稳定性和部署效率已成为工程实践中不可忽视的核心问题。阿里…

作者头像 李华
网站建设 2026/8/3 9:32:43

Qwen2.5-7B实战:企业知识库智能问答系统搭建

Qwen2.5-7B实战&#xff1a;企业知识库智能问答系统搭建 1. 背景与需求分析 1.1 企业知识管理的挑战 在现代企业中&#xff0c;知识资产分散于文档、邮件、会议记录、内部Wiki等多个渠道&#xff0c;导致信息检索效率低下。员工在日常工作中常常面临“知道有资料但找不到”的…

作者头像 李华
网站建设 2026/8/1 14:26:53

基于门电路的3线-8线译码器从零实现方案

从零搭建一个3线-8线译码器&#xff1a;不只是“与非门”的艺术你有没有想过&#xff0c;当你在代码里写下case(addr)的那一刻&#xff0c;背后其实是一堆门电路正在默默为你完成“哪一个输出该被激活”的判断&#xff1f;我们每天都在调用库函数、例化IP核&#xff0c;甚至直接…

作者头像 李华
网站建设 2026/7/27 8:16:52

Qwen2.5-7B电商推荐系统实战:8K长文本生成部署教程

Qwen2.5-7B电商推荐系统实战&#xff1a;8K长文本生成部署教程 1. 引言&#xff1a;为何选择Qwen2.5-7B构建电商推荐系统&#xff1f; 1.1 大模型驱动个性化推荐的演进趋势 随着电商平台商品数量和用户行为数据的爆炸式增长&#xff0c;传统协同过滤与浅层机器学习模型在捕捉…

作者头像 李华
网站建设 2026/8/3 13:13:47

字符设备驱动poll机制实现非阻塞读写

深入字符设备驱动的poll机制&#xff1a;如何实现高效非阻塞 I/O你有没有遇到过这样的场景&#xff1f;一个嵌入式系统需要同时监听多个传感器的数据&#xff0c;比如温湿度、加速度计和串口 GPS。如果用传统的轮询方式去读每个设备&#xff0c;CPU 占用率飙升到 80% 以上&…

作者头像 李华
网站建设 2026/7/31 10:51:32

Qwen2.5-7B镜像推荐:支持中英日韩等29种语言的开箱方案

Qwen2.5-7B镜像推荐&#xff1a;支持中英日韩等29种语言的开箱方案 1. 引言&#xff1a;为何选择Qwen2.5-7B作为多语言推理引擎&#xff1f; 1.1 多语言大模型的现实需求 在全球化业务拓展和技术出海的大背景下&#xff0c;企业对跨语言理解与生成能力的需求日益增长。无论是…

作者头像 李华