数据结构:堆排序

news/2024/9/22 19:29:20/

更完堆,再更一期堆排序


利用容器实现堆排序

在C++等高级语言中,基本上都有堆或者优先队列等容器,借助这些容器很容易实现堆排序。

将数组里面的元素先插入到容器中,建好堆,
接着将容器中的元素,按照升序或降序重新放回数组中
即可实现好排序

#include <iostream>
#include <queue>using namespace std;int main()
{int a[] = { 2,1,4,2,5,5,1,2,3,5,3,2,3,22,31,4,213,12 };//默认建大堆降序//priority_queue<int> pq;//建小堆,可升序priority_queue<int, vector<int>, greater<int>> pq;for (auto e : a){pq.push(e);}int i = 0;while (!pq.empty()){a[i++] = pq.top();pq.pop();}for (auto e : a){cout << e << " ";}cout << endl;return 0;
}

在这里插入图片描述


利用容器实现堆排序的缺点

1、空间复杂度较高,O(N)
2、万一面试的时候面试官让你手写堆排序,不能使用容器,就很尴尬了。

利用数组原地实现堆排序(手写堆排序)

首先,我们需要模拟建堆,将数组建成一个堆。

这里有两种方法,向上建堆和向下建堆。

向上建堆

我们向上调整建堆,就需要调整之前本身就是一个堆,不破坏堆的性质,
那么我们就可以直接从第二个元素开始,每一个元素都向上调整,一直去维护这个堆。
(第一个元素不用调整,本身就是一个堆)

向上调整算法在上一篇文章《数据结构:堆》中有所提及,这里就不详细介绍了
传送门数据结构:堆

向上建堆代码:

void AdjustUp(int* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] > a[parent]){swap(a[child], a[parent]);child = parent;parent = (parent - 1) / 2;}else break;}
}int main()
{int a[] = { 2,1,4,2,5,5,1,2,3,5,3,2,3,22,31,4,213,12 };int n = sizeof(a) / sizeof(a[0]);for (int i = 1; i < n; ++i){AdjustUp(a, i);}
return 0;
}

向下建堆

我们向下建堆,要求左右子树都是堆,才能完成向下调整建堆。
那么我们从最后一个非叶子节点开始向下调整建堆,一直向前,直到根节点。
(叶子节点没有子树,不用向下调整建堆)

向下调整算法在上一篇文章《数据结构:堆》中有所提及,这里就不详细介绍了
传送门数据结构:堆

向下建堆代码:

void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child] < a[child + 1]){child++;}if (a[child] > a[parent]){swap(a[child], a[parent]);parent = child;child = child * 2 + 1;}else break;}
}int main()
{int a[] = { 2,1,4,2,5,5,1,2,3,5,3,2,3,22,31,4,213,12 };int n = sizeof(a) / sizeof(a[0]);for (int i = (n - 1 -1) /2; i >= 0; --i){AdjustDown(a, n, i);}return 0;
}

理性分析一下向上建堆和向下建堆的时间复杂度

算时间复杂度,要考虑最坏情况,这里以满二叉树为例
假设共h层高度

向上建堆

从第二层开始,都需要向上移动柜(k-1)层
每层的个数是2k-1 ,每一层需要移动的次数就是2k-1 * (k-1)
把每一层相加求和即可,
最后,时间复杂度是关于n的函数,最后把h换成n即可。

以下为草稿借鉴
在这里插入图片描述


向下建堆

从倒数第二层开始,每一层向下调整 h - k层
每层的个数是2k-1 ,每一层需要移动的次数就是2k-1 * (h - k)
把每一层相加求和即可,
最后,时间复杂度是关于n的函数,最后把h换成n即可。

向下建堆草稿借鉴:
在这里插入图片描述

综上,向上建堆的时间复杂度是O(N*logN),向下建堆的时间复杂度是O(N),差距十分显著。
所以,我们采用向下建堆算法


感性分析一下为啥向下调整算法效率更高

我们可以发现,

向上调整建堆,越下面调整的次数越多,但是越下面每一层的个数却越多,这就是多 * 多
向下调整建堆,越下面调整的次数越少,但是越下面每一层的个数却越少,这就是多 * 少

可以联系田忌赛马去理解。

升序建大堆,降序建小堆

看到标题,我相信很多小伙伴都不理解,为啥升序建大堆,不应该升序建小堆吗?
且听我慢慢分析

注意,我们需要不借助容器,也就是在原地进行排序。
如果升序建小堆,那么我们找到了最小的元素,怎么找次小的元素呢,怎么找再次小的元素呢?
比如一个小堆
1 2 10 112 111 3 ……
这样会破坏堆的性质,是不可取的

如果升序建大堆有什么好处呢?

假设一个大堆:
213 12 31 4 5 5 22 2 3 5 3 2 3 4 1 2 2 1
我们有最大的元素,
将其与最后一个元素交换,size–
将此时的第一个元素进行向下调整算法,就又可以维护堆的性质。
依此循环,就可以排好序了。

降序建小堆同理,这里就不分析了。


堆排序代码(升序为例)

void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child] < a[child + 1]){child++;}if (a[child] > a[parent]){swap(a[child], a[parent]);parent = child;child = child * 2 + 1;}else break;}
}int main()
{int a[] = { 2,1,4,2,5,5,1,2,3,5,3,2,3,22,31,4,213,12 };int n = sizeof(a) / sizeof(a[0]);for (int i = (n - 1 -1) /2; i >= 0; --i){AdjustDown(a, n, i);}int end = n - 1;while (end){swap(a[end], a[0]);--end;AdjustDown(a, end + 1, 0);}
return 0;
}

在这里插入图片描述


祝大家中秋节快乐!!!


http://www.ppmy.cn/news/1528978.html

相关文章

C++ boost——时间与日期

文章目录 timerprogress_timerdate_time创建日期访问日期日期输出转换C结构日期长度日期运算日期区间日期区间运算日期迭代器其他功能综合运用 timer 是一个小型计时器&#xff0c;提供毫秒级的精度 适用于要求不高的程序计时任务 class timer { public :timer()(_start_time…

2024年9月16日--9月22日,(工作日源码抄写+周末ue5肉鸽独立游戏)

继续进行&#xff0c;按照周一到周五每天晚上在公司抄3小时源码gpu精粹催眠&#xff0c;周末独立游戏的方式&#xff0c;也就是说&#xff0c;要把独立游戏在周末做下去较长的一段时间&#xff0c;看看这条路能不能走通。 具体执行情况&#xff1a; 周一&#xff1a; 15&#…

SQL优化之深度分页优化

深度分页是指在分页查询场景下&#xff0c;当数量很大时&#xff0c;随着页数的增大&#xff0c;查询会变得越慢&#xff0c;数据库在梳理分页查询时需要跳过大量的数据&#xff0c;降低查询效率。 //查询第 1 到第 20 条商品 select * from products limit 20 offset 0; sele…

Python 二级考试

易错点 电脑基础知识 定义学生关系模式如下&#xff1a;Student &#xff08;S#&#xff0c; Sn&#xff0c; Ssex&#xff0c;class&#xff0c;monitorS#&#xff09;&#xff08;其属性分别为学号、学生名、性别、班级和班长学号&#xff09; 在关系模式中&#xff0c;如果…

Spring Boot-定时任务问题

Spring Boot 定时任务问题及其解决方案 1. 引言 在企业级应用中&#xff0c;定时任务是一项常见需求&#xff0c;通常用于自动化执行某些操作&#xff0c;如数据备份、日志清理、系统监控等。Spring Boot 提供了简洁易用的定时任务机制&#xff0c;允许开发者通过简单的配置来…

Python计算机视觉第十章-OpenCV

目录 10.1 OpenCV的Python接口 10.2 OpenCV基础知识 10.2.1 读取和写入图像 10.2.2 颜色空间 10.2.3 显示图像及结果 10.3 处理视频 10.3.1 视频输入 10.3.2 将视频读取到NumPy数组中 10.4 跟踪 10.4.1 光流 10.4.2 Lucas-Kanade算法 10.1 OpenCV的Python接口 O…

【算法业务】互联网风控业务中的拒绝推断场景算法应用分享(涉及半监督算法、异常检测、变分自编码、样本权重自适应调整、迁移学习等)

1. 业务目标和任务描述 该项目是很早期的一个工作&#xff0c;属于互联网信贷风控场景&#xff0c;研究并应用信贷中的拒绝推断任务&#xff0c;处理方式也许对于目前的一些业务还有参考意义&#xff0c;因此这里做下分享。拒绝推断是指在信贷业务中&#xff0c;利用已知的接受…

Vue2学习笔记(02条件渲染 、监视数据的原理)

1、v-if和v-show的区别 2、Vue监视数据的原理