数据结构进阶之堆

今天我们学习的是数据结构里面的,大家先看看我们今天要学习的内容

一、堆概念及认识

在学习堆之前我们得先明白完全二叉树是什么样子,因为堆是依据完全二叉树的结构来实现的,所以在这里我先告诉大家完全二叉树的是什么,如下图:

完全二叉树就是从根节点开始依次往下延伸展开,其他层都是满节点,只有最后一层不同,最后一层可满可不满,但是最后一层必须是从左往右,这就是完全二叉树,也就是堆的逻辑结构。

堆的概念及认识:堆是一种数据结构,分为大堆和小堆,数据从上开始往下排序,上一层的数据如果大于下一层的每个数据,那么它就是大堆,反之就是小堆。

二、堆的结构及操作原理

堆的逻辑结构就是完全二叉树的结构,但是我们要实现自己的堆就需要了解堆的物理结构,它是如何实现对数据的储存的,在这里实现堆我们用数组来实现堆,大家可能会一脸懵,所以接下来我向大家解释为什么用数组来实现。

先看下图来理解:

如果把上层看作父节点,下层看作子节点,再看看它们的的数组下标大家会不会发现父子节点之间存在某种关系,这也是其他大佬发现的规律,在这里我直接告诉大家,用 parent 作为父节点的下标,child 作为子节点的下标,就会有下面的下标规律

child = 2 * parent + 1 ,parent = (child - 1)/  2 。

因为有上面的规律,所以我们实现自己的堆才选择用数组来储存,上面的规律也是堆实现的底层逻辑。

三、堆的实现

在这里我们以实现小堆为例,首先我得先告诉大家小堆的储存结构,大家先看下图:

如果是小堆,那么上一层的数据必须小于下一层的数据,并且最高层的数据是最小的,这就是小堆。那么接下来我们就来实现一个自己的堆。

首先需要创建一个结构体用来储存堆的数组和堆的大小和堆的数据个数,如下:

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;// 堆的物理结构时数组,逻辑结构是满二叉树或完全二叉树
	int size;
	int capacity;
}HP;

接下来我们先写出最简单的堆的初始化和销毁和一个交换函数,如下:

//堆的初始化
void HPInit(HP* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->capacity = hp->size = 0;
}

//堆的销毁
void HPDestroy(HP* hp)
{
	assert(hp);
	free(hp->a);
	hp->capacity = hp->size = 0;
}

//实现交换数据
void Swap(HPDataType* hp1, HPDataType* hp2)
{
	HPDataType tem = *hp1;
	*hp1 = *hp2;
	*hp2 = tem;
}

最难的部分就是堆数据放入时的向上调整,因为是小堆,所以上一层的数据必须小于下一层的每个数据,所以每次放进一个数据就要向上进行调整,用来保证小堆的结构,那如何来实现数据的想上调整呢,请看下面:

根据上面的讲解我们开始实现向上调整,请结合代码中的注释加以理解:

//堆数据的向上调整 时间复杂度:O(logN)
void AdjustUp(HP* hp, int child)
{
	assert(hp);
	assert(hp->size > 0);
	// 截至条件是 当最后一次的子节点小于或者等于0的时候 说明上层没有数据 也就不用再向上调整了
	while (child > 0)
	{
		// 实现数据的交换
		// 根据父子的关系规律来找到父节点
		int parent = (child - 1) / 2;
		if (hp->a[parent] > hp->a[child])
		{
			//交换
			Swap(&(hp->a[parent]), &(hp->a[child]));
			child = parent;
		}
		else
		{
			// 如果已经符合小堆 就直接退出
			break;
		}
	}
}

有了上面的向上调整,我们就可以开始实现数据的入队列了,操作如下:

//堆的放入数据
void HPPush(HP* hp, HPDataType x)
{
	assert(hp);
	if (hp->capacity == hp->size)
	{
		// 堆内存的开辟
		int newcapacity = hp->capacity == 0 ? 4 : 2 * hp->capacity;
		HPDataType* tem = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);
		if (tem == NULL)
		{
			perror("realloc");
			exit(1);
		}
		hp->a = tem;
		hp->capacity = newcapacity;
	}
	// 堆数据的放入
	hp->a[hp->size] = x;
	hp->size++;
	// 每次放入一个数据都需要进行向上调整来保证小堆的完整性
	AdjustUp(hp, hp->size - 1);
}

接下来我们继续实现堆的其他简单操作,如下:

//获取堆顶数据
HPDataType HPTop(HP* hp)
{
	assert(hp);
	return hp->a[0];
}

//堆中的数据个数
int HPSize(HP* hp)
{
	assert(hp);
	return hp->size - 1;
}

//堆的判空
bool HPEmpty(HP* hp)
{
	assert(hp);
	return hp->size == 0;
}

 接下来还有一个难点就是堆顶数据的删除,首先我们得了解一下堆顶数据是如何进行删除的,我来告诉大家,堆顶数据的删除不是真的删除,而是把堆顶的数据和堆的最后一位数据进行交换,并且对堆顶的数据再进行向下调整,来保证小堆的结构。上面我有提到了向下调整,这其实和向上调整一样,请大家看原理图:

那为什么要和两个子节点中较小的进行比较呢,因为要符合小堆,必须最高层数据是最小值,所以要和两个子节点中较小的进行比较,实现如下请结合注释一起理解:

//堆数据的向下调整 时间复杂度:logN
void AdjustDown(HP* hp, int parent)
{
	assert(hp);
	// 用父子节点之间的规律来找到子节点
	int child = 2 * parent + 1;
	// 退出条件为 最后一次的子节点超出堆的大小就不用在进行向下调整 直接退出循环
	while (child < hp->size)
	{
		// 要判断两个子节点中的最小值 首先这个父节点下面要有两个子节点才可以 如果没有就直接和子节点比较饥渴
		if (child + 1 < hp->size)
		{
			if (hp->a[child] > hp->a[child + 1])
			{
				// 假设法选出两个儿子中的最小值
				child = child + 1;
			}
		}
		if (hp->a[parent] > hp->a[child])
		{
			// 交换两个值
			Swap(&(hp->a[child]), &(hp->a[parent]));
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			// 如果没有交换 说明满足小堆的结构 直接退出即可
			break;
		}
	}

}

接下来堆顶数据的删除就可以结合堆顶数据的删除原则(如堆顶数据删除的原理图)就可以写出来了,如下:

//堆顶数据的删除
void HPPop(HP* hp)
{
	assert(hp);
	Swap(&(hp->a[0]), &(hp->a[hp->size - 1]));
	hp->size--;
	AdjustDown(hp, 0);
}

这就是堆的实现,大家还有什么不会的或者没理解的,可以评论区留言或者私信我,我来帮大家解答。

好了,今天的内容就到这,下期再见!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Qt+vstudio2022的报错信息积累

从今天开始记录一下平常开发工作中的报错记录&#xff0c;后续有错误动态补充&#xff01; 报错信息&#xff1a;【MSB8041】此项目需要 MFC 库。从 Visual Studio 安装程序(单个组件选项卡)为正在使用的任何工具集和体系结构安装它们。 解决&#xff1a; 背景&#xff1a;换…

Git回滚操作,工作区和暂存区恢复修改删除的文件

在利用git协作过程中&#xff0c;经常需要进行代码的撤销操作&#xff0c;这个行为可能发生在工作区&#xff0c;暂存区或者仓库区&#xff08;或版本库&#xff09;。 我们先讨论在工作区与暂存区发生的撤销行为&#xff0c;这里会有两个命令提供帮助&#xff0c;git restore…

普发Pfeiffer CCR263 CCR272 CMR261 CMR273 PBR260 IMR265 TPR265 使用说明手侧

普发Pfeiffer CCR263 CCR272 CMR261 CMR273 PBR260 IMR265 TPR265 使用说明手侧

4G/5G布控球/移动执法仪在电力巡检远程视频智能监控方案中的应用

一、背景与需求 随着科技的不断进步&#xff0c;视频监控技术已成为电力行业不可或缺的一环。电力行业的巡检及建设工作&#xff0c;因施工现场在人迹罕见的野外或山区&#xff0c;地形复杂多变&#xff0c;安全更是重中之重&#xff0c;现场工作的视频图像需实时传回监管中心…

用html写一个加载页面动画

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>加载页面动画Ⅴ</title><link rel"stylesheet" href"./style.css"> </head><body><div class"al…

【Git】安装 Git

文章目录 1. CentOS 下安装2. Ubuntu 下安装 Git 是开放源代码的代码托管工具&#xff0c;最早是在 Linux 下开发的。开始也只能应用于 Linux 平台&#xff0c;后面慢慢的被移植到 Windows 下。现在&#xff0c;Git 可以在 Linux、Unix、Mac 和 Windows 这几大平台上正常运行了…

【每日力扣】15. 三数之和与11. 盛最多水的容器

&#x1f525; 个人主页: 黑洞晓威 &#x1f600;你不必等到非常厉害&#xff0c;才敢开始&#xff0c;你需要开始&#xff0c;才会变的非常厉害 15. 三数之和 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k…

【Proteus】蜂鸣器播放音乐

按键按一次&#xff0c;蜂鸣器响一次 &#xff0c;LCD1602同步。 #include <REGX52.H> #include <INTRINS.H>unsigned int keynum; sbit RSP3^0; //** sbit RWP3^1; //** sbit EP3^2; //** sbit buzzerP1^5; void delay(unsigned int n)//1ms {unsigned char a,…

树莓派安装tensorflow

树莓派安装tensorflow 使用编译好的版本自己选择版本进行编译armv71 架构 教程转载 使用编译好的版本 下载tensorflow编译好的版本 https://github.com/lhelontra/tensorflow-on-arm/tags由于python版本支持有限可能需要自己安装python 安装对应的python 自己选择版本进行编译…

【Golang】并发编程之三大问题:原子性、有序性、可见性

目录 一、前言二、概念理解2.1 有序性2.2 原子性后果1&#xff1a;其它线程会读到中间态结果&#xff1a;后果2&#xff1a;修改结果被覆盖 2.3 可见性1&#xff09;store buffer(FIFO)引起的类似store-load乱序现象2&#xff09;store buffer(非FIFO)引起的类似store-store乱序…

Java web应用性能分析概叙

“系统慢”&#xff0c;这是任何一个应用都会出现的问题&#xff0c;面对“系统慢”的问题&#xff0c;客户、测试、开发、管理者等不同角色的人员有不同反应&#xff1a; 客户&#xff1a;啥破东西啊&#xff0c;这么卡&#xff01; 测试&#xff1a;性能bug已提交。 开发&…

嵌入式工程师如何摸鱼?

有老铁问我&#xff0c;做嵌入式开发要加班吗&#xff1f; 也不知道搞什么鬼&#xff0c;现在的年轻人对加班这么抵触。 我刚做开发那会&#xff0c;啥也不懂&#xff0c;每天基本都要加班到晚上7-9点不等&#xff0c;我并不抵触加班&#xff0c;因为早早回家&#xff0c;也没什…

常见的地图绘制方法,这个包全包了~~

在上一篇介绍完Bokeh精美可视化作品之后&#xff0c;有小伙伴咨询我能不能稍系统的介绍下如何在地图上添加如柱形图等其他元素的付方法&#xff1f; 这就让我想到一个优秀的地图绘制可视化包-R-cartography&#xff0c;虽然之前也有简单介绍过&#xff0c;本期就具体分享下该包…

Axure实现导航栏的展开与收缩

Axure实现导航栏的展开与收缩 一、概要介绍二、设计思路三、Axure制作导航栏四、技术细节五、小结 一、概要介绍 使用场景一般是B端后台系统需要以导航栏的展开与收缩实现原型的动态交互&#xff0c;主要使用区域是左边或者顶部的导航栏展开与收缩&#xff0c;同一级导航下的小…

【六】fastapi+vue前后端分离项目

前端代码 https://gitee.com/feiminjie/helloworldfront 后端代码 https://gitee.com/feiminjie/helloworld 整体效果 首页 用例管理页 用例详情页

在vue中发现一个prop新的写法在官方文档没有,查百度不行,还有什么其他方法排查不

先看图&#xff0c;最近在接手一个同事的代码&#xff0c;发现prop有这样的写法&#xff1a; 我自己查了官网&#xff0c;以及百度都没有找到这种写法。这时我灵机一动&#xff0c;想到一个方法&#xff0c;vscode有内置的typesscript&#xff0c;自然有prop类型推断&#xff0…

我用这10招,能减少了80%的BUG

前言 对于大部分程序员来说&#xff0c;主要的工作时间是在开发和修复BUG。 有可能修改了一个BUG&#xff0c;会导致几个新BUG的产生&#xff0c;不断循环。 那么&#xff0c;有没有办法能够减少BUG&#xff0c;保证代码质量&#xff0c;提升工作效率&#xff1f; 答案是肯…

:has()伪类使用

下面的 CSS 代码表示如果 <a> 元素里面有 <img> 元素&#xff0c;则这个 <a> 元素就会匹配。 a:has(img) { display: block; } 我们可以使用这个选择器轻松区分是文字链接还是图像链接 a:has(> img) { display: block; } 表示匹配子元素是 <img>…

NineData正式将SQL开发正式升级为数据库DevOps

NineData SQL 开发早期主要提供 SQL 窗口&#xff08;IDE&#xff09;功能&#xff0c;产品经过将近两年时间的打磨&#xff0c;新增了大量的企业级功能&#xff0c;时至今日已经服务了上万开发者&#xff0c;覆盖了数据库设计、开发、测试、变更等生命周期的功能。 为了让企业…

面试:sleep 和 wait

一、共同点 wait(),wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权&#xff0c;进入阻塞状态 二、不同点 1、方法归属不同 sleep(long)是Thread的静态方法而wait(), wait(long)都是Object的成员方法&#xff0c;每个对象都有 2、醒来的时机不同 执行sleep(l…