数据结构·二叉树(2)

news2024/11/16 3:19:50

目录

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

感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++语言】冲突-C语言:命名冲突(输入输出、缺省参数、引用、内联函数)

文章目录 前言正文2. C的输入与输出&#xff1a;3.缺省参数3.1 缺省参数的概念&#xff1a;3.2 缺省参数的分类&#xff1a;全缺省参数&#xff1a;半缺省参数&#xff1a; 4.函数重载4.1 函数重载的概念&#xff1a; 5.引用5.1 引用的基本概念&#xff1a;5.2 引用的特性&…

后端代码1

// 新增 public JsonResultVo<?> create(ApiIgnore RequestAttribute(ConstVal.REQ_USER) BaseUser baseUser,RequestBody IUTradeBuyPreserveVo iuTradeBuyPreserveVo) {//权限判断if (!baseCompanyService.dataPermission(baseUser, iuTradeBuyPreserveVo.getCompanyi…

Kimi和ChatGPT做古诗词阅读理解,谁更胜一筹?

前几天发过一篇Kimi整理会议的体验教程&#xff0c;没想到大家很感兴趣&#xff0c;这次再来拿Kimi做古诗词阅读理解看看&#xff0c;同时也对比下ChatGPT的效果。 ChatGPT是几乎家喻户晓的AI大模型&#xff0c;Kimi和它对比有哪些异同点呢&#xff1f; 首先它们都是基于对话…

【小沐学AI】智谱AI大模型的一点点学习(Python)

文章目录 1、简介1.1 大模型排行榜 2、智谱AI2.1 GLM2.1.1 模型简介2.1.2 开源代码2.1.2.1 GLM-130B 2.2 ChatGLM2.2.1 模型简介2.2.2 开源代码2.2.2.1 ChatGLM2.2.2.2 ChatGLM22.2.2.3 ChatGLM3 2.3 CodeGeeX2.3.1 模型简介2.3.2 开源代码 2.4 CogView2.4.1 模型简介2.4.2 开源…

【项目技术介绍篇】如何在本地运行若依项目

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

Error: Cannot find module ‘@rollup/rollup-win32-x64-msvc‘

1.背景 新项目需要使用vite搭建一个v3项目,之前也弄过,但项目创建后却一直无法跑起来,大聪明的我一直没有注意到这个问题 2.解决步骤 方案1:删除node_modules和package-lock.json文件重新npm install下包,部分码农通过这个步骤可解决 方案2:node版本或者npm版本不对,或者没…

Redission 分布式锁原理分析

一、前言 我们先来说说分布式锁&#xff0c;为啥要有分布式锁呢? 像 JDK 提供的 synchronized、Lock 等实现锁不香吗&#xff1f;这是因为在单进程情况下&#xff0c;多个线程访问同一资源&#xff0c;可以使用 synchronized 和 Lock 实现&#xff1b;在多进程情况下&#xff…

SpringBoot 文件上传(二)

上一节讲解了如何利用MultipartFile接收浏览器端上传的文件&#xff1a; SpringBoot 文件上传&#xff08;一)-CSDN博客 这节讲解服务器端如何将文件保存到本地目录下&#xff0c;下节讲解服务端如何将文件保存在阿里云上。 本节需要解决两个难点&#xff1a; 文件重名问题…

力扣---最长回文子串---二维动态规划

二维动态规划思路&#xff1a; 首先&#xff0c;刚做完这道题&#xff1a;力扣---最长有效括号---动态规划&#xff0c;栈-CSDN博客&#xff0c;所以会有一种冲动&#xff0c;设立g[i]&#xff0c;表示以第i位为结尾的最长回文子串长度&#xff0c;然后再遍历一遍取最大长度即可…

【PLC】PROFIBUS(二):总线协议DP、PA、FMS

1、总线访问协议 (FDL) 1.1、多主通信 多个主设备间&#xff0c;使用逻辑令牌环依次向从设备发送命令。 特征&#xff1a; 主站间使用逻辑令牌环、主从站间使用主从协议主站在一个限定时间内 (Token Hold Time) 对总线有控制权从站只是响应一个主站的请求它们对总线没有控制…

三轴工作台激光焊接机:实现高精度、高效率焊接的新选择

三轴工作台激光焊接机是一种先进的焊接设备&#xff0c;结合了激光焊接技术与三轴工作台的运动控制&#xff0c;实现了焊接过程的高效、精准与自动化。这种设备主要利用激光束的高能量密度和高速度特性&#xff0c;使工件在熔化的同时快速冷却凝固&#xff0c;从而达到高质量的…

n-皇后问题(DFS深搜两种解法)

题目描述&#xff1a; 思路&#xff1a; 根据题目要求&#xff1a;即任意两个皇后都不能处于同一行、同一列或同一斜线上。我们可以画图去看一下。对角线之间有什么规律可以发掘出来。接下来请看图解 根据上述图片&#xff0c;我们可以把正对角线看成撇对角线&#xff0c;也就…

分享300套常用的多行业商城模板和电商模板

小程序商城模板平台&#xff01;免费用多行业商城模板和电商模板&#xff0c;含小程序商城模板&#xff0c;多款精美高端电商模板免费使用&#xff0c;注册即用免费电商模板开发在线商城。 https://www.erdangjiade.com/templates/4-0-0-0-0-0 实现微信小程序携程首页顶部的界…

通过修改ospf的COST值来控制路由选路

配置好OSPF之后,发现默认走的是上面 PC1>tracert 192.168.200.1traceroute to 192.168.200.1, 8 hops max (ICMP), press Ctrl+C to stop1 192.168.100.254 16 ms <1 ms 16 ms2 10.10.10.2 15 ms &l

python入门题:输入输出练习

以下是Python基础语法的练习&#xff0c;项目要求和代码如下&#xff1a; """ 例3&#xff1a;小精灵&#xff1a;你好&#xff0c;欢迎古灵阁&#xff0c;请问您需要帮助吗&#xff1f;需要or不需要&#xff1f; 你&#xff1a;需要 小精灵&#xff1a;请问你需…

AutoCAD 2025(CAD2025)激活版

AutoCAD 2025 是一款由 Autodesk 公司开发的计算机辅助设计&#xff08;CAD&#xff09;软件。它广泛应用于建筑设计、机械制造、土木工程等领域。 AutoCAD 2025 提供了强大的绘图和设计工具&#xff0c;使用户能够创建精确的二维和三维图形。它支持多种绘图方式&#xff0c;如…

IDEA2023版本创建spring boot项目时,Java版本无法选择Java8问题解决

先简单说下出现本问题的原因&#xff1a; spring boot3.0发布时提到未来Java17将会成为主流版本&#xff0c;所有的Java EE Api都需要迁移到Jakarta EE上来。而spring boot3.0及以上版本已经不支持Java8了&#xff0c;支持Java17及以上版本。同时官方支持项目初始化的 Spring B…

Unity数独完整源码

支持的Unity版本&#xff1a;2018.1或更高。 这是一套完整且高效的数独源码&#xff0c;默认是9x9&#xff0c;有上千种关卡文件&#xff0c;4种难度&#xff0c;内有关卡编辑器&#xff0c;可扩展至4x4、6x6的关卡&#xff0c;还有英文文档对源码各方面可配置的地方进行说明&…

openGauss + Datakit搭建openGauss运维平台

系统架构OS 硬件需求&#xff1a;2c4g [rootlocalhost ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) [rootlocalhost ~]# uname -m x86_64 [rootlocalhost ~]# hostname -I 192.168.92.32 下载地址&#xff1a;https://opengauss.org/zh/download/ 下载…

Django之Web应用架构模式

一、Web应用架构模式 在开发Web应用中,有两种模式 1.1、前后端不分离 在前后端不分离的应用模式中,前端页面看到的效果都是由后端控制,由后端渲染页面或重定向,也就是后端需要控制前端的展示。前端与后端的耦合度很高 1.2、前后端分离 在前后端分离的应用模式中,后端仅返…