【初阶数据结构】——堆的引入和实现二叉树

news2024/11/25 4:06:21

目录

前言

一、二叉树的顺序结构及实现 

1.1二叉树的顺序结构

1.2堆的结构

二、堆的实现

2.1堆向上调整算法(堆的插入)

2.2堆向下调整算法(堆的删除)

2.3建堆的时间复杂度

2.4堆的创建

2.5堆的初始化和空间的销毁

2.6堆的插入

向上调整函数

交换函数

2.7堆的删除

向下调整函数

2.8堆的打印、取值、判空

三、完整代码


前言

上篇文章简单介绍树,讲解了最基本的二叉树,以及二叉树使用数组存储的顺序结构和使用链表存储的链式结构两种存储方式,今天就引入堆来实现二叉树。


一、二叉树的顺序结构及实现 

1.1二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而满二叉树和完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

1.2堆的结构

如果有一个关键码的集合K = { k0,k1 ,k1 ,…, },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki<=K2*i+1 且Ki <=K2*i+2 (Ki >=K2*i+1 且Ki >=K2*i+2 ) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。 

堆的性质:

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

大堆:任何父亲大于等于孩子

小堆:任何父亲小于等于孩子
堆总是一棵完全二叉树。


二、堆的实现

2.1堆向上调整算法(堆的插入)

我们直到在数组中插入数据是在末尾插入,那么用堆来表示就是在有效数据下面做孩子或者父亲,依次插入数据和上面的父亲结点作比较,如果父亲大了就将父亲和孩子互换,一直换到度也就是第一个结点就形成小堆,反之则形成大堆。 


2.2堆向下调整算法(堆的删除)

我么以上面建的小堆为例如果我们删除第一个元素,按照惯例将后面的元素前移,就会形成新的堆但是新的堆不一定是我们的大堆或者小堆上面的情况纯属巧合,通过观察我们可以发现去掉第一个元素形成的左右子树依然是小堆,我们不妨将第一个元素和最后一个元素互换位置,这样最小的元素就在最后,指针前移就可以做到删除,然后第一个位置的两个子树都是小堆再从两个小堆的堆顶选出最小的交换,重复操作又可以是一个小堆了。


2.3建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果): 

2.4堆的创建

typedef int HPDatatype;
typedef struct Heap
{
	HPDatatype* a;
	int size;
	int capacity;
}HP;

还是使用动态开辟空间,来实现;

2.5堆的初始化和空间的销毁

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

和顺序表一样,将指针置空和将容量,size置零 。

//销毁空间
void HPDes(HP* php)
{
	assert(php);
	free(php->a);
	php->size = php->capacity = 0;
}

使用free库函数释放动态开辟的空间,最后将容量和size置零。

2.6堆的插入

void HPPush(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 failed");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	HPadjustUp(php->a, php->size-1);
}

和顺序表的形式差不多,进入函数判断空间是否足够,不够的话动态开辟新的空间,开辟不成功的话打印错误码并退出,成功的话插入有效数据,size++,然后使用向上调整函数调整成堆 。

向上调整函数

调整函数就是上面的堆的向上调整算法,运用父亲和孩子的下标关系调整。

void HPadjustUp(HPDatatype* a, int child)
{
	//找到父亲
	int parent = (child - 1) / 2;
	//根为0  当和根交换后child为0
	while (child > 0)
	{
		//当child小时和父亲交换 建成小堆
		//当child大时和父亲交换 建成大堆
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

交换函数

void swap(HPDatatype* x, HPDatatype* y)
{
	HPDatatype tmp = *x;
	*x = *y;
	*y = tmp;
}

取地址防止出函数创建的变量销毁,导致交换失败。

2.7堆的删除

void HPpop(HP* php)
{
	assert(php);
	assert(php->size);
	//先将头和尾交换  左右子树依然是完整的小/大堆,再从两个子堆中找出最大/小值;
	swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	HPadjustDown(php->a, php->size, 0);
}

 进入函数先判断,如果size为0是会造成越界,再将头和尾交换,size减减,最后使用堆的向下调整算法调整成小堆。

向下调整函数

void HPadjustDown(HPDatatype* a, int n, int parent)
{
	//假设左孩子最小
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child])
		{
			//假设失败 右孩子小
			++child;
		}
		else
		{
			if (a[child] < a[parent])
			{
				swap(&a[child], &a[parent]);
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}

		}
	}
}

 

在这个函数中我们使用了一个假设法我们也不知道子堆的堆顶那个数据最小,但是两者是连续的先假设一个然后进行判断 ;这里一定要注意child的取值范围(child<n),防止越界。

2.8堆的打印、取值、判空

//堆的打印
void HPPrint(HP* php)
{
	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

因为我们创建的是数组,遍历整个数组就可以。

HPDatatype HPtop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

这里要注意size要大于0,当size为0是代表空。

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

当size为0时代表空,0==0返回true。


三、完整代码

#define _CRT_SECURE_NO_WARNINGS 67
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int HPDatatype;
typedef struct Heap
{
	HPDatatype* a;
	int size;
	int capacity;
}HP;
//初始化
void HPInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}
//销毁空间
void HPDes(HP* php)
{
	assert(php);
	free(php->a);
	php->size = php->capacity = 0;
}
void swap(HPDatatype* x, HPDatatype* y)
{
	HPDatatype tmp = *x;
	*x = *y;
	*y = tmp;
}
//
void HPadjustUp(HPDatatype* a, int child)
{
	//找到父亲
	int parent = (child - 1) / 2;
	//根为0  当和根交换后child为0
	while (child > 0)
	{
		//当child小时和父亲交换 建成小堆
		//当child大时和父亲交换 建成大堆
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void HPPush(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 failed");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	HPadjustUp(php->a, php->size-1);
}
void HPPrint(HP* php)
{
	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}
void HPadjustDown(HPDatatype* a, int n, int parent)
{
	//假设左孩子最小
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child])
		{
			//假设失败 右孩子小
			++child;
		}
		else
		{
			if (a[child] < a[parent])
			{
				swap(&a[child], &a[parent]);
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}

		}
	}
}

	

void HPpop(HP* php)
{
	assert(php);
	assert(php->size);
	//先将头和尾交换  左右子树依然是完整的小/大堆,再从两个子堆中找出最大/小值;
	swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	HPadjustDown(php->a, php->size, 0);
}
HPDatatype HPtop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}
bool HPempty(HP* php)
{
	assert(php);
	
	return php->size == 0;
}
int main()
{
	HP hp;
	HPInit(&hp);
	int a[] = { 65,100,70,32,50,60 };
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HPPush(&hp, a[i]);
	}
	HPPrint(&hp);
	while (!HPempty(&hp))
	{
		printf("%d ", HPtop(&hp));
		HPpop(&hp);
	}
	HPDes(&hp);
	return 0;
}

最后的执行结果我们可以发现利用堆的删除可以实现排序,这就是我们下篇文章的内容利用堆实现排序,敬请期待!!! 

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

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

相关文章

【数据结构】图的基本概念,图的存储结构(邻接矩阵;邻接表;十字链表;邻接多重表)

欢~迎~光~临~^_^ 目录 1、图的基本概念 2、图的存储结构 2.1邻接矩阵 2.2邻接表 2.3十字链表 2.4邻接多重表 2.5图的四种存储结构的对比 1、图的基本概念 图是由一组节点&#xff08;通常称为顶点&#xff09;和一组连接这些节点的边&#xff08;通常称为边&#xff0…

注册中心的学习

一、什么是注册中心&#xff1f; 注册中心主要有三种角色&#xff1a; 1.1、服务提供者&#xff08;RPC Server&#xff09;&#xff1a; 在启动时&#xff0c;向 Registry 注册自身服务&#xff0c;并向 Registry 定期发送心跳汇报存活状态。 1.2、服务消费者&#xff08;…

Qt5开发及实例V2.0-第七章-Qt图形视图框架

Qt5开发及实例V2.0-第七章-Qt图形视图框架 第7章 Qt 5图形视图框架7.1 图形视图体系结构7.1.1 Graphics View的特点7.1.2 Graphics View的三元素7.1.3 GraphicsView的坐标系统 7.2 【实例】&#xff1a;图形视图7.2.1 飞舞的蝴蝶7.2.2 地图浏览器7.2.3 图元创建7.2.4 图元的旋转…

大数据-kafka学习笔记

Kafka Kafka 是一个分布式的基于发布/订阅模式的消息队列&#xff08;Message Queue&#xff09;&#xff0c;主要应用于大数据实时处理领域。 Kafka可以用作Flink应用程序的数据源。Flink可以轻松地从一个或多个Kafka主题中消费数据流。这意味着您可以使用Kafka来捕获和传输…

Python 图形化界面基础篇:创建顶部菜单

Python 图形化界面基础篇&#xff1a;创建顶部菜单 引言 Tkinter 库简介步骤1&#xff1a;导入 Tkinter 模块步骤2&#xff1a;创建 Tkinter 窗口步骤3&#xff1a;创建顶部菜单栏步骤4&#xff1a;处理菜单项的点击事件步骤5&#xff1a;启动 Tkinter 主事件循环 完整示例代码…

Python 如何把 String 转换为 Json 对象

在我们对 JSON 进行处理的时候&#xff0c;大概率我们会需要把字符串转换为 JSON 对象后才能进行处理。 Python 贴心的使用 json.loads(employee_string)就可以了。 首先需要做的就是导入 JSON 库。 #include json library import json 对现代程序员来说&#xff0c;JSON …

CNC 3D浮雕 Aspire 11.55 Crack

Aspire 提供了功能强大且直观的软件解决方案&#xff0c;用于在 CNC 铣床上创建和切割零件。有用于 2D 设计和计算 2D 刀具路径的工具&#xff0c;例如仿形、型腔加工和钻孔以及 2.5D 刀具路径&#xff0c;包括&#xff1a;V 形雕刻、棱镜雕刻、成型刀具路径、凹槽、 倒角刀具路…

抖音seo矩阵系统开源代码定制部署

抖音SEO底层开发逻辑主要包括以下几个方面&#xff1a; 1. 关键词优化&#xff1a;抖音SEO需要优化关键词&#xff0c;将关键词嵌入短视频标题、描述、标签等地方&#xff0c;提升抖音短视频在搜索引擎中的排名。 2. 标题优化&#xff1a;抖音短视频的标题应简明扼要&#xff…

C/C++满足条件的数的累加 2023年5月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C满足条件的数的累加 一、题目要求 1、编程实现 2、输入输出 二、解题思路 1、案例分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 C/C满足条件的数的累加 2023年5月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 现有n个整数&#x…

【前端面试题】浏览器面试题

文章目录 前言一、浏览器面试问题1.cookie sessionStorage localStorage 区别2.如何写一个会过期的localStorage&#xff0c;说说想法2.如何定时删除localstorage数据2.localStorage 能跨域吗2.memory cache 如何开启2.localstorage的限制2.浏览器输入URL发生了什么2.浏览器如何…

IIC协议详解

目录 1.IIC协议概述 2.IIC总线传输 3.IIC-51单片机应用 1.起始信号 2.终止信号 3.应答信号 4.数据发送 4.IIC-32单片机应用 用到的库函数&#xff1a; 1.IIC协议概述 IIC全称Inter-Integrated Circuit (集成电路总线)是由PHILIPS公司在80年代开发的两线式串行总线&…

进程组.会话.终端

一.进程组.会话.终端概念 1.1进程组 在Linux操作系统中&#xff0c;进程组&#xff08;Process Group&#xff09;是一组进程的集合。进程组内的每个进程都有一个相同的进程组ID&#xff08;PGID&#xff09;。进程组可以用于进行作业控制、信号传递和进程状态管理等操作。 …

大模型+检索增强(RAG、Atlas 和 REPLUG)

https://zhuanlan.zhihu.com/p/651380539 https://github.com/ninehills/blog/issues/97 1. 检索增强生成 RAG 在问答和对话的场景下&#xff0c;通常可以通过检索和生成两种方式得到一个回复。检索式回复是在外部知识库中检索出满意的回复&#xff0c;较为可靠和可控&#…

如何使用 MATLAB 数学编程软件调用 Python 脚本详细教程(每周更新中)

MATLAB 读写操作 在 MATLAB 中&#xff0c;可以使用各种函数来读取和写入文件。其中&#xff0c;filename.txt 是要读取或写入的文件名&#xff0c;r 表示读取模式&#xff0c;w 表示写入模式。fscanf 和 fprintf 函数用于读取和写入文件内容&#xff0c;%c 和 %s 是格式说明符…

Python 通过 stomp 发送消息到 ActiveMQ 的代码

只需要下面简单的几行代码&#xff0c;我们就可以把我们本地数据发送到 ActiveMQ 上面去。 def send_mq(data):hosts [(AMQHOST, AMQPORT)]conn stomp.Connection(host_and_portshosts, auto_content_lengthFalse)conn.connect(usernameAMQUSER, passcodeAMQPASS, waitTrue)…

基于Spring Boot的医院预约挂号系统设计与实现

前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb;…

功夫再高也怕菜刀。多年经验,会独立开发的机器视觉工程师,技术太强,但是找工作能力差劲

功夫再高也怕菜刀&#xff0c;专业的事情交给专业的人去做。 今年7月份中旬的时候&#xff0c;遇到一位老朋友&#xff0c;向我咨询某公司的信息&#xff0c;其实我根本不了解这家公司的情况与实力&#xff0c;向他说了&#xff0c;抱歉&#xff0c;我查下&#xff0c;等我晚上…

怎么把利用paddlepaddle导出的json文件转化为yolo或者voc文件

这两天想偷懒&#xff0c;想让模型先在数据上标一遍&#xff0c;然后我再做修正&#xff0c;主要是图个省事。由于我们的业务主要是利用paddle,模型也是基于paddle推理的&#xff0c;因此即便我对paddle有一万个吐槽但也不得不用它。但在利用paddle保存推理结果文件时&#xff…

Linux Day17 生产者消费者

一、生产者消费者问题概述 生产者 / 消费者问题&#xff0c;也被称作有限缓冲问题。两个或者更多的线程共享同一个缓冲 区&#xff0c;其中一个或多个线程作为 “ 生产者 ” 会不断地向缓冲区中添加数据&#xff0c;另一个或者多个线程作为 “ 消费者 ” 从缓冲区中取走数据。…

搭建本地MQTT服务器

环境及所用工具 win10本地环境下&#xff0c;使用docker配置一个mqttbroker&#xff0c;选择emqx docker操作&#xff1a;Docker_liangchaaaaa的博客-CSDN博客 测试使用MQTTX软件 Docker拉取镜像仓库 docker pull emqx/emqx:4.2.5 可以上官网看最新版本&#xff0c;或直接…