堆来咯!!!

news2024/11/18 22:42:43

堆是什么?

是土堆吗?

那当然不是啦~

堆是一种被看作完全二叉树的数组。

那么什么是完全二叉树呢?

如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。

堆的特点:对于这个树的任意节点来说,满足根节点大于左右子树的值(大堆),或者任意一节点满足根节点的值小于左右子树的值(小堆)

那么来看一下堆是如何?

是不是很好理解?其实本质就是一个装着数据的数组,但我们将它想象成一个树,

 一棵完全二叉树。

而且这棵二叉树是一个小根堆,即父亲小于或等于它的左子树和右子树的值

那么我们就来实现这个堆的基本操作吧

堆的基本操作

1.定义堆的结构体

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* data;
	int size;//堆当前的有效元素个数
	int capacity;//堆的容量
}Heap;

由于堆是一个数组,所以我们需要用顺序表的形式动态的管理它。所以在这个结构体里头我们定义了该结构体数据类型的数组用来存储数据,size用于展示当前堆的有效元素个数,capacity用于展示当前堆的容量。

2.堆的初始化

Heap* HeapInit()
{
	Heap* hp = (Heap*)malloc(sizeof(Heap));
	if (hp == NULL)
	{
		perror("malloc fail");
		return;
	}
	HPDataType* data = (HPDataType*)malloc(sizeof(HPDataType) * 2);
	if (data == NULL)
	{
		perror("malloc fail");
		return;
	}
	hp->data = data;
	hp->capacity = 2;
	hp->size = 0;
	return hp;
}

在堆区建立一个堆的结构体,让里面的数组大小设置为2(纯属个人习惯),两次malloc可能都会弄成NULL,所以一定要记得用if检查一下,之后perror一下malloc的错误,如果创建成功,那么将该结构体的数据依次设置完毕,返回该结构体的指针。

3.堆的销毁

void HeapDestory(Heap* hp)
{
	assert(hp);

	free(hp->data);
	hp->capacity = 0;
	hp->size = 0;
	free(hp);
	hp = NULL;
}

这个还是比较简单的,只需要先将堆的结构体里面的data释放,再将该结构体释放即可,当然两个顺序千万别反过来。free了之后记得养成好习惯将指针都置为NULL

4.堆的向下调整

这是堆的基本操作中最重要的操作之一,那么我们先来看一下向下调整的相关规则:

ex:以小根堆为例

1.先设置当前结点为根结点,之后通过根节点找到左右子树结点,之后比较左右子树的值,通过比较两个值,选出的那个数值较小的值的结点作为child。

2.再比较根结点(父母结点)和那个数值较小的child结点的值,如果child的结点比parent的结点的值更小,那么两个结点进行交换。

3.如果child结点比parent结点的值更大,那么已符合小根堆的规则,那么我们就会调整结束。

4.处理完这个结点后,如果符合第2条规律,就继续以该child结点进行循环操作,直到出现child < n或者符合第3条规则。

那么就来看我们的代码叭~

void AdjustDown(HPDataType* data, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		//找出最小/大的那个孩子,先默认最小/大为左孩子,而且,child+1小于n
		if (child + 1 < n && data[child] > data[child + 1])
		{
			child++;
		}
		if (data[child] < data[parent])
		{
			Swap(&data[child], &data[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

可能有人发现了,为什么我的左右孩子节点能够通过parent * 2 + 1或parent * 2 + 2获得,那是因为堆是一棵完全二叉树,又因为根节点初始的下标是0,那么通过对该特点的分析,我们就可以得出:

1.childleft = parent * 2 + 1, childright = parent * 2 + 2;

2.parent = (child - 1) / 2

5.堆的向上调整

我们还是来看堆的向上调整的相关规则:

1.先设置当前结点为孩子结点,通过上述的相关结论,我们通过孩子找到了它的父母结点。

2.比较父母结点的值和孩子结点的值,如果孩子结点的值比较小,那么我们就交换两个结点。

3.如果孩子结点的值比较大,那么我们就调整结束

4.如果符合第2条规则,我们会将parent作为新的孩子继续循环,直到child > 0或者符合第3条规则

void AdjustUp(HPDataType* data, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//我默认为小根堆
		if (data[child] < data[parent])
		{
			Swap(&data[child], &data[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

6.获取堆顶元素的值

直接来看代码:

HPDataType HeapTop(HP* php)
{
	assert(php);

	return (php->data[0]);
}

这就没什么好说的了,数组嘛,直接下标访问即可

7.判断堆是否为空

bool HeapEmpty(HP* php)
{
	assert(php);

	return (php->size) == 0;
}

只需要判断这个堆的有效元素个数是否为0即可

8.堆的有效元素个数

int HeapSize(HP* php)
{
	assert(php);

	return php->size;
}

9.入堆操作

展示代码之前,先提几个问题:

1.一个元素从堆这个数组的尾部直接入堆,这个数组是否还是一个大根堆或一个小根堆呢?

很显然,这并不一定是一个大根堆或小根堆。

2.那么如果从堆的头部入堆,是否能是一个大根堆或是一个小根堆呢?

很显然,这比第一点还要离谱一些,不仅仅只是大根堆和小根堆的问题了,如果从头部头插入堆,首先就是移动后面的数据就很麻烦,如果数据量大,那要移动到猴年马月?而且,如果头插入堆,会直接把堆原本的父子结构给破坏了,那这样就得不偿失了。

所以我们一定是只能先从尾部进行入堆之后再进行其他的操作,让整个数组重新变成一个堆。

那么我们怎么才能让这个数组重新变成一个堆呢?

我们来看一下下面的一组图:

 我们通过这个图也就可以得出插入数据的完整过程了

1.先将想要入堆的数据插入整个数组的最尾部

2.之后通过向上调整,将该数据调整到合适的位置即可,这就完成了堆的插入数据,这样还能让这个数组保持堆的性质,不会破坏整个堆的父子关系结构。

那么就来看代码叭!

void HeapPush(HP* php, HPDataType x)
{
	assert(php);

	//push之前判断堆满了没
	if (php->capacity == php->size)
	{
		HPDataType* tmp = (HPDataType*)realloc(php->data, sizeof(HPDataType) * php->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->capacity = php->capacity * 2;
		php->data = tmp;
	}
	php->data[php->size] = x;
	(php->size)++;

	AdjustUp(php->data, php->size - 1);
}

当然啊,一定要判断这个堆是否满了,满了一定要先realloc一下这个数组,让我们的数组能够正常地存入数组当中,将数组的有效元素个数自增1。

10.出堆顶数据操作

还是先来问一个问题,直接让数组的第一个元素出堆能不能直接解决问题?

是的,不能,我们不能直接删除,如果删了之后,我们需要一个个元素移动,而且我们上面已经提到过了,如果移动整个数组,会影响我们的堆的父子关系的结构,所以我们不能直接删除。

那么我们应该如何删除呢?

来看下面的图:

这图直接就能让我有了思路:

1.先将数组的首元素和数组的尾元素调换位置

2.之后将size--,我们就可以直接删除这个元素了。

3.我们再让堆顶的元素向下调整,就能让我们的数组重新变成一个堆。

嗯,完美!

直接来看代码叭!

void HeapPop(HP* php)
{
	assert(php);
	//删除数据的时候要判断堆是否为空
	assert(!HeapEmpty(php));

	Swap(&(php->data[0]), &(php->data[php->size - 1]));
	(php->size)--;
	AdjustDown(php->data, php->size, 0);
}

当然,还是要判断我们的堆是否为空,空了的话,我们就删了个寂寞了。

那么,这就是整个堆了啦!

那么堆排序到后面的排序算法的时候再将,拜拜~

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

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

相关文章

开源自动化测试框架有哪些?怎么选择适合自己的

目录 前言 一、Selenium 二、Appium 三、Robot Framework 四、Cypress 五、TestCafe 六、Nightwatch.js 七、JUnit 八、Pytest 总结&#xff1a; 前言 开源自动化测试框架是现代软件开发和测试领域中不可或缺的一部分。它们使得测试人员能够快速、准确地执行测试用例…

Mysql(十) -- 常见问题处理

1. MySQL数据库cpu飙升的话你会如何分析 重点是定位问题。 使用top观察mysqld的cpu利用率 切换到常用的数据库使用show full processlist;查看会话观察是哪些sql消耗了资源&#xff0c;其中重点观察state指标定位到具体sql pidstat 定位到线程在PERFORMANCE_SCHEMA.THREADS中…

HuggingGPT强势来袭,LLM+专家模型,迈向更通用的AI

出品人&#xff1a;Towhee 技术团队 超级组合&#xff1a;HuggingFace ChatGPT HuggingGPT强势来袭。人类仿佛距离真正的AGI又更近了一步。 HuggingGPT是浙江大学与微软亚洲研究院的联手研究&#xff0c;发布之后迅速引发关注&#xff0c;已经开源。 它的使用非常简单&#x…

计算机时间旅行者:NTP如何帮助计算机在时间上保持同步?

应用场景&#xff1a; NTP&#xff08;网络时间协议&#xff09;是一种用于同步计算机时钟的协议&#xff0c;它可以让多台计算机在网络上保持同步的时间。因此&#xff0c;NTP可以应用于各种需要时间同步的应用程序中&#xff0c;例如&#xff1a; 计算机网络&#xff1a;在…

《花雕学AI》20:ChatGPT使用之体验评测AI EDU的网页版+桌面端+Android+App store组合

最近准备出门&#xff0c;要去新疆哈密参加活动&#xff0c;一直在寻找手机上可用的AI移动端。昨天在网上偶然找到了AI EDU&#xff08;这个不是 MSRA 创立的人工智能开源社区&#xff09;&#xff0c;其链接是&#xff1a;https://ai.aigcfun.com&#xff0c;今天就尝试做个相…

三菱FX2N PLC与昆仑通态(MCGS)之间无线通讯

三菱FX2N PLC与昆仑通态&#xff08;MCGS&#xff09;之间建立无线通讯&#xff0c;其实就是昆仑通态使用三菱 PLC FX2N 通信口扩展 485 接口通过专用协议对 PLC 站点相应寄存器进行读写操作&#xff0c; 从而实现对站点工作状态的控制。 这种情况下&#xff0c;可以使用三菱PL…

ChatGPT对我们的影响-ChatGPT能给我们带来什么

ChatGPT日常应用 ChatGPT是一种应用广泛的自然语言处理算法&#xff0c;其可以应用于多种日常场景。以下是一些ChatGPT的日常应用&#xff1a; 聊天机器人&#xff1a;ChatGPT可用于构建聊天机器人&#xff0c;通过与用户进行自然语言交互来提供个性化的服务和支持。 新闻稿和…

配置springboot的静态资源访问地址为本机,并使用docker部署

springboot版本 2.7.4开发工具 IDEA 2021.2.4 1. SpringBoot提供的默认静态资源访问方法 resouces目录下的 /static、 /public、 /resouces、 /META-INF/resouces、这四个目录下放置的静态资源都可以通过直接通过以下链接访问 http://localhost:8080/aaa.png 2. 将SpringBoo…

入门力扣自学笔记256 C++ (题目编号:1019)

1019. 链表中的下一个更大节点 题目&#xff1a; 给定一个长度为 n 的链表 head 对于列表中的每个节点&#xff0c;查找下一个 更大节点 的值。也就是说&#xff0c;对于每个节点&#xff0c;找到它旁边的第一个节点的值&#xff0c;这个节点的值 严格大于 它的值。 返回一…

C++中COM组件管理思想实践

在开发一个产品时&#xff0c;其往往有很多子模块&#xff0c;子业务和子功能&#xff0c;这些都可以抽象成组件&#xff08;C中本质表现就是一个类&#xff09;。但是如何更好的管理这些类的对象呢&#xff1f;我们可以借鉴微软的COM组件思想来进行对象的注册&#xff0c;创建…

OpenHarmony标准系统开机时长优化

简介 万物互联时代&#xff0c;产品性能至关重要&#xff0c;而系统启动时间是系统性能的重要组成部分&#xff0c;因为用户必须等待系统启动完成后才能使用设备。对于经常需要进行冷启动的汽车等设备而言&#xff0c;较短的启动时间至关重要&#xff08;没有人喜欢在等待几十秒…

如何手写一个文件索引工具everything(第一章)

第一章&#xff08;NTFS格式及USN日志&#xff09; 背景介绍 Windows平台的Everything文件查找速度非常快&#xff0c;优势在于利用了NTFS的USN日志&#xff0c;以及Windows上的文件监测机制我们也可以仿照类似原理&#xff0c;通过查询USN日志、监测Windows平台文件修改、使…

亚马逊卖家如何用facebook推广?

亚马逊作为全球最大的电商平台之一&#xff0c;吸引了大量的卖家和买家。对于亚马逊的卖家而言&#xff0c;如何进行有效的推广和引流成为了他们最关心的问题之一。其中&#xff0c;利用Facebook广告为亚马逊进行推广是一种较为常见的做法。 但是&#xff0c;亚马逊可以用Face…

springboot实现修改用户信息功能

目录 1、UserEntity层 2、UserMapper层 3、UserService层 4、UserController类 5、Postman测试 要实现修改用户信息的功能&#xff0c;需要编写对应的代码&#xff1a; 如&#xff1a; 在UserEntity中定义用户实体类的属性。 在UserMapper中编写修改用户的SQL语句&#…

【C++】哈希的应用 -- 布隆过滤器

文章目录一、布隆过滤器的引入二、哈希函数个数的选择三、布隆过滤器的实现四、布隆过滤器的应用五、布隆过滤器总结一、布隆过滤器的引入 我们在上一节中学习了 位图&#xff0c;知道了位图可以用来快速判断某个数据是否在一个集合中&#xff0c;但是位图有如下的缺点&#x…

Qemu虚拟机读取物理机的物理网卡的流量信息方法

项目背景&#xff1a; 比如我有三个项目 A&#xff0c;B&#xff0c;C&#xff1b;其中A项目部署在物理机上&#xff0c;B&#xff0c;C项目部署在 虚拟机V1,V2中&#xff0c;三个项目接口需要相互调用。 需要解决的问题点&#xff1a; 1&#xff0c;因为A&#xff0c;B&#x…

2016-2017 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2016)题解

2016-2017 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2016) A - Artwork 题目描述&#xff1a; 给定N*M的网格&#xff0c;给出Q次询问&#xff0c;每次询问都给出一个小矩阵&#xff0c;保证每个矩阵要么长为1&#xff0c;要么宽为1&#xff0c;将网格中矩阵部…

最近给shopify跨境电商网站搞google搜索引擎的seo优化,整理了一些内容

接到一个网站&#xff0c;首先要做一些工作&#xff0c;然后按照这个步骤做好每一步&#xff0c;网站的搜索排名会有明显的效果。 对网站进行技术审核&#xff0c;以确保它符合搜索引擎的技术要求。研究关键词并确定目标关键词。优化网站内容&#xff0c;以便更好地针对目标关…

【LeetCode】剑指 Offer 55. 二叉树的深度 p271 -- Java Version

1. 题目介绍&#xff08;55. 二叉树的深度 &#xff09; 面试题55&#xff1a;二叉树的深度&#xff0c; 一共分为两小题&#xff1a; 题目一&#xff1a;二叉树的深度题目二&#xff1a;平衡二叉树 2. 题目1&#xff1a;二叉树的深度 题目链接&#xff1a;https://leetcode.c…

简单写一个Avue增删改查

今天练习了一下avue&#xff0c;真的好用&#xff0c;个人感觉相比于用element plus的组件还方便&#xff01; 简简单单的写了一个页面的增删改查&#xff0c;思路很简单。如果在写那种后台管理项目&#xff0c;基本上全是列表页&#xff0c;用这种方法写出来第一页&#xff0c…