【数据结构】——二叉树堆的实现

 大佬们点点关注,点点赞?!

前言

在上篇博客中我们已经介绍了树和二叉树的相关概念,相信大家都已经清楚了树和二叉树的基本思想,下面我们就来着重看看二叉树堆的实现。

在看堆的实现,我们先看看二叉树的顺序存储

一  二叉树的顺序存储

二叉树的顺序存储就是以顺序表来实现的,也就是把二叉树装进数组中去实现,其中坐标代表着各个结点的位置,这里我们用到一个二叉树的性质,也就是上篇博客的性质4。如果我们存的二叉树的的结构不是完全二叉树或者是满二叉树,那么就会有空间的浪费。

那就会有人问了,为啥空的地方不存储呢, 这也是我们的性质4的原因,因为我们在存的时候,需要找的孩子结点,或者父亲结点,他们的下标很重要

比如知道父亲坐标找孩子:leftchild=parent*2+1,rightchild=parent*2+2;

知道孩子坐标找父亲:parent=(leftchild-1)/2; 这里不需要判断是左孩子还是右孩子,因为这里是向下取整(因为int型向下取整的特点),所以得到的结果都是一样的。

所以如果用顺序存储最好是完全二叉树比较好,这样避免了空间的浪费,在操作中用数组存储的也就只有完全二叉树,这也从而让堆实现起来!

二  堆的概念

这里的堆和操作系统里面的堆是两个东西,一个是数据结构,一个是管理系统的一块分区,这里要区分开来

现在有一段数据,然后我们把这些数据按完全二叉树的形式存储到我们的数组中,如果这组数组拆分成二叉树,符合父亲结点大于孩子结点的我们称之为大堆,反之称为小堆。

三  堆的性质

1.堆的某个结点值总是不大于或者不小于其父亲结点的

2.堆总是完全二叉树

3.堆中的结点如果没有左孩子,那么肯定没有右孩子

4.堆不一定有序

四  堆的实现

4.1 结构体的创建

首先堆是用顺序结构实现的,所以我们用的结构也就是和顺序表的形式是一样的

typedef int HeapDataType;
typedef struct Heap
{HeapDataType * a;int size;//当前大小int capacity;//容量
}Heap;//这里和顺序表一样
4.2  堆的初始化和销毁

这里的初始化也可以直接给空间,看个人的喜好

void HeapIint(Heap* hp)
{assert(hp);//断言,因为hp不可能为空hp->a = NULL;hp->size = 0;hp->capacity = 0;
}
void HeapDestory(Heap* hp)
{assert(hp);free(hp->a);hp->a = NULL;hp->size = 0;hp->capacity = 0;
}
4.3  堆的插入

堆的插入,也就是在最后面插入一个数据,这个数据插入以后,这个堆就不是堆了,因为如果是小堆,我们插入的数据比根节点还小,那么这就不符合堆的性质,所以我们需要向上调整这个算法

向上调整的算法思路就是 把这个插入的数据和他这条路径上的所有数进行比较,如果小那么就交换,最终把破坏的结构恢复过来,也就是8,16,12这条路径,其他的没影响

向上调整需要用到父亲结点,也就是我们插入的是孩子,所以我们需要用孩子的坐标算出父亲的坐标

void HeapPush(Heap* hp, HeapDataType x)
{assert(hp);if (hp->size == hp->capacity)//开辟空间{int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;//HeapDataType* tmp = (HeapDataType*)realloc(hp->a, sizeof(HeapDataType) * newcapacity);if (tmp == NULL){perror("malloc fail");return;}hp->a = tmp;hp->capacity = newcapacity;}hp->a[hp->size] = x;//放入数据hp->size++;AdjustUp(hp->a, hp->size - 1);//向上调整//这里的参数一个是数组,一个是插入数据的坐标
}
4.4  向上调整算法 
void AdjustUp(HeapDataType* a, int child)
{int parent = (child - 1) / 2;while (child>0)//当孩子在第一个位置的时候也就没必要比较了所以是>0{if (a[child] < a[parent]){Swap(&a[child], &a[parent]);//交换函数,进行交换child = parent;//换位置,继续比较parent = (child - 1) / 2;}else{break;//如果符合堆的性质,那么不玩了,直接退出来就行}}
}

这里while的条件判断可能有的人会用parent>=0去比较,这虽然可以,但是纯属巧合,具有风险,这里还是推荐使用上面的条件判断

这里函数的参数没用传堆的结构,只是传了一个数组的结构,这是因为这个的算法应用面比较广,所以不能限制死了

4.5  交换函数
void Swap(HeapDataType* p1, HeapDataType* p2)
{HeapDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
4.6  堆的删除 

这里堆的删除如果是删最后一个元素那么就会变的毫无价值,所以我们删除第一个元素!!

如果我们删除第一个元素,那么我们的堆也就被破坏了,有两种方法,第一种也就是挪动覆盖,和数组一样,但是这样第二个元素就变成了根结点,那么他的兄弟就变成了他的儿子?!然后我们再重新建堆,因为他们的关系全乱了,所以只能这样,建堆也就是上面的操作想想都复杂。

假如之前的堆是这样的,那么调整挪动后就变成这样了

很明显这已经是发生了很大的改变。

第二种方法就是向下调整,也就是不删除第一个元素,先把他和最后一个元素交换,把最后一个元素删除,然后再让第一个元素向下调整比较,比他小的就交换(因为是小堆),最后形成的结构还是小堆,同时也成功把第一个元素删除了


void HeapPop(Heap* hp)
{assert(hp);assert(!HeapEmpty(hp));Swap(&hp->a[0], hp->a[hp->size - 1]);//交换,然后删除最后一个元素hp->size--;AdjustDown(a, hp->size, 0);
}
 4.7  向下调整算法

这里的我们得到的是父亲结点,需要算出孩子结点,所以我们传参的时候传父亲的下标,同时由于在比较过程中需要有一个跳出循环的条件,所以我们需要把n传进去

void AdjustDown(HeapDataType* a, int n, int parent)
{int child = parent*2+1;while (child<n)//如果孩子都大于等于n了,说明比较已经到最后了,也没必要比了,再比就越界了{if (child + 1 < n && a[child + 1] < a[child])//这里需要算出哪个孩子比较小(小堆)//child+1就是右孩子//同时右孩子可能不存在,所以要有前面的判断条件{child++;}if (a[child] < a[parent])//下面的就和向上调整的算法差不多了{Swap(&a[parent], &a[child]);parent = child;child = parent*2+1;}else{break;}}
}

有了上面的操作,那么下面的操作也就变得比较简单了

4.8  取堆顶元素 
HeapDataType HeapTop(Heap* hp)
{assert(hp);assert(!HeapEmpty(hp));return hp->a[0];
}
4.9  堆的数据判空
bool HeapEmpty(Heap* hp)
{assert(hp);return hp->size == 0;
}
4.10  堆的测试

 

int main()
{Heap hp;HeapIint(&hp);int a[] = { 12 ,15, 16, 30, 45, 25, 8 };for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++){HeapPush(&hp, a[i]);//把数据一个个插入进去,最终形成堆}while (!HeapEmpty(&hp)){int top = HeapTop(&hp);//取出第一个元素cout << top << " ";//打印HeapPop(&hp);//然后删除第一个元素,但是后面还是保持堆,因为采取了向下调整去维护}HeapDestory(&hp);return 0;
}

打印结果: 8 12 15 16 25 30 45

但是我们的本意是堆排序,并不是把这堆数据打印出来,所以我们可以这样

int main()
{Heap hp;HeapIint(&hp);int a[] = { 12 ,15, 16, 30, 45, 25, 8 };for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++){HeapPush(&hp, a[i]);}int i = 0;while (!HeapEmpty(&hp)){a[i++] = HeapTop(&hp);//把取出来的数据放进数组里面去HeapPop(&hp);}HeapDestory(&hp);for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++){cout << a[i] << " ";}return 0;
}

 但是我们又会发现这样是不是太麻烦了每次都要建一个堆,才能排序

那我们就有了下面的方法

五  堆排序


5.1  向上调整建堆

假设给定一个数组,那么我们从第二个元素开始,依次去比较,比如先给第一个元素,那么这肯定是个堆。然后给第二个元素,和第一个元素比较,比较完以后,那么这个还是一个堆,然后依次放入元素,最后就形成了堆,并且还是在数组里面的

	for (int i = 1; i < n; i++)//第一个元素不用比较,所以从第二个元素开始{AdjustUp(a, i);}
5.2  向下调整建堆

向下调整建堆我们需要从最后一个父亲结点开始, 因为最后的元素肯定都是叶结点,开始的时候他们不用去比较。比较完以后,那么当前的子树就是一个堆,那么继续往向上一个结点去向下调整,最后只剩根结点,然后进行一次向下调整即可

for (int i = (n - 1 - 1) / 2; i >= 0; i--)//从最后一个父亲结点开始{AdjustDown(a, n, i);}

可能图画的比较抽象,但是画的没错!

既然有两种方法那可能有一个快一个慢,这里可以直接得出向下调整比较快时间复杂度为O(N),向上调整是N*O(logN);至于具体的解释这里就不说明了,自己也可以感觉一下,假如叶子结点有N个,那么向上调整会比向下调整的要多

5.3  排序 

建完堆以后我们就要开始排序了,排序用向下调整算法,因为向下调整可以选出最大的和最小的数,因为他可以跟他的左右孩子比较,向上调整就不具备这样的优势

	for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}int end = n - 1;while (end > 0){Swap(&a[0],&a[end]);//倒数第一个数和第一个元素交换AdjustDown(a,end, 0);//这里的end是一直在变动的,因为排好的元素不需要进行比较了end--;}for (int i = 0; i < n; i++)cout << a[i] << " ";
}

如果是升序:建大堆

如果是降序:建小堆

因为后面的排序会把第一个元素放到后面,所以排完以后,就会变成有序的序列 

 

5.3  topK问题

如果我们要求从一个数组中选出前k个最大的数或者最小的数,那么我们的思路是直接建立一个大堆然后依次pop9次就行,因为最后一个不用pop,直接拿出来就行,每次pop,都是最大的数所以能够完成,虽然这行得通,但是如果是几亿的数,这种方法也会很慢

所以有了下面的改进

我们可以先建立一个k个数的小堆,然后与后面N-k个元素进行比较,不满足就替换堆顶元素,然后先下调整,这里调整的范围是K,不是N,所有比较大的数会在这个小堆的底部,最后形成一个k个数最大的小堆

	for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, k, i);//建小堆}for (int i = k; i < n; i++){if (a[i] > a[0])//选出k个最大的数{a[0] = a[i];AdjustDown(a, k, 0);}}for (int i = 0; i < k; i++)cout << a[i] << " ";
}

以上便是堆排序的全内容 希望大家喜欢,都看到这里了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/293734.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《QT实用小工具·一》电池电量组件

1、概述 项目源码放在文章末尾 本项目实现了一个电池电量控件&#xff0c;包含如下功能&#xff1a; 可设置电池电量&#xff0c;动态切换电池电量变化。可设置电池电量警戒值。可设置电池电量正常颜色和报警颜色。可设置边框渐变颜色。可设置电量变化时每次移动的步长。可设置…

脑部肿瘤检测YOLOV8

脑部肿瘤检测&#xff0c;采用YOLOV8训练得到PT模型&#xff0c;然后转换成ONNX&#xff0c;OPENCV调用&#xff0c;支持C/PYTHON/ANDORID开发脑部肿瘤检测YOLOV8

Windows安装TortoiseSVN客户端结合Cpolar实现公网提交文件到本地服务器

文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控制系统&#xff0c;它与Apache Subversion&#xff08;SVN&#xff09;集成在一起&#xff0c;提供了一个用户友好的界面&#xff0c;方便用…

第二篇:3.1 广告印象(AD Impression) - IAB与MRC及《增强现实广告效果测量指南1.0》

--- 我为什么要翻译美国IAB科技公司系列标准 翻译计划 第一篇概述—IAB与MRC及《增强现实广告效果测量指南》之目录、适用范围及术语第二篇广告效果测量定义和其他矩阵之- 3.1 广告印象&#xff08;AD Impression&#xff09;第三篇广告效果测量定义和其他矩阵之- 3.2 可见度 …

云数据仓库Snowflake论文完整版解读

本文是对于Snowflake论文的一个完整版解读&#xff0c;对于从事大数据数据仓库开发&#xff0c;数据湖开发的读者来说&#xff0c;这是一篇必须要详细了解和阅读的内容&#xff0c;通过全文你会发现整个数据湖设计的起初原因以及从各个维度&#xff08;架构设计、存算分离、弹性…

【解決|三方工具】Obi Rope 编辑器运行即崩溃问题

开发平台&#xff1a;Unity 2021.3.7 三方工具&#xff1a;Unity资产工具 - Obi Rope   问题背景 使用Unity三方开发工具 - Obi Rope 模拟绳索效果。配置后运行 Unity 出现报错并崩溃。通过崩溃日志反馈得到如下图所示 这是一个序列化问题造成的崩溃&#xff0c;指向性为 Obi…

SpringSecurity学习总结(三更草堂)

SpringSecurity安全框架的核心功能是认证和授权&#xff1a; 认证&#xff1a;验证当前访问系统的是不是本系统的用户&#xff0c;并且要确认具体是哪个用户。 授权&#xff1a;经过认证后判断当前用户是否具有进行某个操作的权限。 一般来说中大型的项目都是使用SpringSecurit…

StableDiffusion Web UI开启FP8,极大节约显存

升级了Pytorch后&#xff0c;StableDiffusion最新版本就可以有使用FP8的基础了&#xff0c;因此把秋叶的LINUX包也升级到了最新的版本。 升级Pytorch参考我的升级记录&#xff1a; ComfyUI SDWebUI升级pytorch随记-CSDN博客 然后下一步就是如何开启FP8了。与ComfyUI不同&…

事件穿透效果

讲述一下事件穿透的需求&#xff0c;大家可以根据实际情况来考虑是否采用这种方式来制作页面&#xff0c;&#xff08;项目中遇到了底部是地图&#xff0c;两侧面板&#xff0c;但是UI在设计的时候为了好看&#xff0c;会有很大的遮罩阴影部分&#xff0c;如果按照时间制作会导…

51单片机学习笔记11 使用DS18B20温度传感器

51单片机学习笔记11 使用DS18B20温度传感器 一、DS18B20简介1. 主要特点2. 工作原理3. 引脚说明4. ROM 二、1-wire协议简介1. 总线结构&#xff1a;2. 通信方式&#xff1a;3. 数据传输&#xff1a;4. 设备识别&#xff1a;5. 供电方式&#xff1a;6. 应用场景&#xff1a;7. 优…

Docker容器、Serverless与微服务:腾讯云云原生架构技术实践案例集解析

前言 随着云原生技术的飞速发展&#xff0c;容器化和函数计算正成为企业和开发者关注的焦点。在这一潮流中&#xff0c;腾讯云凭借其卓越的技术实力和深厚的行业积累&#xff0c;发布了《2023腾讯云容器和函数计算技术实践精选集》&#xff0c;为我们提供了一份深入探索云原生…

风控系统:通过净值及盈亏开启和关闭自动交易

一、风控对交易员的好处 帮助交易员执行交易纪律并保护他们的交易资金。 纪律风控&#xff1a;对不符合交易纪律的交易执行风控&#xff0c;对交易纪律性差的交易员执行约束操作。净值风控&#xff1a;对满足条件的净值执行风控&#xff0c;防止交易员的账户净值过度下降。手数…

train拦截器

拦截器拦截到的请求&#xff0c;设置本地变量member&#xff0c;主要为了获取memberId&#xff0c;在passenger表中存放memberId。 拦截器&#xff1a; 乘客表外键memberId关联member表

KUKA机器人调整示教器灵敏度(校屏)

KUKA机器人KRC4的示教器升级后&#xff0c;示教器屏幕由之前的电阻屏改为电容屏&#xff0c;不仅在外观上有所变化&#xff0c;屏幕校准的方法也有所不同。通过以下方法分别对新旧两款示教器进行屏幕校正&#xff0c;调整示教器屏幕灵敏度。 对新款示教器而言&#xff1a; 一…

BIONIOAIO

通信技术整体解决的问题 1.局域网内的通信要求 2.多系统间的底层消息传递机制 3.高并发下&#xff0c;大数据量的通信场景需要 4.游戏行业。无论是手游服务端、还是大型网络游戏&#xff0c;java的应用越来越广 IO模型基本说明 就是用什么样的通道或者说是通信模式和架构…

淘宝详情数据采集(商品上货,数据分析,属性详情,价格监控),海量数据值得get

淘宝详情数据采集涉及多个环节&#xff0c;包括商品上货、数据分析、属性详情以及价格监控等。在采集这些数据时&#xff0c;尤其是面对海量数据时&#xff0c;需要采取有效的方法和技术来确保数据的准确性和完整性。以下是一些关于淘宝详情数据采集的建议&#xff1a; 请求示…

2014年认证杯SPSSPRO杯数学建模B题(第二阶段)位图的处理算法全过程文档及程序

2014年认证杯SPSSPRO杯数学建模 B题 位图的处理算法 原题再现&#xff1a; 图形&#xff08;或图像&#xff09;在计算机里主要有两种存储和表示方法。矢量图是使用点、直线或多边形等基于数学方程的几何对象来描述图形&#xff0c;位图则使用像素来描述图像。一般来说&#…

[Qt] QString::fromLocal8Bit 的使用误区

QString::fromLocal8Bit 是一个平台相关的函数。默认情况下在 Windows 下 就是 gbk 转 utf-8 ,在 Linux就应该是无事发生。因为Linux平台默认的编码方式就是 utf-8 可以通过 void QTextCodec::setCodecForLocale(QTextCodec *c)来修改 Qt默认的编码方式。如下 第一输出乱码的…

当当狸智能激光雕刻机 多种材质自由雕刻,轻松打造独一无二的作品

提及“激光雕刻”&#xff0c;大多数人的印象一般都是&#xff1a;笨重巨大、价格昂贵、操作复杂、使用门槛较高、调试难度大...不是普通人能够随意操作的&#xff0c;让人望尘莫及。 而小米有品上新的这台「当当狸桌面智能激光雕刻机L1」&#xff0c;将超乎你的想象&#xff…

5.11 Vue配置Element UI框架

Vue配置Element UI框架 目录一、 概要二、 开发前准备1. 搭建Vue框架 三、 安装 Element UI1. 引入 Element UI 依赖2. 在 main.js 中引入 Element UI 和相关样式&#xff1a;3. 按需引入(非必须, 可忽略)4. 简单构建一个主页面 目录 一、 概要 Element UI 是一个基于 Vue.js …