二叉树讲解《三》(堆的应用)
个人主页:欢迎大家光临——>沙漠下的胡杨
各位大帅哥,大漂亮
如果觉得文章对自己有帮助
可以一键三连支持博主
你的每一分关心都是我坚持的动力
☄: 本期重点:堆排序以及Topk问题的实现
希望大家每天都心情愉悦的学习工作。
☄: 本期重点:堆排序以及Topk问题的实现
堆排序(基本不使用):
堆排序适用版:
我们有两种建堆方式:
1.向上调整建堆:
2.向下调整建堆:
排升序和降序分别建什么堆呢?
整体代码实现:
堆实现Top-k问题:
解决思路:
模拟实现例子:
在上一篇博客中我们说到了如何实现一个堆,下面我们来用它实现一些功能。
堆排序(基本不使用):
首先我们知道了,位于堆顶的元素是一个堆中最大或者最小的,那么我们可以根据这一特性进行选数,让数据从大到小或者从小到大排序。
根据我们所学的,我们可以先创建堆,然后在把要排序的数组中的元素一个个插入堆中,接着我们在依次取出栈顶元素放入要排序的数组,然后在Pop栈顶元素,循环操作,直到堆为空停止,就排好了。
void HeapSort(int *a,int n) {HP hp;HeapInit(&hp);for (int i = 0; i < n; ++i){HeapPush(&hp, a[i]);//插入堆}int i = 0;while (!HeapEmpty(&hp))//判空{a[i++] = HeapTop(&hp);//放入要排序的数组。HeapPop(&hp);//删除堆顶}HeapDestroy(&hp);//销毁堆 }int main() {int a[] = { 15, 18, 59, 64, 54, 87, 36, 56, 41, 23 };//要排序的数组HeapSort(a, sizeof(a) / sizeof(a[0]));堆排序for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)//打印{printf("%d ", a[i]);}printf("\n");return 0; }
下面是打印结果:
我们可以看出,在逻辑上是二叉树。,并且也是降序排列。好像达到我们的要求了,但是还是有很重要的两点,就是这个排序的失败之处
1. 需要先有一个堆 这样的数据结构,才能使用这种办法。
2.在排序过程中,额外开辟了一个数字,空间复杂度为O(N)。
所以这种堆排序很不好,下面我们看一个实用的堆排序。
堆排序适用版:
我们有两种建堆方式:
1.向上调整建堆:
我们知道,在堆中那么多函数接口中,其实最关键的是向上调整和向下调整算法,我们可以只使用这两个函数,就完成建堆,然后在进行排序。
我们调整后就是一个堆了,但是我们如何把这个堆变为有序呢?
我们首先把第一个元素和最后一个元素交换下位置,
我们不考虑最后一个元素,
把第一个元素向下调整,我们就可以得到一个新的堆,
如此反复,就可以得到这个数组的降序。
代码实现如下:
void Swap(HPDataType* p1, HPDataType* p2)//交换 {HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp; } void AdjustDwon(HPDataType* a, int size, int parent)//向下调整 {int child = parent * 2 + 1;//左孩子while (child < size)//孩子节点下,到size结束{ //要在确定有右孩子的情况下,右孩子大于左孩子,就使用右孩子(大堆)//想变为小根堆,大于变小于(child +1 <size不变)if (child + 1 < size && a[child] < a[child + 1]){child = child + 1;}//如果孩子大就交换(大堆)if (a[child] > a[parent])//想变为小根堆,大于变小于{Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}} }void AdjustUp(HPDataType* a, int child)//向上调整 {int parent = (child - 1) / 2;//找到父亲节点while (child > 0)//如果child等于0,表示调整到根,不可在调整返回。{//孩子大于父亲就交换(大根堆)if (a[child] > a[parent])//想变为小根堆,大于变小于{Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}} }void HeapSort2(int *a, int n) {for (int i = 1; i < n; i++){AdjustUp(a, i);}int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDwon(a, end, 0);--end;} }int main() {int a[] = { 15, 18, 59, 64, 54, 87, 36, 56, 41, 23 };HeapSort2(a, sizeof(a) / sizeof(a[0]));for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++){printf("%d ", a[i]);}printf("\n");return 0; }
2.向下调整建堆:
我们向下调整建堆的思路和向上调整不太类似,我们先从倒数第一个非叶子节点向上调整,调整到根结束。
最后我们按照图示方法建堆结果为:
也符合堆的性质,然后后面的思路和向上建堆就一样了,把第一个元素和最后一个元素交换,然后不考虑最后一个元素,让第一个元素向下调整在变为一个堆就好了,重复过程即可
代码如下:
void HeapSort2(int *a, int n) {for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDwon(a, n, i);//向下调整建堆}int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDwon(a, end, 0);--end;} }int main() {int a[] = { 15, 18, 59, 64, 54, 87, 36, 56, 41, 23 };HeapSort2(a, sizeof(a) / sizeof(a[0]));for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++){printf("%d ", a[i]);}printf("\n");return 0; }
排升序和降序分别建什么堆呢?
我们的建堆思路有了,但是我们要把数字排升序和降序分别要建什么堆呢?
排升序,就建大堆,排降序,就建小堆。
解释如下图:
首先,我们是排升序建的大堆,如果我们要排的是降序,我们依然建的大堆,那怎么办?
我们可以找到最大的数据,应该把它放在第一位,然后不考虑它继续找次大的,但是我们的父子关系全乱了,需要重新建堆了,这时间复杂度就变为O(N)*O(N)了,不划算。所以我们选择排降序时建小堆,我们找到其中最小的,然后把最小的放最后,不考虑它,然后使用向下调整,这样时间复杂度就降低为log(N)*O(N),这才是堆排序快的地方。
整体代码实现:
void AdjustDwon(HPDataType* a, int size, int parent)//向下调整 {int child = parent * 2 + 1;//左孩子while (child < size)//孩子节点下,到size结束{ //要在确定有右孩子的情况下,右孩子大于左孩子,就使用右孩子(大堆)//想变为小根堆,大于变小于(child +1 <size不变)if (child + 1 < size && a[child] < a[child + 1]){child = child + 1;}//如果孩子大就交换(大堆)if (a[child] > a[parent])//想变为小根堆,大于变小于{Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}} }void AdjustUp(HPDataType* a, int child)//向上调整 {int parent = (child - 1) / 2;//找到父亲节点while (child > 0)//如果child等于0,表示调整到根,不可在调整返回。{//孩子大于父亲就交换(大根堆)if (a[child] > a[parent])//想变为小根堆,大于变小于{Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}} }void HeapSort2(int *a, int n) {//时间复杂度O(N*logN)//for (int i = 1; i < n; i++)//{// AdjustUp(a, i);//向上调整建堆//}//时间复杂度O(N)for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDwon(a, n, i);//向下调整建堆}int end = n - 1;//时间复杂度O(N*logN)while (end > 0){Swap(&a[0], &a[end]);AdjustDwon(a, end, 0);--end;} }int main() {int a[] = { 15, 18, 59, 64, 54, 87, 36, 56, 41, 23 };HeapSort2(a, sizeof(a) / sizeof(a[0]));for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++){printf("%d ", a[i]);}printf("\n");return 0; }
堆实现Top-k问题:
解决思路:
我们在平时生活中,会遇到很多的类似求前K个最大数的问题,比如我们平时买零食,可能会选销量最好的前十家店购买,然后我们平时打游戏也会有一些实时的排名榜单,这些都是Top-k问题,我们可以使用堆来进行解决。
现在我们遇到了一个问题,让我们从1000亿个数据中选出最大的前1000个数,我们怎么办?
思路1:我们可以使用堆排序,然后取前1000个数据。(空间复杂度太大,不行)
思路2:我们可以建立一个1000元素的小堆,然后使用1000亿中前1000个元素建堆,然后我们从1001个数和堆顶数据比较,如果比他大就进堆,然后向下调整,直到最后一个数据,这个堆就是前1000个大的数据。(空间复杂度很小,时间复杂度偏大一点点,可行)
模拟实现例子:
我们有10000个数,求出最大的前10个数据。我们使用时间戳的概念,用srand函数生成随机数,然后我们对他们取模10000,然后我们在随机给上10个数,把它们的值加上10000,然后判断打印的数据是否大于10000.
代码和运行结果如下:
void PrintTopK(int* a, int n, int k) {// 1. 建堆--用a中前k个元素建堆int* KMinHeap = (int *)malloc(sizeof(int)*k);assert(KMinHeap);for (int i = 0; i < k; i++){KMinHeap[i] = a[i];}for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDwon(KMinHeap, k, i);}// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换for (int j = k; j < n; j++){if (a[j]>KMinHeap[0]){KMinHeap[0] = a[j];AdjustDwon(KMinHeap, k, 0);}}for (int i = 0; i < k; i++){printf("%d ", KMinHeap[i]);}printf("\n");} void TestTopk() {int n = 10000;int* a = (int*)malloc(sizeof(int)*n);srand(time(0));for (int i = 0; i < n; ++i){a[i] = rand() % 10000;}a[6] = 10000 + 1;a[12] = 10000 + 2;a[645] = 10000 + 3;a[3333] = 10000 + 4;a[1222] = 10000 + 5;a[459] = 10000 + 6;a[9999] = 10000 + 7;a[8778] = 10000 + 8;a[5897] = 10000 + 9;a[44] = 10000 + 10;PrintTopK(a, n, 10); }
打印的结果都是大于10000的说明我们的思路成立。
二叉树讲解《三》(堆的应用)相关推荐
- Python培训讲解二叉树的三种深度
python代码实现了二叉树,这次将会实现二叉树的几种遍历方法,来更好的解析二叉树的结构特点.分别是一种广度遍历,和三种深度遍历方法:先序遍历,中序遍历,后序遍历.下面是代码实现: 1.先序遍历 遍历 ...
- Python教程讲解二叉树的三种深度
python代码实现了二叉树,这次将会实现二叉树的几种遍历方法,来更好的解析二叉树的结构特点.分别是一种广度遍历,和三种深度遍历方法:先序遍历,中序遍历,后序遍历.下面是代码实现: 1.先序遍历 遍历 ...
- c语言中二叉树中总结点,C语言二叉树的三种遍历方式的实现及原理
二叉树遍历分为三种:前序.中序.后序,其中序遍历最为重要.为啥叫这个名字?是根据根节点的顺序命名的. 比如上图正常的一个满节点,A:根节点.B:左节点.C:右节点,前序顺序是ABC(根节点排最先,然后 ...
- 二叉树的三种遍历(递归与非递归) + 层次遍历
<转载于 >>> > 二叉树是一种非常重要的数据结构,很多其他数据机构都是基于二叉树的基础演变过来的.二叉树有前.中.后三种遍历方式,因为树的本身就是用递归定义的,因此 ...
- 超级详细树讲解三 —— B树、B+树图解+代码
首先很高兴你看到了这篇文章,这篇文章可能会花费你很长很长的时间去看,但是这篇文章包括的内容绝对足够你对树的一个系统性的学习.为什么要写这篇文字呢?因为自己在学习树的时候,有些博客只有图解,有些博客只有 ...
- c语言二叉树的遍历菜单系统,C语言二叉树的三种遍历方式的实现及原理
C语言二叉树的三种遍历方式的实现及原理 发布时间:2020-10-03 19:43:57 来源:脚本之家 阅读:63 作者:看雪. 二叉树遍历分为三种:前序.中序.后序,其中序遍历最为重要.为啥叫这个 ...
- C语言基本数据结构之二(二叉树的三种遍历,节点数以及深度算法)
关于二叉树的定义,网上有比较好的介绍,在这里就简单介绍二叉树的一些性质 二叉树的基本性质 1)二叉树的第i层上至多有 2^(i-1)(i ≥1)个结点: 2)深度为 h 的二叉树中至多含有 2^h – ...
- 数据结构:关于重建二叉树的三种思路
前言: 前几天在温习<编程之美>这本书的时候,看到了二叉树的重建.正好,也想复习一下数据结构的知识,就来写了一个小Demo.居然有新发现(本文中的第三种方式). 我们在学习数据结构的时候, ...
- Python实现二叉树的三种深度遍历方法!
python代码实现了二叉树,这次将会实现二叉树的几种遍历方法,来更好的解析二叉树的结构特点.分别是一种广度遍历,和三种深度遍历方法:先序遍历,中序遍历,后序遍历.下面是代码实现: 1.先序遍历 遍历 ...
- 玩转iOS开发:NSURLSession讲解(三)
文章分享至我的个人技术博客: https://cainluo.github.io/14986211698053.html 前言 虽然前面两讲都是说了NSURLSession的一些理论上的知识, 但我们 ...
最新文章
- 数据连接池的工作机制是什么?
- c语言智能指针是什么,C ++中的智能指针
- Java与C语言混合编程
- HttpClient使用具体解释
- Wilcoxon秩和检验简介与MATLAB实现
- Linux安装yum教程
- Windows + CUDA + Anaconda + TensorFlow + PyCharm
- android data/app下的文件被误删,系统恢复,怎样恢复被误删除的文件
- win10系统计算机如何分盘,windows10怎么分盘
- 雅猴的脚印——2019年下半年
- 为大众而写的程序员小说——从 简单易懂的现代魔法 说开去
- uniapp-连接服务器超时,点击重试
- 考出PMP证书到底有没有用?
- vue生成海报(vue-canvas-poster)
- Oracle数据库(索引、视图、伪列与伪表)
- python如何安装keras和tensorflow
- 【北邮国院大二下】产品开发与营销知识点整理 Topic4
- FAST2022 DEPART: Replica Decoupling for Distributed Key-Value Storage Qiang(翻译分析)
- 玉米系统和秋叶系统哪个好_房产中介系统哪个好,房地产管理行业系统价格
- shell中encoding=utf-8_如何在Linux中将文件编码转换为UTF-8
热门文章
- _ViewStart.cshtml介绍
- syslog与syslog服务器的配置
- 四轴无人机那些事 番外篇 4 关于PWM模式的理解
- Ardiuno智能电蚊拍
- dev c++ 学习C语言+快捷键
- 简单的博弈问题:牛羊吃草问题(递归解法-暴力解法)
- 网络七层结构(讲人话)
- Suggestions of 周爱民
- Freemarker导出word图片不显示可能的原因
- TransUNet:Transformers Make Strong Encoders for Medical Image Segmentation用于医疗图像分割的transformers编码器详解