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

数据结构·二叉树(2)

目录

1 堆的概念

2 堆的实现

2.1 堆的初始化和销毁

2.2 获取堆顶数据和堆的判空

2.3 堆的向上调整算法

2.4 堆的向下调整算法

2.4 堆的插入

2.5 删除堆顶数据

2.6 建堆

3 建堆的时间复杂度

3.1 向上建堆的时间复杂度

3.2向下建堆的时间复杂度

4 堆的排序


前言:前面介绍了树以及二叉树及其二叉树的存储方式,本文就介绍基于二叉树模式下的一种结构——堆。


1 堆的概念

堆分为大堆和小堆,小堆是指每个父节点的值都小于子节点,大堆是子节点的值都大于父节点,

小堆是这样的,大堆同理就可以了。

堆在逻辑上是完全二叉树结构,实际的物理结构是数组,接下来就进入到重点——堆的实现。


2 堆的实现

实现堆的时候,我们不像之前实现顺序表的时候,有增删查改以及指定位置的删除增加等等,因为堆单纯用来存储数据是没有太大的意义的,所以实现的接口也不大一样。

堆同样用结构体定义,一个是数据,一个是空间大小,一个是有效数据个数。


typedef int HDataType;typedef struct Heap
{HDataType* arr;int size;int capacity;
}Heap;//堆的初始化
void HPInit(Heap* php);//建堆
void HPInitArray(Heap* php,HDataType* a, int n);//堆的销毁
void HPDestroy(Heap* php);//堆的插入
void HPPush(Heap* php,HDataType x);//获取堆顶数据
HDataType HPTop(Heap* hp);//堆的删除数据
void HPPop(Heap* php);//堆的判空
bool HPempty(Heap* php);//堆的向上调整算法
void AdjustUp(HDataType* arr,int child);//堆的向下调整算法
void AdjustDown(HDataType* arr,int size ,int parent);

2.1 堆的初始化和销毁

销毁和初始化与之前线性表的初始化基本上就是一样的,不用过多介绍

void HPInit(Heap* php)
{assert(php);php->arr = NULL;php->capacity = php->size = 0;
}
//堆的销毁
void HPDestroy(Heap* php)
{assert(php);free(php->arr);php->arr = NULL;php->capacity = php->size = 0;
}

2.2 获取堆顶数据和堆的判空

获取数据只需要判断堆是不是空的就行,判空只需要检查size的值就可以了。

bool HPempty(Heap* php)
{assert(php);return php->size == 0;
}
//获取堆顶数据
HDataType HPTop(Heap* php)
{assert(php);assert(!HPempty(php));return php->arr[0];
}

因为后面的向上调整和向下调整,我们对于数据的交换用的是很频繁的,所以我们单独创建一个函数用来交换数据:

//交换数据
void Swap(HDataType* px, HDataType* py)
{HDataType tem = *px;*px = *py;*py = tem;
}

2.3 堆的向上调整算法

堆的向上调整算法,即是我们插入数据之后,保持数据的结构依然是堆,所以向上调整就是从最后一个数据入手,往上依次调整,如果堆是小堆,那么就是子节点与父节点比较大小,子节点小于父节点,就交换,大堆同理可得。

那么向上调整,我们知道子节点,如何求的父节点呢?

其实通过节点之间的存储规律,我们可以得到

左子节点 = 父节点 * 2 + 1,右子节点 = 父节点 * 2 + 2;

知道任意子节点我们就可以求父节点,实际操作的时候我们求父节点的时候怎么知道子节点是左还是右呢?

解决方法就是不管三七二十一,父节点 = (子节点 - 1)  / 2,不管多出来的1,因为整型运算,1 / 2 = 0,所以1是被忽略了。

因为调整的次数可能不止一次,可能调整到高度的一半就停止了,或者是调整到了根部,所以我们使用while循环,循环条件就是子节点的下标,因为经历一次调整后,子节点会到父节点上,父节点又到该节点的父节点上,那么判断条件就应该是子节点的下标位置。

//堆的向上调整算法
void AdjustUp(HDataType* arr, int child)
{int parent = (child - 1) / 2;//注意大小堆的写法while (child){if (arr[child] < arr[parent]){Swap(&arr[child],&arr[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}	
}

2.4 堆的向下调整算法

如果说向上调整算法是从子节点入手,那么向下调整算法就是从父节点入手,父节点和子节点相互比较必然存在一个问题就是,子节点可能只能只有左节点,没有右节点,那我们还要考虑的是两个节点谁小的问题,父节点与两个子节点较小的节点比较,这里可以用到假设法解决。

传进去的参数是数组,堆的有效数据个数,父节点的下标。

这里同样用到while循环,因为是从上往下调整的,所以结束条件应该是child。

为什么是child而不是parent呢?因为调整到最后两层的时候,parent在倒数第二次就不用动了,已经调整结束了,所以向下调整比向上调整有一个明显的优势是在于最后一层不是干涉,时间复杂度会少很多很多,后面再介绍。

假设法找两个子节点中小的那个,为了防止存在越界访问,比如只有一个左孩子,但是child + 1就访问到了右孩子,就越界了,所以child + 1  < size就是为了防止越界访问的。

最后就是进行比较,交换,赋值了。

//堆的向下调整算法
void AdjustDown(HDataType* arr, int size, int parent)
{int child = parent * 2 + 1;//为什么不用child当作循环条件呢?while (child < size){//先找两个孩子中小的那个 假设法if (arr[child + 1] < arr[child] && child + 1 < size){child++;}//交换if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

2.4 堆的插入

 堆的插入很简单的,就跟顺序表的插入一样的, 无非是最后要保持数据依然是堆而已,因为数据是在最后位置插入的,所以可以用向上算法进行调整,前面就是判断空间够不够,不够扩容就行,就没其他要注意的:

//堆的插入
void HPPush(Heap* php, HDataType x) 
{assert(php);//判断空间是否足够if (php->capacity == php->size){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HDataType* tem = (HDataType*)realloc(php->arr, sizeof(HDataType) * newcapacity);if (tem == NULL){perror("realloc fail");return;}php->arr = tem;php->capacity = newcapacity;}php->arr[php->size++] = x;//插入后依然保持为堆AdjustUp(php->arr,php->size - 1);}

2.5 删除堆顶数据

删除数据都是删除的堆顶数据,那么删除了之后我们该如何保持堆依然是大小堆呢?我们不能直接然后面的数据往前移动一位,这会让堆的数据完全乱套的,结构完全变化了。

前人是思路很是清奇的。我们不妨让第一个数据和末尾的数据进行交换,size--后,堆顶的数据就被删除了,问题是如何保持堆的结构呢?你看,向下调整这不就有大用了,从堆顶一直往下调整呗就,很清奇的这个思路,一下就删除好了。

结合这个思路,如果我们想要找最小,次小的数据是可以模拟这个思路的,后面介绍咯。

当然,没有数据肯定就不能删除了。

//堆的删除数据  删除的是堆顶数据
void HPPop(Heap* php)
{//数据删除之后依然保持为堆assert(php);assert(!HPempty(php));Swap(&php->arr[0], &php->arr[php->size - 1]);php->size--;AdjustDown(php->arr,php->size,0);
}

2.6 建堆

建堆有两个方法,一个是初始化一个堆之后,进行插入数据,调整操作,还有一种就是,初始化的这个过程就进行调整数据,即创建好一个满足堆需求的数组,再拷贝上去就行。

数据给好之后,该赋值的也都要赋值,然后就是调整数据部分,我们可以选择向上调整也可以选择向下调整,至于效率,是向下调整优先的,所以向上调整一般用的是比较少的,后面介绍。

//建堆
void HPInitArray(Heap* php,HDataType* a,int n)
{assert(php);php->arr = (HDataType*)malloc(sizeof(HDataType) * n);if (php->arr == NULL){perror("malloc fail!");return;}memcpy(php->arr, a, sizeof(HDataType) * n);php->capacity = php->size = n;//向上建堆//for (int i = 0; i < n; i++)//{//	AdjustUp(php->arr,i);//}//向下建堆for (int i = (php->size - 1 - 1) / 2; i >= 0; i--){AdjustDown(php->arr, php->size, i);}
}

向下建堆有个要注意的点就是i = php->size - 1 - 1,这是为了防止越界访问,假设堆里面只有9个元素,传进去的i就是4,进入到向下调整之后,child = 9,可是这是size指向的位置,一访问就越界了。


3 建堆的时间复杂度

建堆无非就两种方式,向上建堆和向下建堆,两种方式看似相差不大,实际上时间复杂度是相差较大的,这里就来慢慢分析:

计算时间复杂度之前,我们不妨计算一下树的高度与节点个数之间的关系:
二叉树的节点是以二次方递增的,第一层有2^0个节点,第二层有2^1个节点……第h层有2^(h - 1)次方个节点,那么总节点个数N = 2^0 + 2^1 + 2^3 + …… + 2^(h - 1),这里使用高中的等比求和公式,可以得出,N = 2^h - 1,那么h = log(N + 1)。

3.1 向上建堆的时间复杂度

时间复杂度估算,即是估算每个节点执行多少次操作,第一层的节点,执行调整操作次数至多为0次,第二层1次,第三层2次,第四层3次,第h层 h -1 次。

总的执行次数就是该层的所有节点 * 该层节点执行的至多次数.

F( h ) = 2^0 * 0 + 2 ^ 1 * 1 + 2 ^ 2 * 2 + 2 ^ 3 * 3  + …… +2 ^ (h - 1) * (h - 1),这里利用高中的错位相减,可以得到F(h) = 2^(h - 2) + 2,那么F(N) = ( N  + 1 ) * (log( N + 1) - 2)  + 2。

所以向上建堆的时间复杂度就是O(N) = N * log(N)

3.2向下建堆的时间复杂度

同3.1,向下建堆与向上建堆不一样的是向下建堆止步于倒数第二层,这就是为什么向下建堆算法优于向上建堆算法。

节点向下调整至多调整到倒数第二层,所以第一层的节点执行的次数为h - 1,第二层为h - 2,倒数第二层执行的次数为1次,所以:
F(h) = 2^0 * (h - 1) + 2 ^ 1 * (h - 2) + 2 ^ 2 * (h - 3) + …… + 2 ^ (h-1) * 1,结合高中的数学知识可以得到F(h) = 2^h - h - 3,F(N) = (N + 1) - log( N + 1) - 3.

所以向下建堆的时间复杂度就是O(N) = N - log(N)

这样看来向下建堆的效率远高于向上建堆的效率。


4 堆的排序

堆用来存储数据意义不大,排序倒是有点意思,当我们想让一个数组变成升序,我们是大堆还是小堆呢? 一般来说,小堆就是子节点大于父节点,满足升序,但是实际操作发现哪哪儿都是坑,特别容易改变结构。

面的删除操作有着异曲同工之妙,我们实现升序就选择大堆,讲堆顶数据放在最后,size--就访问不了最大的数据,然后选出第二大的数据,再交换,再size--,再选择第三大的数据,再交换,再size--,重复操作,最后就实现了堆排。

//堆排
void HPsort(HDataType* arr, int size)
{for (int i = (size - 1 - 1); i >= 0 ; i--){AdjustDown(arr,size,i);}int end = size - 1;while (end){Swap(&arr[0], &arr[end]);AdjustDown(arr, end, 0);end--;}
}

感谢阅读!

相关文章:

数据结构·二叉树(2)

目录 1 堆的概念 2 堆的实现 2.1 堆的初始化和销毁 2.2 获取堆顶数据和堆的判空 2.3 堆的向上调整算法 2.4 堆的向下调整算法 2.4 堆的插入 2.5 删除堆顶数据 2.6 建堆 3 建堆的时间复杂度 3.1 向上建堆的时间复杂度 3.2向下建堆的时间复杂度 4 堆的排序 前言&…...

MATLAB算法实战应用案例精讲-【毕业季论文专用】人工智能视觉检测技术及其在实际应用中的挑战与前景

目录 摘要: 第一章:引言 1.1 研究背景 1.2 研究目的与意义...

Linux虚拟机环境搭建spark

Linux环境搭建Spark分为两个版本&#xff0c;分别是Scala版本和Python版本。 一、 安装Pyspark 本环境以 Python 环境为例。 1、下载spark 下载网址&#xff1a;https://archive.apache.org/dist/spark 下载安装包&#xff1a;根据自己环境选择合适版本&#xff0c;本环境…...

STL的string容器

string基本概念 string是C风格的字符串&#xff0c;本质上是一个类。 string 和 char* 的区别 char* 是一个指针&#xff1b; string是一个类&#xff0c;内部封装了 char* &#xff0c;用来管理字符串&#xff0c;是一个 char* 型的容器。 特点 string内部封装了很多成员…...

半导体工艺技术

完整内容点击&#xff1a;【半导体工艺技术】...

acwing算法提高之图论--单源最短路的扩展应用

目录 1 介绍2 训练 1 介绍 本专题用来记录使用。。。。 2 训练 题目1&#xff1a;1137选择最佳线路 C代码如下&#xff0c; #include <iostream> #include <cstring> #include <algorithm> #include <queue>using namespace std;const int N 101…...

SQLServer数据库使用Function实现根据字段内容的拼音首字母进行数据查询

实现SQL首字母查询分两步&#xff0c;第一步建Function&#xff0c;第二步引用新建的Function。 1. 首先需要自定义一个查询的Function&#xff0c;详细SQL如下&#xff1a; ALTER function [dbo].[GetDataByPY](str nvarchar(4000)) returns nvarchar(4000) as begin decla…...

Linux——信号概念与信号产生方式

目录 一、概念 二、前台进程与后台进程 1.ctrlc 2.ctrlz 三、信号的产生方式 1.键盘输入产生信号 2.系统调用发送信号 2.1 kill()函数 2.2 raise()函数 2.3 abort()函数 3.异常导致信号产生 3.1 除0异常 3.2 段错误异常 4.软件条件产生信号 4.1 管道 4.2 闹钟…...

赋值语句还能当判断条件?涨芝士了!

赋值和条件看似是C语言中毫不相关的两个概念&#xff0c;虽然实际过程中我猜测不会有太多这种不太符合常理的情况出现&#xff0c;但是现在在学习的过程中&#xff0c;为了出题而出题总是会整出一些花活出来.....这很难不让人联想起高中时一些大佬为了彰显自己的数学天赋而自己…...

数据结构 - 算法效率|时间复杂度|空间复杂度

目录 1.算法效率 2.时间复杂度 2.1定义 2.2大O渐近表示法 2.3常见时间复杂度计算举例 3.空间复杂度 3.1定义 3.2常见空间复杂度计算举例 1.算法效率 算法的效率常用算法复杂度来衡量&#xff0c;算法复杂度描述了算法在输入数据规模变化时&#xff0c;其运行时间和空间…...

接口自动化之 + Jenkins + Allure报告生成 + 企微消息通知推送

接口自动化之 Jenkins Allure报告生成 企微消息通知推送 在jenkins上部署好项目&#xff0c;构建成功后&#xff0c;希望可以把生成的报告&#xff0c;以及结果统计发送至企微。 效果图&#xff1a; 实现如下。 1、生成allure报告 a. 首先在Jenkins插件管理中&#x…...

『Apisix安全篇』探索Apache APISIX身份认证插件:从基础到实战

&#x1f680;『Apisix系列文章』探索新一代微服务体系下的API管理新范式与最佳实践 【点击此跳转】 &#x1f4e3;读完这篇文章里你能收获到 &#x1f6e0;️ 了解APISIX身份认证的重要性和基本概念&#xff0c;以及如何在微服务架构中实施API安全。&#x1f511; 学习如何使…...

【01-20】计算机网络基础知识(非常详细)从零基础入门到精通,看完这一篇就够了

【01-20】计算机网络基础知识&#xff08;非常详细&#xff09;从零基础入门到精通&#xff0c;看完这一篇就够了 以下是本文参考的资料 欢迎大家查收原版 本版本仅作个人笔记使用1、OSI 的七层模型分别是&#xff1f;各自的功能是什么&#xff1f;2、说一下一次完整的HTTP请求…...

『大模型笔记』常见的分布式并行策略(分布式训练)

常见的分布式并行策略(分布式训练) 文章目录 一. 为什么分布式训练越来越流行二. 常见的并行策略2.1 数据并行2.2 模型并行2.3 流水并行2.4 混合并行二. 参考文献一. 为什么分布式训练越来越流行 近年来,深度学习被广泛应用到各个领域,包括计算机视觉、语言理解、语音识别、广…...

java 企业工程管理系统软件源码+Spring Cloud + Spring Boot +二次开发+ 可定制化

工程项目管理软件是现代项目管理中不可或缺的工具&#xff0c;它能够帮助项目团队更高效地组织和协调工作。本文将介绍一款功能强大的工程项目管理软件&#xff0c;该软件采用先进的Vue、Uniapp、Layui等技术框架&#xff0c;涵盖了项目策划决策、规划设计、施工建设到竣工交付…...

3D数据格式导出工具HOOPS Publish如何生成高质量3D PDF?

在当今数字化时代&#xff0c;从建筑设计到制造业&#xff0c;从医学领域到电子游戏开发&#xff0c;3D技术已经成为了不可或缺的一部分。在这个进程中&#xff0c;将3D模型导出为3D PDF格式具有重要的意义。同时&#xff0c;HOOPS Publish作为一个领先的解决方案&#xff0c;为…...

【springboot】闲话 springboot 的几种异步机制 及 长轮询的概念和简单实现

文章目录 引子springboot的几种异步形式开启异步支持和线程池配置&#xff08;重要&#xff09;第一种&#xff1a;Async第二种&#xff1a;Callable<T>第三种&#xff1a;WebAsyncTask<T>第四种&#xff1a;DeferredResult<T> 长轮询的简单实现概念实现服务…...

Mysql---安全值守常用语句

文章目录 目录 文章目录 一.用户权限设置 用户设置 元数据查询 Union联合查询 分组查询 字符串函数 总结 一.用户权限设置 用户设置 #用户创建 create user "用户名""%主机名" identified by "密码" #用户删除 drop user 用户名 #用户查询…...

containerd快速安装指南

1 containerd快速安装指南&#x1f680; 本指南旨在提供一个简洁有效的方法来安装containerd。我们将通过一份易于理解的脚本步骤&#xff0c;指导您完成安装&#x1f527;。请根据您的实际需求&#xff0c;适当调整containerd版本及其相关依赖。 注意事项&#xff1a; 本安装…...

Javascript - 正则表达式相关的一些基础的范例

很久以前的一些学习资料&#xff0c;归档发布&#xff1b; 正则表达式的基础&#xff0c;以HTML代码来示范&#xff1a; <html><head><title></title><script language"javascript">function test(){//从页面要求客户输入一个字符串…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

uniapp 小程序 学习(一)

利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 &#xff1a;开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置&#xff0c;将微信开发者工具放入到Hbuilder中&#xff0c; 打开后出现 如下 bug 解…...