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

数据结构:堆排序


更完堆,再更一期堆排序


利用容器实现堆排序

在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;
}

在这里插入图片描述


祝大家中秋节快乐!!!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Java Jdbc 链接db2数据库示例
  • yolo自动化项目实例解析(二)ui页面整理
  • Vue: watch5种监听情况
  • WPF利用Path自定义画头部导航条(TOP)样式
  • Python基础语法(1)上
  • [数据集][目标检测]河道垃圾检测数据集VOC+YOLO格式2274张8类别
  • 提问即创作:用Prompt提示词引领AI灵感爆发
  • 关于axios同步获取数据的问题
  • Redis embstr 编码
  • MATLAB在嵌入式系统设计中的最佳实践
  • 《Oracle(一)- 基础》
  • 【重学 MySQL】二十四、笛卡尔积的错误和正确的多表查询
  • DOM编程
  • 桥接模式详解和分析JDBC中的应用
  • 预处理详解(二)
  • 【笔记】你不知道的JS读书笔记——Promise
  • 【跃迁之路】【463天】刻意练习系列222(2018.05.14)
  • CSS魔法堂:Absolute Positioning就这个样
  • DOM的那些事
  • JDK 6和JDK 7中的substring()方法
  • js面向对象
  • SpringBoot几种定时任务的实现方式
  • vue从创建到完整的饿了么(11)组件的使用(svg图标及watch的简单使用)
  • Vue全家桶实现一个Web App
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • Windows Containers 大冒险: 容器网络
  • 从输入URL到页面加载发生了什么
  • 给第三方使用接口的 URL 签名实现
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 聊一聊前端的监控
  • 深度学习入门:10门免费线上课程推荐
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 算法系列——算法入门之递归分而治之思想的实现
  • 微信小程序:实现悬浮返回和分享按钮
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • 终端用户监控:真实用户监控还是模拟监控?
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • 回归生活:清理微信公众号
  • 浅谈sql中的in与not in,exists与not exists的区别
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • # Apache SeaTunnel 究竟是什么?
  • # Redis 入门到精通(七)-- redis 删除策略
  • #include
  • #pragma once与条件编译
  • #QT(一种朴素的计算器实现方法)
  • $.ajax()参数及用法
  • $HTTP_POST_VARS['']和$_POST['']的区别
  • (11)MSP430F5529 定时器B
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (php伪随机数生成)[GWCTF 2019]枯燥的抽奖
  • (Spark3.2.0)Spark SQL 初探: 使用大数据分析2000万KF数据
  • (二)hibernate配置管理
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (四)React组件、useState、组件样式