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

【数据结构】堆排序

一、堆排序的概念

堆排序(Heapsort):利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。通过堆来进行选择数据,需要注意的是 排升序要建大堆,排降序建小堆。

二、堆排序的实现

我们先创建一个堆排序的函数:

void HeapSort(int arr[], int n);

假设我们要对下列数组来使用堆排序(升序):

int arr[] = {70, 56, 30, 25, 15, 10, 75};

根据我们之前学到的知识,数组是可以直接看为完全二叉树的,所以我们可以把它化为堆。此时我们就可以 "选数" (堆排序本质上是一种选择排序)。

第一步:构建堆

第一步就是要想办法把 arr 数组构建成堆(这里我们先构建成小堆)。我们介绍两种方法,分别为向上调整算法和向下调整算法:

方法1:向上调整

void Swap(HPDataType* px, HPDataType* py) {HPDataType tmp = *px;*px = *py;*py = tmp;
}
/* 小堆的向上调整 */
void AdjustUp(int* arr, int child) {assert(arr);// 首先根据公式计算算出父亲的下标int parent = (child - 1) / 2;// 最坏情况:调到根,child=parent 当child为根节点时结束(根节点永远是0)while(child > 0) {if(arr[child] < arr[parent]) {  // 如果孩子小于父亲(不符合小堆的性质)// 交换他们的值Swap(&arr[child],&arr[parent]); // 传地址// 往上走child = parent;parent = (child - 1) / 2;} else {  // 如果孩子大于父亲(符合小堆的性质)// 跳出循环break;  }}
}

方法1:

/* 升序 */
void HeapSort(int arr[], int n) {for (int i = 1; i < n; i++) {AdjustUp(arr, i);   // 传入数组 和 child的下标}
}

方法2:向下调整

void SmallAjustDown(int* arr, int n, int parent) {int child = parent * 2 + 1; // 默认为左孩子while(child < n) { // 叶子内// 选出左右孩子中小的那一个if(child + 1 < n && arr[child + 1] < arr[child]) {child = child + 1;}// 如果孩子小于父亲(不符合小堆的性质)if(arr[child] < arr[parent]) {  // 交换它们的值Swap(&arr[child], &arr[parent]);// 往下走parent = child;child = parent * 2 + 1;} else { // 如果孩子大于父亲(符合小堆的性质)// 跳出循环break;}}
}

方法2:

/* 升序 */
void HeapSort(int arr[], int n) {for (int i = (n - 1 - 1) / 2; i >= 0; i--) {AdjustDown(arr, n, i);}
}

也可以这样写:

/* 升序 */
void HeapSort(int arr[], int sz) {int father = ((sz - 1) - 1) / 2;  // 计算出最后一个叶子节点的父亲while (father >= 0) {AdjustDown(arr, sz, father);father--;}
}

测试一下:

#include <stdio.h>/* 交换函数 */
void Swap(int* px, int* py) {int tmp = *px;*px = *py;*py = tmp;
}/* 小堆下调 */
void AdjustDown(int arr[], int sz, int father_idx) {int child_idx = father_idx * 2 + 1;                                        // 计算出左孩子的值(默认认为左孩子大)while (child_idx < sz) {                                                   // 最坏情況:调到叶子(child >= 数组范围时必然已经调到叶子)if ((child_idx + 1 < sz) && (arr[child_idx + 1] < arr[child_idx])) {   // 如果右孩子存在且右孩子比左孩子小child_idx = child_idx + 1;                                         // 让其代表右孩子}if (arr[child_idx] < arr[father_idx]) {                                // 如果孩子的值小于父亲的值(大符合小堆的性質)Swap(&arr[child_idx], &arr[father_idx]);                           // 交换它们的值/* 往下走 */father_idx = child_idx;                                            // 更新下标child_idx = father_idx * 2 + 1;                                    // 计算出该节点路线的新父亲} else {                                                               // 如果孩子的值大于父亲的值(符合小堆的性质)break;                                                             // 终止循环}}
}/* 升序 */
void HeapSort(int arr[], int sz) {/* 创建大堆,选出最大的数  O(N)  */int father = ((sz - 1) - 1) / 2;  // 计算出最后一个叶子节点的父亲while (father >= 0) {AdjustDown(arr, sz, father);father--;}
}void HeapPrint(int arr[], int sz) {for (int i = 0; i < sz; i++) {printf("%d ", arr[i]);}printf("\n");
}int main()
{int arr[] = {70, 56, 30, 25, 15, 10, 75, 33, 50, 69};int sz = sizeof(arr) / sizeof(arr[0]);HeapSort(arr, sz);HeapPrint(arr, sz);return 0; 
}

运行结果如下:

第二步:排序

刚才介绍了两种方法来构建堆,现在堆已经构建完毕了,我们可以开始设计排序部分的算法了。

如果排升序,建小堆……

① 选出最小的数,放到第一个位置,这很简单,直接取顶部就可以得到最小的数。

② 但问题来了,如何选出次小的数呢?

建小堆来排升序是完全可以的,但是效率太低!

所以使用大堆来排升序。

我们刚才已经实现好小堆了,根据上一节学到的知识,小堆要变成大堆,直接把刚才的代码的 "<" 改成 ">" 即可: 

#include <stdio.h>/* 交换函数 */
void Swap(int* px, int* py) {int tmp = *px;*px = *py;*py = tmp;
}/* 大堆下调 */
void AdjustDown(int arr[], int sz, int father_idx) {int child_idx = father_idx * 2 + 1;                                        // 计算出左孩子的值(默认认为左孩子大)while (child_idx < sz) {                                                   // 最坏情況:调到叶子(child >= 数组范围时必然已经调到叶子)if ((child_idx + 1 < sz) && (arr[child_idx + 1] > arr[child_idx])) {   // 如果右孩子存在且右孩子比左孩子大child_idx = child_idx + 1;                                         // 让其代表右孩子}if (arr[child_idx] > arr[father_idx]) {                                // 如果孩子的值大于父亲的值(不符合大堆的性質)Swap(&arr[child_idx], &arr[father_idx]);                           // 交换它们的值/* 往下走 */father_idx = child_idx;                                            // 更新下标child_idx = father_idx * 2 + 1;                                    // 计算出该节点路线的新父亲} else {                                                               // 如果孩子的值小于父亲的值(符合大堆的性质)break;                                                             // 终止循环}}
}/* 升序 */
void HeapSort(int arr[], int sz) {/* 创建大堆,选出最大的数  O(N)  */int father = ((sz - 1) - 1) / 2;  // 计算出最后一个叶子节点的父亲while (father >= 0) {AdjustDown(arr, sz, father);father--;}}void PrintArray(int arr[], int sz) {for (int i = 0; i < sz; i++) {printf("%d ", arr[i]);}printf("\n");
}int main()
{int arr[] = {70, 56, 30, 25, 15, 10, 75, 33, 50, 69};int sz = sizeof(arr) / sizeof(arr[0]);HeapSort(arr, sz);PrintArray(arr, sz);return 0; 
}

运行结果如下:

现在改成了大堆,我们要排升序,我们可以让堆顶数和最后的数进行交换:

这并不会带来堆结构的破坏!我们把75不看作堆的一部分即可。再进行向下调整,就可以找到次小的数了。

步骤总结:

① 建大堆,选出最大的数。

② 最大的数跟最后一个数交换。

③ 如何选出次大的数呢?把最后一个数不看作堆里面,进行向下调整。

代码实现:

第一种

/* 堆排序 - 升序 */
void HeapSort(int arr[], int sz) {/* 创建大堆,选出最大的数  O(N)  */int father = ((sz - 1) - 1) / 2;  // 计算出最后一个叶子节点的父亲while (father >= 0) {AdjustDown(arr, sz, father);father--;}/* 依次选数,调堆   O(N * logN)  */int end = sz - 1;while (end > 0) {Swap(&arr[0], &arr[end]);      // 最大的数跟最后一个数交换AdjustDown(arr, end, 0);       // 调堆,选出次大的数end--;}
}

第二种:

void HeapSort(int arr[], int sz) {/* 建堆 */for (int father = (sz - 1 - 1) / 2; father >= 0; father--) {AdjustDown(arr, sz, father);}/* 排序 */for (int end = sz - 1; end > 0; end--) {Swap(&arr[0], &arr[end]);   // 最大的数跟最后一个数交换AdjustDown(arr, end, 0);    // 调堆,选出次大的数}
}

三、完整代码

升序:使用大堆

#include <stdio.h>void Swap(int* pa, int* pb) {int tmp = *pa;*pa = *pb;*pb = tmp;
}void AdjustDown(int arr[], int sz, int father) {int child = father * 2 + 1;while (child < sz) {if (child + 1 < sz && arr[child + 1] > arr[child]) {child += 1;}if (arr[child] > arr[father]) {Swap(&arr[child], &arr[father]);father = child;child = father * 2 + 1;}else {break;}}
}/* 堆排序 - 升序 */
void HeapSort(int arr[], int sz) {/* 创建大堆,选出最大的数  O(N)  */int father = ((sz - 1) - 1) / 2;  // 计算出最后一个叶子节点的父亲while (father >= 0) {AdjustDown(arr, sz, father);father--;}/* 依次选数,调堆   O(N * logN)  */int end = sz - 1;while (end > 0) {Swap(&arr[0], &arr[end]);      // 最大的数跟最后一个数交换AdjustDown(arr, end, 0);       // 调堆,选出次大的数end--;}
}void HeapPrint(int arr[], int sz) {int i = 0;for (i = 0; i < sz; i++) {printf("%d ", arr[i]);}printf("\n");
}int main()
{int arr[] = { 70, 56, 30, 25, 15, 10, 75, 33, 50, 69 };int sz = sizeof(arr) / sizeof(arr[0]);printf("排序前: ");HeapPrint(arr, sz);HeapSort(arr, sz);printf("排序后: ");HeapPrint(arr, sz);return 0;
}

运行结果如下:

降序:使用小堆

#include <stdio.h>/* 交换函数 */
void Swap(int* px, int* py) {int tmp = *px;*px = *py;*py = tmp;
}/* 小堆下调 */
void AdjustDown(int arr[], int sz, int father_idx) {int child_idx = father_idx * 2 + 1;                                        // 计算出左孩子的值(默认认为左孩子大)while (child_idx < sz) {                                                   // 最坏情況:调到叶子(child >= 数组范围时必然已经调到叶子)if ((child_idx + 1 < sz) && (arr[child_idx + 1] < arr[child_idx])) {   // 如果右孩子存在且右孩子比左孩子小child_idx = child_idx + 1;                                         // 让其代表右孩子}if (arr[child_idx] < arr[father_idx]) {                                // 如果孩子的值小于父亲的值(不符合小堆的性質)Swap(&arr[child_idx], &arr[father_idx]);                           // 交换它们的值/* 往下走 */father_idx = child_idx;                                            // 更新下标child_idx = father_idx * 2 + 1;                                    // 计算出该节点路线的新父亲}else {                                                               // 如果孩子的值大于父亲的值(符合小堆的性质)break;                                                             // 终止循环}}
}/* 堆排序 - 降序 */
void HeapSort(int arr[], int sz) {/* 创建大堆,选出最大的数  O(N)  */int father = ((sz - 1) - 1) / 2;  // 计算出最后一个叶子节点的父亲while (father >= 0) {AdjustDown(arr, sz, father);father--;}/* 依次选数,调堆   O(N * logN)  */int end = sz - 1;while (end > 0) {Swap(&arr[0], &arr[end]);      // 最大的数跟最后一个数交换AdjustDown(arr, end, 0);   // 调堆,选出次小的数end--;}
}
void PrintArray(int arr[], int sz) {for (int i = 0; i < sz; i++) {printf("%d ", arr[i]);}printf("\n");
}int main()
{int arr[] = { 70, 56, 30, 25, 15, 10, 75, 33, 50, 69 };int sz = sizeof(arr) / sizeof(arr[0]);printf("排序前: ");PrintArray(arr, sz);HeapSort(arr, sz);printf("排序后: ");PrintArray(arr, sz);return 0;
}

运行结果如下:

http://www.dtcms.com/a/403444.html

相关文章:

  • 05.容器网络
  • 自己做的博客网站吗网站重构
  • vue 构建工具如何选择 | vue-cli 和 vite的区别
  • 公司网站建设会计上怎么处理怎么做一个网站
  • 网站建设群国金紫郡府淮北论坛
  • DC-DFTC工具串scan chain过程中遇到Error(TEST-1313)
  • flutter 3.22+ Android集成高德Flutter地图自定义Marker显示
  • K8s学习笔记(六) K8s升级与节点管理
  • Android,jetpack compose实现俄罗斯方块,简单案例♦️
  • Kubernetes(K8S)完全详解:从架构设计到云原生实践
  • IDEA/WebStorm 卡顿问题与启动参数调优指南
  • 杭州精品课程网站建设青海媒体网站建设公司
  • Metal - 5.深入剖析 3D 变换
  • 上海网站建设友汇网站网站修改备案号
  • nccl中的rdma是指什么 (来自deepseek)
  • C#练习题——List排序与反转操作详解:怪物属性排序示例
  • Linux离线安装指定包
  • WebGIS:在 Vue 2 项目中使用 Mapbox 时,如果需要加载的 GIS 数据量过大,怎么让接口一次性获取的geojson数据分批加载
  • 您的前端开发智能工作流待升级,查收最新 Figma2Code!
  • 多品牌集运管理的革新:易境通集运系统的一站式解决方案
  • 用dw做网站的步骤山东网站建设开发
  • Docker和K8S的区别详解
  • 高性能内存池(四)----CentralCache实现
  • Python快速入门专业版(四十四):Python面向对象基础:类与对象的创建与使用(核心概念解析)
  • 阿里云电影网站建设教程2345浏览器网址导航
  • flutter json转实体类
  • MCU内存到下载的诸多问题
  • 论文解读:利用中断隔离技术的 Linux 亚微秒响应性能优化
  • 莱芜住房和城乡建设厅网站海外代理ip
  • 服务器时间同步校准