一起学数据结构(8)——二叉树中堆的代码实现

news2024/11/25 4:20:28

在上篇文章中提到,提到了二叉树中一种特殊的结构——完全二叉树。对于完全二叉树,在存储时,适合使用顺序存储。对于非完全二叉树,适合用链式存储。本文将给出完全二叉树的顺序结构以及相关的代码实现:

1. 二叉树的结构建立、销毁及初始化:

给出下图中二叉树的逻辑结构及存储结构:

二叉树的逻辑结构只是根据想象创建出来的结构,但是在计算机存储时,并不存在逻辑结构这种形式。而是下方的存储结构。所以,以下所有操作的对象都是存储结构中连续的数组。例如需要在上述二叉树中插入一个新的结点,并且此结点存储了元素90。逻辑结构表示如下:

对于存储结构而言,以上操作只是在已有数组的末尾插入了一个值。存储结构表示如下:
 

所以,对于二叉树的结构建立,可以仿照之前的顺序表的结构,代码如下:

typedef int HPDataType;

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

其中,指针a用来后续进行连续空间的开辟,size表示数组的长度,capacity用来记录数据的容量,一旦size = capacity就需要进行扩容。

对于二叉树的销毁与初始化,与前面的文章中方式相同,只给出代码,不做多余解释:

二叉树的初始化如下:

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

二叉树的销毁如下:

void HeapDestory(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

2. HeapPush向二叉树中插入结点:

2.1  HeapPush 向二叉树中插入结点:


前面提到,二叉树的逻辑结构在计算机中并不存在,并且下面的代码操作都是针对二叉树的逻辑结构的。所以,向二叉树中插入元素这一操作,实际就是在数组的末尾插入一个元素。但是在插入元素之前,需要判定数组的空间是否有剩余,即满足:size < capacity。一旦size = capacity,则需要进行一次扩容。对于扩容这一步骤在之前的文章中多次使用,这里依旧直接给出代码:
 

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

	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* newnode = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (newnode == NULL)
		{
			perror("realloc");
			exit(-1);
		}
		php->a = newnode;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;

}

2.2 针对二叉树为小堆情况下,新插入元素的位置调整:

2.2.1 结点调节的逻辑分析:

例如对于上面给出的完全二叉树,可以看出,这个二叉树满足父结点数值<子结点数值,为小堆

 对于新结点中插入的数值,需要区分下列情况进行判定:

1. 插入的值大于父结点中的数值56,即:


此时插入的结点的数值为90,依旧满足子结点数值>父结点数值,因此不需要对结点的关系进行更改。

2. 插入的值小于父结点中的数值56

此时插入的结点中存储的值为32。不满足小堆中父结点存储的值<子结点的关系。所以,需要对二叉树的关系进行更改,即对此时的子结点、父结点交换位置,效果如下:

但是,如果插入的结点中存储的值,小于二叉树中任何结点的值,例如新插入结点中存储的值为1。此时调整后的二叉树的效果如下:

对于调整父结点、子结点的关系,在上一篇文章中,根据完全二叉树的性质给出了下列的等式:

对于一个父结点,假设其在数组中的下标为parent,则其左子结点的下标:

                                                 child1 = parent*2+1

其右子结点的下标为:

                                                child2 = parent*2+2

对于任意子结点child(左、右子结点都适用),其父结点的下标可以用下面的等式表示,及:

                                                 parnet = (child-1)/2

定义函数Adjustup用于完成对子结点、父结点关系的调整:
前面提到,对于插入子结点,最极端的情况就是子结点中存储的值小于二叉树中任意结点的值,导致子结点需要一直与父结点进行替换,直至到根结点,即数组中下标为0的结点的位置。因此需要利用循环来控制结点的调整次数,通过上面的分析可以得出,循环的条件就是子结点对应的下标> 0,即:child> 0

2.2.2 Adjustup 结点调节的代码分析:
 

对于上面的给出的向二叉树中插入元素的操作中,需要注意,在插入元素后,会对表示数组长度的size+1,所以,每次进行上述插入操作后:size-数组的实际长度=1

上面说到,会创建函数Adjustup函数来调整结点的位置,整个操作中,需要知道子结点、父结点的下标,因为父结点的下标可以通过上面给出的等式进行计算。所以,在对函数传递参数时,只需要传递数组的指针和插入的子结点的下标。因为插入的结点为数组的最后一位,所以,直接传递数组的实际长度,即size-1即可。

对于交换子结点和父节点这一功能在后面需要多次适用,所以,为了方便,直接再创建一个交换函数Swap即可。交换函数对应的代码如下:
 

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType* tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

调整函数代码如下:

void Adjustup(HPDataType* a, int child)
{
	assert(a);

	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;
		}
	}
}

对于上面已经给出的插入函数,在最后加上已经定义好的调整函数即可。

为了验证前面的功能是否正常,再定义函数HeapPrint用于打印二叉树的结点。打印函数嗲澳门如下:

void HeapPrint(HP* php)
{
	assert(php);
	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
}

通过下面的代码,对插入并且调整结点的函数进行验证:

#include"TreeNode.h"


void Test1()
{
	int a[] = { 9,8,7,5,4,3,2 };
	HP hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HeapPush(&hp, a[i]);
	}
	HeapPrint(&hp);


	HeapDestory(&hp);

}
int main()
{
	Test1();
	return 0;
}

运行结果如下:

运行结果显示的是二叉树的存储结构,将上述存储结构转为逻辑结构,即:

     满足小堆的定义。         

3.删除二叉树中的结点:                     

3.1 删除二叉树中结点逻辑分析:           

给定下列的二叉树:

对于二叉树的删除结点而言,删除尾部结点的意义并不大,所以,一般的删除结点都是指删除二叉树的根结点。

前面提到,对于二叉树的操作,是作用在二叉树的存储结构上,上图二叉树的存储结构如下:

 对于一个连续的数组,删除头结点的方式可以是让后面的元素依次向前覆盖。但是对于二叉树而言,向前覆盖来删除头结点的方式可能会造成二叉树的结构错误,例如,利用覆盖的方法删除了头结点的二叉树的逻辑结构如下:

调整过后的二叉树,将不再满足上方的小堆关系,即子结点存储的数据>父结点存储的数据。并且,再顺序表的文章中就提到,对于依次覆盖这种方法,时间复杂度为O(N),效率并不高。

为了解决上面的问题,给定下方的方法来完成对于根结点的删除:

首先,将二叉树的尾结点和根结点替换,效果如下:

对于二叉树的存储结构,可以随机访问数组的任意下标所对应的位置,并且尾插和尾删的效率为O(1),所以,直接将尾结点删除,即:

通过上述方法删除了二叉树的原根结点之后,此时二叉树的子树并没有受到影响,依旧是小堆,删除了二叉树的原根结点后,需要对二叉树中的结点的位置进行调整。

在调整位置时,需要先比较结点的子结点所存储的数据的大小,例如根节点的子结点分别存储了数据5,3,由于根节点存储的数据4大于右子结点所存储的数据3,因此两个结点交换位置。

需要注意,此处的调整并不同于上方给出的调整函数Adjustup。上方的函数调整的对象是针对于新插入的尾结点。调整的方向是总体向上的,本处的调整是针对于置换完后的新的根结点,总体的方向是自上而下的,因此,此处再构建另一个调整函数Adjustdown

3.2 代码分析——Adjustdown 删除二叉树中结点:

对于调整函数Adjustdown,首先判断非叶子结点的子结点的大小关系,并找到较小的一个。可以先假设一个结点为小结点(此处默认左子结点为小结点),对于左子结点的下标,可以通过上面的等式

                                                        child = parent*2+1

得出,对于右子结点,可以表示为 child+1,再对两个结点中数据的大小进行判定,如果a[child+1]<a[child],则表示右子结点为较小的结点,让child+1,反之不变。此处需要注意一种特殊情况,及:

此时,存储数据3的结点只有左结点,没有右结点,child+1会引起数组的越界访问,所以,在进行结点大小判定之前,需要判定child+1<size

找到左、右子结点中较小的一个后,对父结点 、子结点的大小进行判定,如果子结点中存储的数据小于<父结点中的数据,则交换两个结点的位置。

对于交换这一过程,最好的情况下是交换一次,最坏的情况下是交换到叶子结点。所以,判断循环是否进行的条件便是判断child的大小是否<size。对应代码如下:

void Adjustdown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while ( child < size)
	{
		if (child+1 < size && a[child + 1] < a[child])
		{
			child++;
		}
		if (a[parent] > a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

3.3 代码分析——HeapPop 删除二叉树中的结点:

删除结点一共需要下列步骤:

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. 返回根结点所存储的数据:

原理较为简单,只给出代码,不做解释:

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

	return php->a[0];
}

5. 探空:

原理较为简单,只给出代码,不做解释:

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

	return php->size == 0;
}

6. 代码总览:

头文件如下:

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int HPDataType;

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


void HeapInit(HP* php);

void HeapDestory(HP* php);

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

void Adjustup(HPDataType* a, int child);

void Swap(HPDataType* p1, HPDataType* p2);

void HeapPrint(HP* php);

void Adjustdown(HPDataType* a, int size, int parent);

void HeapPop(HP* php);

HPDataType HeapTop(HP* php);

bool HeapEmpty(HP* php);

TreeNode.c文件如下:

#include"TreeNode.h"

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

void HeapDestory(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}
void HeapPrint(HP* php)
{
	assert(php);
	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
}

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType* tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//新插入子结点向上升,
void Adjustup(HPDataType* a, int child)
{
	assert(a);

	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;
		}
	}
}

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

	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* newnode = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (newnode == NULL)
		{
			perror("realloc");
			exit(-1);
		}
		php->a = newnode;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;

	Adjustup(php->a, php->size-1);
}
//健小堆
void Adjustdown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while ( child < size)
	{
		//找出较小的结点
		if (child+1 < size && a[child + 1] > a[child])
		{
			child++;
		}
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}


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);

}

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

	return php->a[0];
}

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

	return php->size == 0;
}

Test.c文件:

int main()
{
	Test1();
	int a[] = { 70,65,100,32,50,60 };
	HPqort(a, sizeof(a) / sizeof(int));
	printf("\n");
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

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

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

相关文章

Categraf v0.3.22部署

wget https://github.com/flashcatcloud/categraf/releases/download/v0.3.22/categraf-v0.3.22-linux-amd64.tar.gz下载安装包。 sudo mkdir /opt/categraf创建一个目录。 tar zxf categraf-v0.3.22-linux-amd64.tar.gz -C /opt/categraf进行解压。 /opt/categraf/categ…

ORA-27102: out of memory

正在外面办事呢&#xff0c;项目经理打电话并截图说明&#xff0c;物理服务器增加内存后&#xff0c;他调整sgapga后&#xff0c;重启无法启动了&#xff0c;报错ORA-27102: out of memory。 SYSorcl> startup; ORA-27102: out of memory Linux-x86_64 Error: 28: No space…

9领域事件

本系列包含以下文章&#xff1a; DDD入门DDD概念大白话战略设计代码工程结构请求处理流程聚合根与资源库实体与值对象应用服务与领域服务领域事件&#xff08;本文&#xff09;CQRS 案例项目介绍 # 既然DDD是“领域”驱动&#xff0c;那么我们便不能抛开业务而只讲技术&…

深度学习综述:Computation-efficient Deep Learning for Computer Vision: A Survey

论文作者&#xff1a;Yulin Wang,Yizeng Han,Chaofei Wang,Shiji Song,Qi Tian,Gao Huang 作者单位&#xff1a;Tsinghua University; Huawei Inc. 论文链接&#xff1a;http://arxiv.org/abs/2308.13998v1 内容简介&#xff1a; 在过去的十年中&#xff0c;深度学习模型取…

原生HTML实现marquee向上滚动效果

实现原理&#xff1a;借助CSS3中animation动画以及原生JS克隆API <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /…

【MySQL集群二】使用MyCat和ProxySql代理MySQL集群

中间件代理MySQL MyCat安装MyCat介绍&#xff1a;步骤1&#xff1a;安装Java环境步骤2&#xff1a;下载并解压Mycat步骤3&#xff1a;配置Mycat步骤4&#xff1a;启动Mycat ProxySql安装ProxySql介绍&#xff1a;步骤1&#xff1a;更新系统步骤2&#xff1a;安装ProxySQL步骤3&…

数学笔记:傅里叶变化

1 介绍 简而言之&#xff0c;傅里叶变换把一个输入信号分解成一堆正弦波的叠加 比如&#xff0c;以下是一个波&#xff1a; 这个波可以分解为两个正弦波的叠加。 也就是说&#xff0c;当我们将两个正弦波相加时&#xff0c;就会得到原来的波 哪怕是一个方波 也可以分解成一组…

【块状链表C++】文本编辑器(指针中 引用 的使用)

》》》算法竞赛 /*** file * author jUicE_g2R(qq:3406291309)————彬(bin-必应)* 一个某双流一大学通信与信息专业大二在读 * * brief 一直在竞赛算法学习的路上* * copyright 2023.9* COPYRIGHT 原创技术笔记&#xff1a;转载…

稀疏奖励问题解决方案总览

方案简介 HER (Hindsight Experience Replay) - 2017年 思想 HER&#xff08;Hindsight Experience Replay&#xff09;是一种特别设计用于解决稀疏奖励问题的强化学习算法。它主要用于那些具有高度稀疏奖励和延迟奖励的任务&#xff0c;特别是在连续动作空间中&#xff0c;如机…

IDEA设置注释快捷键进行 注释对齐

给大家推荐一个嘎嘎好用的功能~ 相信大家在使用IDE写代码的时候&#xff0c;经常用到 Ctrl / 来注释代码吧&#xff0c;但是默认的是将注释在行首对齐&#xff0c;看着很让人不舒服。但是下面的操作会将注释会和当前代码对齐&#xff0c;还会自动保留一个空格&#xff0c;真的…

【用unity实现100个游戏之13】复刻类泰瑞利亚生存建造游戏——包括建造系统和库存系统

文章目录 前言素材人物瓦片其他 一、建造系统1. 定义物品类2. 绘制地图3. 实现瓦片选中效果4. 限制瓦片选择5. 放置物品功能6. 清除物品7. 生成和拾取物品功能 二、库存系统1. 简单绘制UI2. 零代码控制背包的开启关闭3. 实现物品的拖拽拖拽功能拖拽恢复问题 4. 拖拽放置物品5. …

【C语言精髓 之 指针】指针*、取地址、解引用*、引用

/*** file * author jUicE_g2R(qq:3406291309)————彬(bin-必应)* 一个某双流一大学通信与信息专业大二在读 * copyright 2023.9* COPYRIGHT 原创技术笔记&#xff1a;转载需获得博主本人同意&#xff0c;且需标明转载源* language …

人工智能驱动的自然语言处理:解锁文本数据的价值

文章目录 什么是自然语言处理&#xff1f;NLP的应用领域1. 情感分析2. 机器翻译3. 智能助手4. 医疗保健5. 舆情分析 使用Python进行NLP避免NLP中的陷阱结论 &#x1f389;欢迎来到AIGC人工智能专栏~人工智能驱动的自然语言处理&#xff1a;解锁文本数据的价值 ☆* o(≧▽≦)o *…

flutter web 优化和flutter_admin_template

文章目录 Flutter Admin TemplateLive demo: https://githubityu.github.io/live_flutter_adminWeb 优化 Setup登录注册英文 亮色主题 中文 暗黑主题管理员登录权限 根据权限动态添加路由 第三方依赖License最后参考学习 Flutter Admin Template Responsive web with light/da…

C++ 学习系列 -- std::vector (未完待续)

一 std::vector 是什么&#xff1f; vector 是c 中一种序列式容器&#xff0c;与前面说的 array 类似&#xff0c;其内存分配是连续的&#xff0c;但是与 array 不同的地方在于&#xff0c;vector 在运行时是可以动态扩容的&#xff0c;此外 vector 提供了许多方便的操作&…

世界前沿技术发展报告2023《世界信息技术发展报告》(四)电子信息技术

&#xff08;四&#xff09;电子信息技术 1. 概述2. 微电子技术2.1 精细制程芯片2.1.1 中国台积电发布2纳米制程工艺细节2.1.2 美国英特尔公司称2030年芯片晶体管密度将达到目前的10倍2.1.3 韩国三星电子率先实现3纳米制程芯片量产2.1.4 日本丰田、索尼等8家公司合资成立高端芯…

【李沐深度学习笔记】矩阵计算(1)

课程地址和说明 线性代数实现p4 本系列文章是我学习李沐老师深度学习系列课程的学习笔记&#xff0c;可能会对李沐老师上课没讲到的进行补充。 本节是第一篇 矩阵计算 标量导数 导数刻画的是函数在某点的瞬时变化率 这东西都是考研学过的&#xff0c;快速略过&#xff0c;如…

网站接入公网并配置域名访问【详细教程】

网站接入公网并配置域名访问【详细教程】 安装Nginx上传网页文件配置Nginx腾讯云配置域名映射接入公网备案流程 本教程将以腾讯云服务器和腾讯云域名为例&#xff0c;介绍如何快速将网站接入公网并配置域名访问。我们将使用xshell工具进行操作&#xff0c;并涵盖安装nginx、上传…

Unity之VR如何实现跟随视角的UI

前言 我们在制作VR项目的时候,大部分时候,是把UI固定到一个位置,比如桌子或者空中,这么做固然稳定,但是当我们有以下需求的时候,固定位置的UI可能会不适用: 1.场景较小,操作物体占用了很大体积,没有固定的可以清晰显示完整UI的位置。 2.需要频繁的前后左右,更换姿势…

Unity3D 使用LineRenderer自由画线

原理 一个LineRenderer是一次画线&#xff0c;需要使用对象池一帧记录一个鼠标位置 代码 这是线绘制器的代码&#xff0c;依赖于笔者写过的一个简易对象池 传送门&#xff1a;>>对象池 using EasyAVG; using System; using System.Collections.Generic; using UnityEn…