被问了100遍的 堆的基本功能如何实现? 绝了!!!

news2024/9/30 9:36:34

文章目录

  • 堆的介绍
    • 堆的概念
    • 堆的结构
  • 堆的向下调整算法
    • 建堆的时间复杂度
  • 堆的向上调整算法
  • 堆的基本功能实现
    • 初始化堆
    • 打印堆
    • 堆的插入
    • 堆的删除
    • 获取堆顶的数据
    • 获取堆的数据个数
    • 堆的判空
    • 销毁堆

堆的介绍

堆的概念

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

小堆:将根结点最小的堆叫做小堆,也叫最小堆或小根堆。
大堆:将根结点最大的堆叫做大堆,也叫最大堆或大根堆。

堆的性质
 堆中某个结点的值总是不大于或不小于其父结点的值。
 堆总是一棵完全二叉树。

堆的结构

在这里插入图片描述

堆的向下调整算法

现在我们给出一个数组,逻辑上看作一棵完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。
在这里插入图片描述
但是,使用向下调整算法需要满足一个前提
 若想将其调整为小堆,那么根结点的左右子树必须都为小堆。
 若想将其调整为大堆,那么根结点的左右子树必须都为大堆。
在这里插入图片描述
向下调整算法的基本思想(以建小堆为例):
 1.从根结点处开始,选出左右孩子中值较小的孩子。
 2.让小的孩子与其父亲进行比较。
 若小的孩子比父亲还小,则该孩子与其父亲的位置进行交换。并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。
 若小的孩子比父亲大,则不需处理了,调整完成,整个树已经是小堆了。

代码实例:

//交换函数
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

//堆的向下调整(小堆)
void AdjustDown(int* a, int n, int parent)
{
	//child记录左右孩子中值较小的孩子的下标
	int child = 2 * parent + 1;//先默认其左孩子的值较小
	while (child < n)
	{
		if (child + 1 < n&&a[child + 1] < a[child])//右孩子存在并且右孩子比左孩子还小
		{
			child++;//较小的孩子改为右孩子
		}
		if (a[child] < a[parent])//左右孩子中较小孩子的值比父结点还小
		{
			//将父结点与较小的子结点交换
			Swap(&a[child], &a[parent]);
			//继续向下进行调整
			parent = child;
			child = 2 * parent + 1;
		}
		else//已成堆
		{
			break;
		}
	}
}

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

上面说到,使用堆的向下调整算法需要满足其根结点的左右子树均为大堆或是小堆才行,那么如何才能将一个任意树调整为堆呢?
 答案很简单,我们只需要从倒数第一个非叶子结点开始,从后往前,按下标,依次作为根去向下调整即可。
在这里插入图片描述
代码实例:

	//建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, php->size, i);
	}

建堆的时间复杂度

那么建堆的时间复杂度又是多少呢?
 当结点数无穷大时,完全二叉树与其层数相同的满二叉树相比较来说,它们相差的结点数可以忽略不计,所以计算时间复杂度的时候我们可以将完全二叉树看作与其层数相同的满二叉树来进行计算。
在这里插入图片描述
在这里插入图片描述
总结一下:

堆的向下调整算法的时间复杂度:T ( n ) = O ( log ⁡ N ) T(n)=O(\log N)T(n)=O(logN)。
建堆的时间复杂度:T ( n ) = O ( N ) T(n)=O(N)T(n)=O(N)。

堆的向上调整算法

当我们在一个堆的末尾插入一个数据后,需要对堆进行调整,使其仍然是一个堆,这时需要用到堆的向上调整算法。
在这里插入图片描述

向上调整算法的基本思想(以建小堆为例):
 1.将目标结点与其父结点比较。
 2.若目标结点的值比其父结点的值小,则交换目标结点与其父结点的位置,并将原目标结点的父结点当作新的目标结点继续进行向上调整。若目标结点的值比其父结点的值大,则停止向上调整,此时该树已经是小堆了。
在这里插入图片描述

代码示例:

//交换函数
void Swap(HPDataType* x, HPDataType* y)
{
	HPDataType tmp = *x;
	*x = *y;
	*y = tmp;
}

//堆的向上调整(小堆)
void AdjustUp(HPDataType* a, int 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;
		}
	}
}

堆的基本功能实现

初始化堆

首先,必须创建一个堆类型,该类型中需包含堆的基本信息:存储数据的数组、堆中元素的个数以及当前堆的最大容量。

typedef int HPDataType;//堆中存储数据的类型

typedef struct Heap
{
	HPDataType* a;//用于存储数据的数组
	int size;//记录堆中已有元素个数
	int capacity;//记录堆的容量
}HP;

然后我们需要一个初始化函数,对刚创建的堆进行初始化,注意在初始化期间要将传入数据建堆。

//初始化堆
void HeapInit(HP* php, HPDataType* a, int n)
{
	assert(php);

	HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType)*n);//申请一个堆结构
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	php->a = tmp;
	memcpy(php->a, a, sizeof(HPDataType)*n);//拷贝数据到堆中
	php->size = n;
	php->capacity = n;
	int i = 0;
	//建堆
	for (i = (php->size - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, php->size, i);
	}
}

打印堆

打印堆中的数据,这里用了两种打印格式
第一种打印格式是按照堆的物理结构进行打印,即打印为一排连续的数字。
第二种打印格式是按照堆的逻辑结构进行打印,即打印成树形结构。

//求结点数为n的二叉树的深度
int depth(int n)
{
	assert(n >= 0);

	if (n>0)
	{
		int m = 2;
		int hight = 1;
		while (m < n + 1)
		{
			m *= 2;
			hight++;
		}
		return hight;
	}
	else
	{
		return 0;
	}
}

//打印堆
void HeapPrint(HP* php)
{
	assert(php);
	//按照物理结构进行打印
	int i = 0;
	for (i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
	//按照树形结构进行打印
	int h = depth(php->size);
	int N = (int)pow(2, h) - 1;//与该二叉树深度相同的满二叉树的结点总数
	int space = N - 1;//记录每一行前面的空格数
	int row = 1;//当前打印的行数
	int pos = 0;//待打印数据的下标
	while (1)
	{
		//打印前面的空格
		int i = 0;
		for (i = 0; i < space; i++)
		{
			printf(" ");
		}
		//打印数据和间距
		int count = (int)pow(2, row - 1);//每一行的数字个数
		while (count--)//打印一行
		{
			printf("%02d", php->a[pos++]);//打印数据
			if (pos >= php->size)//数据打印完毕
			{
				printf("\n");
				return;
			}
			int distance = (space + 1) * 2;//两个数之间的空格数
			while (distance--)//打印两个数之间的空格
			{
				printf(" ");
			}
		}
		printf("\n");
		row++;
		space = space / 2 - 1;
	}
}

堆的插入

数据插入时是插入到数组的末尾,即树形结构的最后一层的最后一个结点,所以插入数据后我们需要运用堆的向上调整算法对堆进行调整,使其在插入数据后仍然保持堆的结构。

//堆的插入
void HeapPush(HP* php, HPDataType x)
{
	assert(php);

	if (php->size == php->capacity)
	{
		HPDataType* tmp = (HPDataType*)realloc(php->a, 2 * php->capacity*sizeof(HPDataType));
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		php->a = tmp;
		php->capacity *= 2;
	}
	php->a[php->size] = x;
	php->size++;
	//向上调整
	AdjustUp(php->a, php->size - 1);
}

堆的删除

堆的删除,删除的是堆顶的元素,但是这个删除过程可并不是直接删除堆顶的数据,而是先将堆顶的数据与最后一个结点的位置交换,然后再删除最后一个结点,再对堆进行一次向下调整。
 原因:我们若是直接删除堆顶的数据,那么原堆后面数据的父子关系就全部打乱了,需要全体重新建堆,时间复杂度为O ( N ) O(N)O(N)。若是用上述方法,那么只需要对堆进行一次向下调整即可,因为此时根结点的左右子树都是小堆,我们只需要在根结点处进行一次向下调整即可,时间复杂度是:O(log(N))。

//堆的删除
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);//向下调整
}

获取堆顶的数据

获取堆顶的数据,即返回数组下标为0的数据。

//获取堆顶的数据
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	return php->a[0];//返回堆顶数据
}

获取堆的数据个数

获取堆的数据个数,即返回堆结构体中的size变量。

//获取堆中数据个数
int HeapSize(HP* php)
{
	assert(php);

	return php->size;//返回堆中数据个数
}

堆的判空

堆的判空,即判断堆结构体中的size变量是否为0。

//堆的判空
bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;//判断堆中数据是否为0
}

销毁堆

为了避免内存泄漏,使用完动态开辟的内存空间后都要及时释放该空间,所以,一个用于释放内存空间的函数是必不可少的。

//销毁堆
void HeapDestroy(HP* php)
{
	assert(php);

	free(php->a);//释放动态开辟的数组
	php->a = NULL;//及时置空
	php->size = 0;//元素个数置0
	php->capacity = 0;//容量置0
}

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

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

相关文章

计算机图形学-GAMES101-9

前言 材质和光的相互作用很重要。VertexShader和FragmentShader。纹理贴图Texture mapping。 一、在三角形中插值 为什么要在三角形内部插值&#xff1f;虽然我们的操作很多是在三角形顶点上进行计算的&#xff0c;但是对于三角形内部我们也希望每个像素点能得到一个值&…

FLASH锁死,STLink烧程序烧完一次无法再烧?

ST烧程序烧完一次无法再烧&#xff0c;因为把烧录引脚占用&#xff0c;所以可以再配置一下。 &#xff08;平时不勾PA13和PA14&#xff0c;也是会通过PA13和PA14烤录&#xff0c;勾上是为了防止锁死FLASH&#xff09; 如果锁住&#xff0c;再烧烧不进去 卡点&#xff0c;按住复…

【踩坑无数终极0错版】mac-Parallels Desktop的windwos虚拟机安装最新夜神模拟器+burpsuite证书安装+app渗透

文章目录 前言一、安装夜神模拟器二、夜神模拟器配置三、安装证书与所需软件四、测试抓包总结 前言 不想说了&#xff0c;反正我吐了&#xff0c;直接看正文吧。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、安装夜神模拟器 mac上是安装不成功…

Spring6新特性来了!便捷替代Feign封装RPC接口

spring6的新特性笔者最近也有在研究&#xff0c;其中在HttpServiceProxyFactory服务代理工厂的使用方式体验上&#xff0c;笔者认为极其像是在用Feign编写RPC接口&#xff0c;使用服务代理工厂我们只要在全局配置单例的服务代里工厂bean再维护一个http interface接口就能统一的…

跨域跨网访问延迟高?中科三方云解析智能线路提供最优解析方案

在日常工作生活中&#xff0c;大多数人都是直接通过域名访问web服务器&#xff0c;但计算机并不能直接识别域名&#xff0c;因此需要域名系统&#xff08;DNS&#xff0c;Domain Name System&#xff09;将域名翻译成可由计算机直接识别的IP地址&#xff0c;这个环节就是域名解…

MOSFET开关:电源变换器基础知识及应用

​MOSFET是一种常用的场效应晶体管&#xff0c;广泛应用于电源变换器中。电源变换器是一种将输入电源转换为输出电源的电路&#xff0c;通常用于电子设备中。在本文中&#xff0c;我们将介绍MOSFET开关及其在电源变换器中的基础知识和应用。 一、MOSFET开关的基础知识 MOSFET…

MySQL---单列索引(包括普通索引、唯一索引、主键索引)、组合索引、全文索引。

1. 索引 索引是通过某种算法&#xff0c;构建出一个数据模型&#xff0c;用于快速找出在某个列中有一特定值的行&#xff0c;不使用索 引&#xff0c;MySQL必须从第一条记录开始读完整个表&#xff0c;直到找出相关的行&#xff0c;表越大&#xff0c;查询数据所花费的 时间…

算法之单调栈常见题目

什么时候需要使用单调栈&#xff1f; 通常是一维数组&#xff0c;要寻找任意一个右边或者左边第一个比自己大或小的元素的位置&#xff0c;此时我们就想到可以使用单调栈了。 单调栈的本质是空间换时间&#xff0c;因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高…

电轨车运维作业vr实操培训平台训练一批高素质的维修型人才

卡车由于使用频繁、长期载重以及各种不可预测的外界作业技能人员的培训。基于web3d开发的卡车检修作业3d模拟仿真教学系统弥补了教学条件的不足&#xff0c;在提升培训效果、节省教学经费上有显著的作用。 深圳华锐视点研发的卡车检修作业3d模拟仿真教学系统实时动态展示三维仿…

GNN 学习记录

GNN 参考资料&#xff1a;https://www.bilibili.com/video/BV16v4y1b7x7 图网络为什么复杂 需要接受任意尺寸的输入没有固定的节点顺序和参考锚点&#xff08;比如文本是从前往后处理&#xff0c;图像是有像素点的&#xff0c;图没有起始点&#xff09;动态变化和多种模态的…

月入3000万,23岁美国女网红用AI分身交1000多男友!谈恋爱按分钟计费

来源 | 新智元 微信号&#xff1a;AI-era 【导读】这位23岁的女网红用GPT-4复刻了一个自己后&#xff0c;已经周入7万多美元了。不仅如此&#xff0c;短短几天内&#xff0c;候补名单上就排了差不多1万名男施主。 各种逼真的AI工具火了之后&#xff0c;各路心思活泛的选手都开…

Spring Boot 项目【前后端分离】 之架子搭建

Spring Boot 项目【前后端分离】 之架子搭建 注意如果看过我ssm项目的博客的项目的前端可以不需要看或者快速看一下即可 比较页面什么的一样主要是技术栈不同. 技术栈 - 使用了前后端分离&#xff0c;前端的主体框架Vue3&#xff0b;后端的基础框架Spring-Boot 1.前端技术栈…

SQL: STUFF()和FOR XML PATH的使用

STUFF(param1, startIndex, length, param2) 将param1中自startIndex(SQL中都是从1开始&#xff0c;而非0)起&#xff0c;删除length个字符&#xff0c;然后用param2替换删掉的字符。 示例&#xff1a; select STUFF(abcdefg,1,0,1234) --结果为1234abcdefg select ST…

每日一题163——矩阵置零

给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]] 示例 2&#xff1a; 输入&…

Docker与Docker-compose安装Vulfocus Vulhub漏洞环境

目录 一.docker 和 docker-compose 介绍&#xff1a; docker&#xff1a; docker-compose&#xff1a; 二者的区别&#xff1a; 二者的联系&#xff1a; 二者的总结&#xff1a; 二.Centos 7安装Docker 三.Centos 7安装docker-compose 四.docker-compose搭建Vulhub漏…

Linux安装MySQL后无法通过IP地址访问处理方法

本文主要总结Linux安装Mysql后&#xff0c;其他主机访问不了MySQL数据库的原因和解决方法 环境说明&#xff1a; MySQL 5.7.30CentOS Linux release 7.6.1810 (Core) 创建完Mysql数据库后可以查看mysql 日志获取root 用户登录密码 [rootlocalhost mysql-5.7.30]# cat /var/l…

2 机器学习知识 Softmax回归 deep learning system

机器学习算法的三个主要部分 The hypothesis class: 模型结构loss fuction 损失函数An optimization method&#xff1a;在训练集上减小loss的方法 多分类问题 训练数据&#xff1a; x ( i ) ∈ R n , y ( i ) ∈ 1 , . . . , k f o r i 1 , . . . m x^{(i)}\in \mathbb{R}…

在Fedora-Workstation-Live-x86_64-36-1.5中编译安装信使iptux0.7.6

在Fedora-Workstation-Live-x86_64-36-1.5中编译安装信使iptux0.7.6 https://github.com/iptux-src/iptux/tree/v0.7.6 下载信使iptux-0.7.6.zip&#xff0c;类似飞鸽传书ipmsg&#xff0c;已经尝试过0.8.3版本不成功 [rootfedora ~]# unzip /home/ruhong/download/iptux-0.7…

usb摄像头驱动-core层driver.c

usb摄像头驱动-core层driver.c 文章目录 usb摄像头驱动-core层driver.cusb_bus_typeusb_device_matchusb_uevent usb_register_driver 在ubuntu中接入罗技c920摄像头打印的信息如下&#xff1a; 在内核中&#xff0c;/driver/usb/core/driver.c 文件扮演了 USB 核心驱动程序管…

自动化遍历测试技术之android maxim遍历测试工具

这里写目录标题 一、问题1、例如app中存在问题2、解决方法3、改进策略4、自动遍历测试5、常见遍历工具与技术 二、android maxim 遍历测试工具策略使用环境预备命令行模式策略 三、android fastbot 遍历测试工具使用 一、问题 业务线众多 业务流程复杂 依赖传统券商一些资源 …