数据结构之堆的创建

news2024/12/26 21:36:04

1、堆的概念及结构

1.1堆的概念 

如果有一个关键码的集合K={k0,k1,k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足ki<=k2i+1且ki<=k2i+2(或满足ki>=k2i+1且ki>=k2i+2),其中i=0,1,2,…,则称该集合为堆。

小堆:每个父节点不大于子节点

大堆:每个父节点不小于子节点 

堆的性质: 

堆中某个节点的值总是不大于或不小于其父节点的值;

堆总是一棵完全二叉树。

1.2堆的结构

堆的逻辑结构是棵完全二叉树,堆在物理结构上是一个一维数组,所以堆的本质是一个数组。堆又分为大根堆和小根堆。 

大根堆:每个父节点大于等于子节点 

小根堆:每个父节点小于等于子节点

2、堆的实现

堆的实现跟顺序表的实现本质上 是一样的,对堆进行一系列初始化、插入、删除、销毁等操作。但是也有不同之处,就是要实现堆需要两个算法,一个是向上调整算法,一个是向下调整算法。

我们先搞一下堆的向上调整算法和向下调整算法

2.1堆的向上调整算法

向上调整算法经常用在堆的插入中,我们插入数据只能在堆的尾部插入,但是插入之后,堆的性质就会发生变化,就需要我们将插入的尾部数据顺着双亲向上一步一步的调整,成为新的堆

 比如现在有一个小堆,我想插入一个6进去。第一步就是将数据6插入到小堆的尾部,也就是最后一个孩子的后面。

当6插进去之后,你会发现堆的性质发生改变,原来是一个小根堆,每个父亲节点都小于或等于自己的子节点,但插入6之后,就不满足这个性质了,所以就需要我们将6顺着双亲往上调整,让他恢复原本小堆的性质,那么该怎么进行调整呢?这就是我们的第二步了。

我们只需要让插入的新结点与它的父节点进行比较,如果插入的这个节点大于等于它的父节点就不用做改变。如果插入的这个节点小于他的父节点,就让他们两个的节点值交换位置。交换完之后,还需要让父节点与他的父节点进行比较,如果该父节点也小于它的父节点,那就也让这个父节点向上调整,直到调整到堆顶的位置。

注意:未插入数据时,必须是个堆。

 代码演示

 child = parent * 2+1;

 parent = (child-1)/2;

void AdjustUp(HeapDataType* a, int child)//child是下标,插入的尾元素的下标
{
	//记录插入数据的父节点
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//以小堆为例
		if (a[child] < a[parent])
		{//如果父节点大于子节点了,交换两个节点的值
			Swap(&a[child], &a[parent]);
			//交换后接着比,孩子到父亲上,成为原祖父的儿子
			child = parent;
			//更新到新的父亲
			parent = (child - 1) / 2;
		}
		else
		{
			break;//父亲节点小于子节点,已经成为堆了,不用再比较了。
		}
	}
}

2.2堆的向下调整

堆的向下调整经常用到堆的删除操作中,也就是用来删除堆顶元素。向下调整的前提是:除了堆顶元素外,左右子树必须满足堆的性质,也就是左右子树要么全是大堆,要么全是小堆。

除了堆顶元素37不满足小堆的性质外,其它的元素都满足。

以小堆为例: 

  

那么如何将他调成小堆呢?这就需要我们进行向下调整了。 

算法思路:

小堆:找子结点中相对较小的子节点与父节点进行比较 ,如果父节点大于子节点,则两者交换。

大堆:找子结点中相对较大的子节点与父亲比较,如果父节点小于子节点,则进行交换。

堆的向下调整算法,最坏的情况(一直需要交换节点),需要向下调整h-1次,(h为树的高度)。而h = log2(N+1)(N为树的总结点数)。所以堆的向下调整算法的时间复杂度为:O(logN) 。

上面说到,使用向下调整算法,需要满足其的左右子树均为大堆或者小堆,那么我们怎么才能将任意一个数转化为堆呢?我们只需要从倒数第一个非叶子节点开始,从后往前,按下标,作为根,依次向下调整就可以了。

2.3两个数值交换

这个思想大家应该都不陌生

void Swap(int* child, int* parent)
{
	HeapDataType tmp = *child;
	*child = *parent;
	*parent = tmp;
}

2.4堆的初始化 

堆的本质:物理上操纵的是数组,逻辑上操纵的是树。所以堆的初始化跟之前的顺序表差不多,就是把数组置空,数组的容量和有效元素个数都为0。(为什么要断言呢?)

void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}

2.5堆的销毁

堆的销毁也是跟之前的顺序表一样,因为他们的本质都是数组。所以要销毁堆的话,直接把数组释放掉,再把数组置为空,数组的有效元素个数和容量都为0.

void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

2.6堆的插入

 插入一个元素,你要看他的容量是否满了,如果满了,那就扩容,扩完容再插入。没满那就直接插入。插入之后,元素的有效个数就多了一个(size++)。

需要注意的是,这是堆,堆有大堆和小堆。插入元素之后,会有两种情况:一是插入之后堆不受影响,就是直接插入了,该是大堆还是大堆,该是小堆还是小堆。二是插入之后,不是堆了,需要进行向上调整。

void HeapPush(HP* php, HeapDataType x)
{
	assert(php);
//如果空间满了
	if (php->size == php->capacity)
	{//如果空间为0,那就开辟四个字节。空间不为0,那就开辟2*capacity个字节
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HeapDataType* tmp = (HeapDataType*)realloc(php->a, newCapacity * sizeof(HeapDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	php->a[php->size] = x;//把x插入
	php->size++;//成功插入数据x,有效元素个数加一
	AdjiustUp(php->a, php->size - 1);//向上调整算法
}

2.7堆的删除 

堆的删除操作需要用到向下调整。堆的删除是要删除哪一个数据呢?堆的删除删除的是堆顶数据。因为删除堆尾数据没有意义,堆尾的数据不一定是最大的,也不一定是最小的,所以没有任何意义。

大堆:堆顶数据便是最大值。当最大值删除后,我们才可能获得次大的,然后是次次大的,以此类推。

小堆:小堆的堆顶数据是最小的。当堆顶数据删除后,我们可以获取次小的,然后是次次小的,依次类推。

那我们该怎么删除堆顶数据呢?如果直接将堆顶数据删除,剩下的数据就会混乱,那我们该怎么操作呢?

第一步:将堆顶数据与堆尾数据进行交换;

第二步:删除堆尾数据

第三步:让交换上去的堆顶数据向下调整,恢复堆的性质。

这样做堆的左右子树也不会有太大的变化。

void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));//堆不为空时
	Swap(&php->a[0], &php->a[php->size - 1]);//交换堆中的第一个数据和最后一个数据
	php->size--;//删除堆尾数据
	AdjustDown(php->a, php->size, 0);//向下调整数据
}

2.8获取堆顶数据

HeapDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	return php->a[0];//获取堆顶数据
}

2.9堆的数据个数

int	HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

2.10堆的判空 

bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

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

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

相关文章

Windows环境下SD卡多分区 隐藏分区 解决python裸读写扇区失败

SD卡分区 右键“我的电脑”->“管理”->“磁盘管理”&#xff1b; 如果SD卡有文件系统&#xff0c;点击"删除卷"&#xff0c;重新做卡&#xff1b; 删除文件系统后如下图&#xff0c;点击“新建简单卷”&#xff1b; 在导航页“指定卷大小”&#xff0c;设置…

61.以太网数据回环实验(4)以太网数据收发器发送模块

&#xff08;1&#xff09;状态转移图&#xff1a; &#xff08;2&#xff09;IP数据包格式&#xff1a; &#xff08;3&#xff09;UDP数据包格式&#xff1a; &#xff08;4&#xff09;以太网发送模块代码&#xff1a; module udp_tx (input wire gmii_txc …

网络层_计算机网络

文章目录 网络层数据平面路由器工作原理网际协议&#xff08;*IP*&#xff09;IPv4IPv6DHCP NAT 控制平面路由选择算法因特网中自治系统内部的路由选择&#xff1a;OSPFISP 之间的路由选择&#xff1a;BGP ICMPSNMP 网络层 尽力而为服务&#xff08;best-effort services&…

尚品汇-支付宝介绍、跳转支付订单页面实现(四十六)

目录&#xff1a; &#xff08;1&#xff09;支付宝介绍 &#xff08;1&#xff09;支付宝介绍 &#xff08;3&#xff09;显示付款页面信息 &#xff08;5&#xff09;创建支付控制器PaymentController &#xff08;1&#xff09;支付宝介绍 支付宝简介 支付宝&#xf…

没资料的屏幕怎么点亮?思路分享

这次尝试调通一个没资料的屏幕&#xff0c;型号是HYT13264&#xff0c;这个是淘宝上面的老王2.9元屏&#xff0c;成色很好但是长期库存没有资料和代码能点亮&#xff0c;仅仅只有一个引脚定义。这里我使用Arduino Nano作为控制器尝试点亮这个模块。 首先&#xff0c;已知别人找…

当水泵遇上物联网:智能水务新时代的浪漫交响

在当代科技的宏伟乐章中&#xff0c;物联网&#xff08;IoT&#xff09;技术宛如一位技艺高超的指挥家&#xff0c;引领着各行各业迈向智能化的新纪元。当这股创新浪潮涌向古老的水务行业时&#xff0c;一场前所未有的“智能水务”革命便悄然上演&#xff0c;而水泵——这一传统…

vue3 自定义指令 directive

1、官方说明&#xff1a;https://cn.vuejs.org/guide/reusability/custom-directives 除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外&#xff0c;Vue 还允许你注册自定义的指令 (Custom Directives)。 我们已经介绍了两种在 Vue 中重用代码的方式&#xff1a;组件和…

828华为云征文 | 华为云Flexusx实例,高效部署Servas书签管理工具的优选平台

需要了解 本文章主要讲述在 华为云Flexus X 实例上使用docker快速部署Servas&#xff0c;一款功能强大的自托管书签管理工具&#xff0c;专为追求高效与个性化的用户设计。选择合适的云服务器&#xff1a; 本文采用的是 华为云服务器 Flexus X 实例&#xff08;推荐使用&#x…

Vue 中 watch 和 watchEffect 的区别

watch 和 watcheffect 都是 vue 中用于监视响应式数据的 api&#xff0c;它们的区别在于&#xff1a;watch 用于监视特定响应式属性并执行回调函数。watcheffect 用于更通用的响应式数据监视&#xff0c;但回调函数中不能更新响应式数据。Vue 中 watch 和 watchEffect 的区别 …

C++ 日历计算器的实现

日历计算器 创建一个日期类对运算符进行重载代码 创建一个日期类 年月日为类的成员变量&#xff0c;所以放到私有区域&#xff0c;又因为成员变量为内置类型&#xff0c;编译器自动生成的默认构造函数对其不做处理&#xff0c;所以需要我们显示定义一个构造函数&#xff0c;而…

亿发:中小型制造企业数字化转型典型场景、痛点、解决方案

随着全球制造业的不断发展&#xff0c;中小型制造企业正面临前所未有的挑战和机遇。数字化转型成为了企业提升竞争力、优化生产效率、应对市场变化的关键路径。然而&#xff0c;对于资源相对有限的中小型制造企业而言&#xff0c;数字化转型并非易事。他们在推进转型的过程中往…

视频的编码与传输 学习笔记2 信息论

说白了&#xff0c;关键点就三个&#xff1a;信源&#xff0c;压缩与信道 DMS就是无记忆的信源&#xff0c;该输出什么就输出什么。 马尔卡夫信源&#xff0c;准确来讲是m马尔卡夫&#xff0c;会受到前m个状态的影响。&#xff08;妈的&#xff0c;马尔卡弗还在追我&#xff0c…

【工具分享】针对加解密综合利用后渗透工具 - DecryptTools

下载地址&#xff1a; 链接: https://pan.quark.cn/s/2e451bd65d79工具介绍 支持22种OA、CMS 加解密密码查询功能 万户OA 用友NC 金蝶EAS 蓝凌OA 致远OA 宏景ERP 湖南强智 金和jc6 瑞友天翼 金和C6 Navicat 华天动力 FinalShell 亿赛通 帆软报表 H3C CAS Weblogic 金蝶云星空…

基于SSM+Vue+MySQL的可视化高校公寓管理系统

系统展示 管理员界面 宿管界面 学生界面 系统背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范化管理。这样的…

51单片机-第十二节-LCD1602液晶显示屏

一、LCD1602介绍&#xff1a; LCD1602是一种字符型液晶显示屏&#xff0c;可以显示ASCII码的标准字符和其他的内置特殊字符。 显示容量&#xff1a;16*2个字符&#xff0c;每个字符为5*7点阵。 二、引脚及应用电路&#xff1a; 其中&#xff1a;D0-7这8位数据是接在P0引脚上…

聚焦2024数博会|与天空卫士一起探索AI与数据安全的融合应用

中国国际大数据产业博览会&#xff08;简称数博会&#xff09;&#xff0c;是全球首个以大数据为主题的博览会&#xff0c;自2015年创办以来&#xff0c;经过多年的深厚沉淀&#xff0c;数博会已发展成为国际知名、引领前沿趋势的专业展示合作平台。 2024年8月28日至30日&#…

T1打卡——mnist手写数字识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 1.定义GPU import tensorflow as tfgpustf.config.list_physical_devices("GPU")if gpus:gpu0gpus[0]tf.config.experimental.set_memort_groth(gp…

重卡换电连接器的应用

电动汽车换电模式涉及在专门充电站集中存储和充电大量电池&#xff0c;实现统一配送&#xff0c;并在换电站对电动汽车进行快速电池更换服务&#xff0c;或整合充电、物流、调配和换电服务。新能源汽车面临续航限制和配套设施不完善等问题&#xff0c;影响了其大规模推广。电池…

Java创建线程(5种方法)

操作系统提供api操作线程 线程本身是操作系统提供的&#xff0c;操作系统提供API让我们操作线程&#xff0c;JVM对操作系统api进行了封装&#xff0c;在线程这一部分&#xff0c;就提供了Thread类&#xff0c;表示线程。 创建线程 创建一个MyThread类&#xff08;类的名字不…

基于PI控制算法的异步感应电机转速控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于PI控制算法的异步感应电机转速控制系统simulink建模与仿真。PI控制器是一种经典的线性控制器&#xff0c;它通过将控制量的比例部分和积分部分相结合来实现对系统输出的调…