【数据结构】拆分详解 - 堆

news2024/10/6 6:50:50

文章目录

  • 前言
  • 一、堆是什么?
  • 二、堆的接口实现(以小堆为例)
    •   0.声明
    •   1. 创建,初始化
    •   2. 销毁
    •   3. 插入
      •    3.1  向上调整
    •   4. 删除
      •    4.1 向下调整
    •   5. 获取堆顶元素值
    •   6. 获取有效元素个数
    •   7. 判断是否为空
  • 三、两种建堆算法分析
    •   1. 向上调整算法
    •   2. 向下调整算法
    •   3. 时间复杂度分析
  • 总结


前言

文章细分了各个知识点,可在目录中快速跳转
手机端用户在查看代码块时建议点击代码右上角放大查看,每一段代码均有完整注释。

本文介绍堆的定义及接口实现。如果对顺序表和二叉树还不熟悉的可以参考【数据结构】拆分详解 - 顺序表;树与二叉树


一、堆是什么?

  • 定义:堆是二叉树的顺序存储结构。
    本质上就是一个顺序表(数组),但逻辑上是一颗完全二叉树。就像人一样,我们都是人,但人与人之间差异明显,我们要关注的是堆的逻辑内在。

需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

  • 分类
  1. 大堆:任意一个父亲 <= 孩子(数值大小)
  2. 小堆: 任意一个父亲 >= 孩子(数值大小)
  • 性质
  1. 堆中某个节点的值总是不大于或不小于其父节点的值
  2. 堆总是一棵完全二叉树

在这里插入图片描述

二、堆的接口实现(以小堆为例)

  0.声明

typedef int HPDataType;  //注释1

typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;

}HP;  //注释2

注释:

  1. 由于后期我们可能会改变结构体存储数据的类型,而一但更改,我们需要对每一个调用该类型的地方进行修改,十分麻烦,使用typedef对数据的类型进行重命名,这样以后要更换类型只需要更改此处就可以达到全文替换的目的

  2. 重命名简便后续输入,注意我们为什么不直接命名简便一点呢?每个命名都是基于英文单词的释义,这样可以增加在多人协作,以及后续维护代码时的可读性。

  1. 创建,初始化

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

  2. 销毁

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

  3. 插入

  • 思路
  1. 尾插
  2. 向上调整
void HeapPush(HP* php, HPDataType x)
{
	assert(php);

	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		//注意tmp只有在需要扩容时才会创建,赋值a应该放在分支内,否则会在不需要扩容时出问题
		php->a = tmp;
		php->capacity = newcapacity;
	}

	php->a[php->size] = x;
	php->size++;
	
	AdjustUp(php->a, php->size - 1);
}

   3.1  向上调整

从下往上,找父亲,比较父亲与孩子的值大小,不符合堆的定义就进行调整(互换)

  1. 利用父亲与孩子的下标关系,找到当前孩子对应的父亲,比较调整
  2. 调整完继续向上找父亲的父亲,不断调整,直到找到根,或者符合堆的定义(父亲 >= 孩子 或 父亲 <= 孩子,本文以小堆为例,则为父亲 <= 孩子时停止)
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;  //完全二叉树数组实现性质:双亲与孩子的下标关系
	
	//child == 0 结束,不论child为奇为偶,最后一次都是(1-1)/2 = 0,即找到根
	while (child > 0)
	{
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			child = parent;
			parent = (parent - 1) / 2;  //同上,下标关系
		}
		else  //向上调整的前提是除了新插入的数据,其他部分为堆,因此只要找到符合的堆的父亲就可以跳出循环了
		{
			break;
		}
	}
}

  4. 删除

  • 目的性:之前我们所学的顺序表和链表没有很强的目的性,只是选择容易实现的方式进行接口实现,但我们应当认识到数据结构发明出来是有目的的,如堆可以进行堆排序,TopK问题(选出最大/最小的前K个数),如删除接口的实现,我们知道顺序表的尾删比较简单,时间复杂度低,但在堆中直接尾删会破坏子数的堆性质,使得结构混乱。堆的目的是为了对数据进行选择排序,子树不再为堆,后续排序也就无从谈起了。后续我们会讲解堆的应用,读者会更好的认识到这一点。
  • 思路
  1. 交换头尾
  2. 尾删
  3. 向下调整
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDown(php->a, php->size, 0);
}

   4.1 向下调整

从上往下,找孩子,比较父亲与孩子的值大小,不符合堆的定义就进行调整(互换)

  1. 利用父亲与孩子的下标关系,找到当前父亲对应的孩子,比较调整
  2. 调整完继续向下找孩子的孩子,不断调整,直到遍历堆,或者符合堆的定义(父亲 >= 孩子 或 父亲 <= 孩子,本文以小堆为例,则为父亲 <= 孩子时停止)
void AdjustDown(HPDataType* a, int size, int parent)
{
	//不知道父亲的左右孩子谁小,故假设法假设左孩子较小,若假设错误则调整为右孩子
	int child = parent * 2 + 1;
	while (child < size)
	{
		//右孩子较小就改为右孩子
		if (a[child] > a[child + 1])
		{
			child++;
		}
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			parent = child;
			child = child * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

  5. 获取堆顶元素值

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}

  6. 获取有效元素个数

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

	return php->size;
}

  7. 判断是否为空

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

	return php->size == 0;
}

三、两种建堆算法分析

上文我们在堆插入堆删除时分别粗略介绍、被动使用了向上调整向下调整算法进行调整,是建立在原先就已经为堆的基础上,准确地说是左右子树均为堆的前提下。如果给一个数组,让你直接建堆,即左右子树不为堆的情况下,应该如何操作

  1. 向上调整算法

从上往下。从第二层往下遍历堆,将所有节点都访问一次进行向上调整。

  2. 向下调整算法

从下往上。从倒数第一个非叶子节点所在层次开始向上,将所有节点都访问一次进行向下调整,直到访问到根节点。

在这里插入图片描述

  3. 时间复杂度分析

  • 向下调整

在这里插入图片描述

公式:【每层调整次数 = 节点数 * (倒数的层数-1)(即向下调整的次数)】
求和 :等差 * 等比 -> 错位相减
运算在这里插入图片描述
因此:建堆的时间复杂度为O(N)。

  • 向上调整

公式:【每层调整次数 = 节点数 * (层数-1)(即向上调整的次数)】
求和:等差 * 等比 => 错位相减
运算在这里插入图片描述
因此:建堆的时间复杂度为O(N*logN)。


总结

知识逻辑框架可看文章开头的目录。
文章中有什么不对的丶可改正的丶可优化的地方,欢迎各位来评论区指点交流,博主看到后会一一回复。

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

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

相关文章

一线大厂Redis高并发缓存架构(待完善)

场景1&#xff1a;秒杀库存场景&#xff0c; 10000人抢100个商品 如果用普通的分布式锁实现&#xff0c; 最后抢到的人&#xff0c;要等前面99个人抢完 优化方案&#xff1a;可用分段锁&#xff0c; 降低锁的粒度&#xff0c; 比如1-10库存用锁product:101_1,11-20库存用锁pr…

电梯导航的小练习

目录 css代码 html代码 js代码 完整代码 效果图 需求&#xff1a;点击某个模块&#xff0c;显示对应内容 css代码 <style>*{padding: 0;margin: 0;list-style: none;}ul{display: flex;justify-content: center;position: fixed;top: 0;left: 20%;}ul>li{text-…

【解决方案】基于物联网表计的综合能源管理方案

安科瑞顾强 为加快推进国家“双碳”战略和新型能源体系建设&#xff0c;努力实现负荷准确控制和用户精细化管理&#xff0c;按照“政府主导、电网组织、政企协同、用户实施”的指导原则&#xff0c;多地成立市/县级电力负荷管理中心&#xff0c;包括浙江宁波、慈溪、辽宁大连、…

git的相关实用命令

参看文章&#xff1a;https://blog.csdn.net/qq_21688871/article/details/130158888 http://www.mobiletrain.org/about/BBS/159885.html 1、git commit后&#xff0c;但发现文件有误&#xff0c;不想push(提交到本地库&#xff0c;回退到暂存区&#xff09; git reset --sof…

sd_webui的实用插件,prompt/lama/human matting/...

热烈欢迎大家在git上star&#xff01;&#xff01;&#xff01;冲鸭&#xff01;&#xff01;&#xff01; 1.prompt优化插件 GitHub - leeguandong/sd_webui_beautifulprompt: beautifulprompt extension performs stable diffusion automatic prompt engineering on a bro…

LLM;超越记忆《第 2 部分 》

一、说明 在这篇博客中&#xff0c;我深入研究了将大型语言模型&#xff08;LLM&#xff09;提升到基本记忆之上的数学框架。我们探索了动态上下文学习、连续空间插值及其生成能力&#xff0c;揭示了 LLM 如何理解、适应和创新超越传统机器学习模型。 LLM代表了人工智能的重大飞…

集简云语聚AI新增模型测试,支持多模型同时进行交互,快速评估不同模型性能

语聚AI模型测试 在ChatGPT爆火的推动下&#xff0c;由生成式 AI 掀起的全球人工智能新浪潮就此拉开了序幕&#xff0c;人工智能也成为越来越多企业提升业务效率、优化业务流程的首选方案。 然而&#xff0c;面对层出不穷的AI模型&#xff0c;每个模型在完善度、功能性、易用性…

rank的相关loss

1、相关loss 1.1、loss相关简介 排序优化时&#xff0c;主要从三个角度来考虑构建loss&#xff0c;分别为pointwise、pairwise、listwise。pointwise将排序所有query当成一个整体&#xff0c;计算每个<query,doc>对的loss,相当于一个二分问题。pairwise以每个query为维…

快照读通过MVCC解决不可重复读当前读通过间隙锁解决幻读

简介 Multi-Version Concurrency Control 多版本并发控制&#xff0c;MVCC 是一种并发控制的方法&#xff0c;一般在数据库管理系统中&#xff0c;实现对数据库的并发访问&#xff1b;在编程语言中实现事务内存。 *往期知识不做重点 事务具有4个特征,分别是原子性、一致性、隔…

HarmonyOS脚手架:UI组件之文本和图片

主要实现UI组件文本和图片的常见效果查看&#xff0c;本身功能特别的简单&#xff0c;其目的也是很明确&#xff0c;方便大家根据效果查看相关代码实现&#xff0c;可以很方便的进行复制使用&#xff0c;当然了&#xff0c;这些所谓的小功能都是开胃小菜&#xff0c;脚手架的最…

Redis数据结构之跳表

跳表是一种有序的数据结构&#xff0c;它通过在每个节点中维持多个指向其他节点的指针&#xff0c;从而达到快速访问节点的目的。其核心思想就是通过建立多级索引来实现空间换时间。 在Redis中&#xff0c;使用跳表作为Zset的一种底层实现之一&#xff0c;这也是跳表在Redis中的…

西南科技大学(数据结构A)期末自测练习五

一、选择题&#xff08;每空 1 分&#xff0c;共 5 分&#xff09; 1、下面关于图的叙述中&#xff0c;正确的是&#xff08; &#xff09;。 (1)&#xff0e;回路是简单路径 (2)&#xff0e;存稀疏矩阵&#xff0c;用邻接矩阵比邻接表更省空间 (3)&#xff0e;若有像图中存在…

Seaborn可视化图形绘制_Python数据分析与可视化

Seaborn可视化图形绘制 频次直方图、KDE和密度图矩阵图分面频次直方图条形图折线图 Seaborn的主要思想是用高级命令为统计数据探索和统计模型拟合创建各种图形&#xff0c;下面将介绍一些Seaborn中的数据集和图形类型。 虽然所有这些图形都可以用Matplotlib命令实现&#xff08…

MySQL的系统信息函数

系统信息函数让你更好的使用MySQL数据库 1、version()函数 查看MySQL系统版本信息号 select version();2、connection_id()函数 查看当前登入用户的连接次数 直接调用CONNECTION_ID()函数--不需任何参数--就可以看到当下连接MySQL服务器的连接次数&#xff0c;不同时间段该…

深度学习第4天:感知机模型

☁️主页 Nowl &#x1f525;专栏《机器学习实战》 《机器学习》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 ​ 文章目录 感知机模型介绍 神经网络搭建感知机 结构 准备训练数据 感知机的损失函数与优化方法 测试结果 完整代码 多层感知机 结语 感知机模…

【C语言】【字符串函数的模拟实现】strcpy,strcat,strcmp,strncpy,strncat,strstr

1.strcpy char* strcpy(char*destination,const char* source)源字符串必须以’\0’结尾会将原字符串中的‘\0’拷贝到目标字符串中目标空间必须足够大&#xff0c;能放得下源字符串 模拟实现&#xff1a; char *my_strcpy(char* des,const char *sour) {char* retdes;asser…

MinkowskiEngine安装

本人配置&#xff1a; cuda10.1, gcc7.5.0, g7.5.0 gcc --version # 查看gcc版本代码 g --version #查看g版本代码安装步骤&#xff1a; pip install ninja # 安装依赖git clone https://github.com/NVIDIA/MinkowskiEngine.git # 下载到本地 cd MinkowskiEngine # 进入…

大三上oracle数据库期末复习

1、创建表空间 2、创建用户 3、用户授权 oracle数据库逻辑存储结构&#xff1a; 1、表空间&#xff08;最大的逻辑存储单元&#xff09; 创建表空间 2、段 3、盘区&#xff08;最小的磁盘空间分配单元&#xff09; 4、数据块&#xff08;最小的数据读写单元&#xff09; 用…

Java核心知识点整理大全26-笔记

目录 27. Storm 7.1.1. 概念 27.1.1. 集群架构 27.1.1.1. Nimbus&#xff08;master-代码分发给 Supervisor&#xff09; 27.1.1.2. Supervisor&#xff08;slave-管理 Worker 进程的启动和终止&#xff09; 27.1.1.3. Worker&#xff08;具体处理组件逻辑的进程&#xff…

周报:浅谈对豆瓣网页实战的注意事项

制作整体网页时HTML代码和CSS代码的常用处理方法&#xff1a; 分开HTML代码和CSS代码&#xff0c;专门制作一个CSS文件专门来放置css代码&#xff0c;css文件里一般有作者样式(XXX.css)和通用样式(common.css)。这样会使代码更易维护&#xff0c;且整齐美观。 写代码前的注意…