【数据结构初阶】树+二叉树+堆的实现

news2025/1/17 21:49:17

真正的勇士,就是在看清生活的真相后,依旧慷慨面对他所遭受的苦难与挫折。

大学究竟教会了我们什么呢?或许答案只有一个,看清自己,与自己和解,和自己坐下来谈一谈。

人生或许本就没有什么意义,但我们还是要生活,不是吗?
在这里插入图片描述

文章目录

  • 一、树
    • 1.1 树的介绍
    • 1.2 树的重要概念
    • 1.3 树的表示(左孩子右兄弟)
  • 二、二叉树
    • 2.1 二叉树的介绍
    • 2.2 二叉树的性质
  • 三、二叉树的顺序结构及实现
    • 3.1 二叉树的顺序结构
    • 3.2 堆的概念及结构
    • 3.3 堆(底层就是顺序表)的实现
      • 3.3.1 堆结构体设计+堆的初始化+堆的销毁
      • 3.3.2 堆的插入(附:向上调整算法)+堆的删除(附:向下调整算法)
      • 3.3.3 取堆顶数据+堆的大小+堆的判空
      • 3.3.4 测试接口



一、树

1.1 树的介绍

树是一种非线性的数据结构,它是一种由有限个结点组成的具有层状结构的集合,把它叫做树是因为它看起来像一颗倒挂起来的树,叶子朝下,根root朝上。

其中最上面的结点称之为根节点,而且每一棵子树之间是不能有交集的,否则就不是树状结构了,下面的Linux目录的结构就是我们的树形结构。
在这里插入图片描述

1.2 树的重要概念

1.结点的度: 一个结点含有的子树的个数称为该节点的度

2.叶结点或终端结点: 子树个数为0的结点

3.双亲结点或父节点: 如果一个结点有子结点,则这个结点称为子节点的父节点

4.孩子结点或子结点: 一个结点含有的子树的根结点就是这个结点的子结点

5.结点的层次: 从根开始定义,根为第一层,根的子节点为第二层,一次类推

6.结点的祖先: 从树的根节点开始一直到这个结点所经过的路径上所有的结点就是这个结点的祖先。

7.子孙: 以某一结点作为根结点的子树下的所有结点都是这个根结点的子孙

在这里插入图片描述

8.森林: 多棵互不相交的树组成的集合称之为森林。

例如Linux中的ls指令其实就是将当前所处根结点的所有子节点全部列出来
在这里插入图片描述

1.3 树的表示(左孩子右兄弟)

树的结构在表示时,不仅要存储值,还要链接其每个结点之间的关系,但我们不知道每个结点的度是多少,但不要担心,问题总会有解决的办法的,下面就来说一下左孩子右兄弟表示法的精妙所在。

我们不管那么多,每个结点只能有一个孩子,剩下的子节点就跟父节点没关系了,全靠他的第一个孩子来连接,直到一个孩子的brother指到空指针,这一层就完事了。
后面的也依次类推。
在这里插入图片描述

二、二叉树

2.1 二叉树的介绍

一棵二叉树是结点的一个有限集合,该集合可以为空或由两棵子树构成,子树分别称为左子树和右子树,并且二叉树中结点的度是不可以超过2的,也就是任意一个结点的子节点个数必须小于等于2。

在这里插入图片描述
任意的一棵二叉树都是由下面的几种情况复合而成的。

只要一棵树的最大度小于等于2,我们就可以把这棵树称之为二叉树,空树,只有根节点等都可以称之为二叉树。
在这里插入图片描述

当然二叉树中也有一些特殊的树,分别是完全二叉树和满二叉树。

满二叉树就是除叶结点之外的结点的度都是2.

完全二叉树其实就是特殊的满二叉树,只要满足最后一行是连续的叶结点,中间不可以空开,除最后一层外其他层满足满二叉树的特点,这样的树我们称之为完全二叉树。但完全二叉树最后一层最少都得有一个结点。
在这里插入图片描述
在这里插入图片描述

2.2 二叉树的性质

1.满二叉树的结点个数:2^h-1(h代表树的层数)

2.完全二叉树的结点个数:最多个数:2^h-1 最少个数:2^(h-1)

3.对于任何一棵二叉树,假设叶结点个数为n0,度为2的结点个数为n2,则有结论n0=n2+1,也就是叶结点个数永远比度为2结点个数多1

4.完全二叉树度为1的结点个数要么是1要么是0.

5.父子结点关系:parent=(child-1) / 2 leftcihld=parent2+1 rightchild=parent2+2

6.满二叉树高度为h,节点数是n,h=log(n+1),我们后面会经常用高度来判断算法的时间复杂度。

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

3.1 二叉树的顺序结构

现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

3.2 堆的概念及结构

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

其实堆就是完全二叉树

其实为什么要存在逻辑结构这种东西呢?

其实吧实际在内存当中的存储结构就两种,一种是数组一种是链表,但由于我们生活中的存储模式可不简简单单只有这两种结构,所以我们将这两种基本的存储结构抽象成我们想要的结构,用算法来将其进行实现。

所以其实逻辑结构存在的最大意义就是方便我们理解,如果没有逻辑结构,我们对着生涩的数组裸写代码,是非常容易写错的,所以我们需要逻辑,需要它将抽象的东西变得生动化,可视化,形象化。
在这里插入图片描述

3.3 堆(底层就是顺序表)的实现

3.3.1 堆结构体设计+堆的初始化+堆的销毁

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

其实这里堆结构体就是一个顺序表,我们定义的结构体和顺序表也没什么区别

void HeapInit(HP*php)
//这里初始化开不开辟空间都可以,我们可以选择这里开空间,后面用realloc修改空间大小,也可以这里不开空间,直接后面realloc修改空间
//因为当realloc接收的指针为NULL时,他的作用和malloc是一样的,所以这里开不开辟空间都是可以的
{
	assert(php);
	php->array = NULL;
	php->size = php->capacity = 0;
}

初始化这里也没什么新奇的东西和之前讲解的链表顺序表等,没什么区别

void HeapDestroy(HP* php)
{
	assert(php);
	free(php->array);
	php->array = NULL;
	php->size = php->capacity = 0;
}

释放掉动态开辟的数组空间,然后将指针置为空,其他变量置为0即可。

3.3.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->array, sizeof(HPDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail\n");
			exit(-1);
		}
		php->array = tmp;
		php->capacity = newCapacity;
	}
	php->array[php->size] = x;
	php->size++;

	AdjustUp(php->array, php->size - 1);
}

在插入数据之前我们还是需要先检查空间是否满,如果满了,我们直接扩容就好了。最后我们调用向下调整的接口进行实现堆的调整

void AdjustUp(HPDataType* array, int child)//传过来你插入的孩子的下标
{
	int parent = (child - 1) / 2;
	while (child > 0)//child会被赋值到祖先的位置,这时parent已经越界了,我们的向上调整也就结束了,所以child>0
	{
		if (array[child] > array[parent])
		//如果想要调整为小堆的话,我们只要调整这里的比较符号就可以了,保证树中所有的父亲都小于等于孩子
		{
			Swap(&array[child], &array[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

我们每一次插入新的数据,我们都让这个新的数据和他的父节点进行比较,我们这里默认建的是大堆,所以只要每次插入的孩子结点大于父节点时,我们就将这个子节点向上调整到parent下标的位置,parent继续向上调整到新的parent的位置,等到child的位置到达祖先的位置也就是根节点root的位置时,我们向上调整的循环也就结束了。

多说一句,大家可能有点蒙,为什么child-1除以2就是parent的结点了,不用分情况讨论吗?child的下标既有可能是奇数,也有可能是偶数啊,你不用区分一下吗?

其实是不用区分的,因为/求的是商,我们的偶数和奇数下标经过上面的运算过后,其实结果是一样的。所以在利用孩子找父节点时,只要减1再除以2就OK了。

void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);//括号内表达式若判断为假,直接报错,粗暴的方式来解决

	Swap(&php->array[0], &php->array[php->size - 1]);
	php->size--;

	//AdjustDownMe(php->array, php->size -1);
	AdjustDownTeach(php->array, php->size, 0);
}

我们再删除堆顶数据时,利用了一个小技巧就是,我们将堆顶数据用堆中最小辈分的数据覆盖掉,然后依次向下调整数据的位置,以便保证堆还是大堆,为什么这样做是可行的呢?这样做可以保持根的某一个子树结构不变,我们只要调整另一子树的数据就可以了,而且这个调整还是递归式的,我们的算法时间复杂度是logN。
要知道logN可是非常快的,所以这个算法是非常牛逼的。

void AdjustDownTeach(HPDataType* array, int n, int parent)
{
	int child = parent * 2 + 1;//上来我们就先假设最大的孩子是左孩子

	while (child<n)//我们想的是循环结束的条件,写的是循环继续的条件
	{
		//保证有右孩子的同时,看看我们的假设是否正确,错误就调整
		if (child + 1 < n && array[child + 1] > array[child])
		//如果假设错误,我们将孩子改为右孩子,并且你也有可能没有右孩子,没有右孩子,默认左孩子就是最大的
		//这里其实不用担心没有孩子的问题,因为如果parent没有孩子,child被赋值过后肯定大于n了直接跳出循环了就。
		{
			++child;//将下标自增,左孩子就变为右孩子
		}
		if (array[parent] < array[child])
		{
			Swap(&array[parent], &array[child]);
			parent = child;
			child = parent * 2 + 1;//这里再重新假设左孩子是大的,下一次循环就是先看看我们的假设是否正确,若不正确就进行调整。
		}
		else
		{
			break;
		}
	}
}

这里的代码延续了我们之前再做栈和队列的一些面试题时所用到的技巧了,就是假设法,如果假设错了,我们直接利用if语句进行调整就OK了,向下调整也简单,只要我们的parent小于他的子节点,那它就得往下走,不能占顶部的位置,这算法和前面的向上调整也是比较相似的。

3.3.3 取堆顶数据+堆的大小+堆的判空

这几个接口真的是简单的要死,我不想说了,写了这么多数据结构了,今天见的属实是简单的要死。

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->array[0];
}
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;

}

3.3.4 测试接口

void TestHeap1()
{
	int array[] = { 27,15,19,18,28,34,65,49,25,37 };
	HP heap;//建一个堆
	HeapInit(&heap);
	for (int i = 0; i < sizeof(array) / sizeof(int); i++)
	{
		HeapPush(&heap, array[i]);//我们的插入模块儿里面,每一次插入之后,都会再重新向上调整一次,以此来保证我们的堆是大堆。
	}
	HeapPrint(&heap);
	HeapPop(&heap);
	HeapPrint(&heap);
	//topK快
	int k = 5;
	while (k--)
	{
		printf("%d ", HeapTop(&heap));//求出topk个数据,大堆中最大的前5个数据
		HeapPop(&heap);
	}
		 
	HeapDestroy(&heap);

}
void TestHeap2()
{
	int array[] = { 27,15,19,18,28,34,65,49,25,37 };
	HP heap;//建一个堆
	HeapInit(&heap);
	for (int i = 0; i < sizeof(array) / sizeof(int); i++)
	{
		HeapPush(&heap, array[i]);//我们的插入模块儿里面,每一次插入之后,都会再重新向上调整一次,以此来保证我们的堆是大堆。
	}
	HeapPrint(&heap);
	
	
	//topK快
	int k = 5;
	while (!HeapEmpty(&heap))//利用堆顶数据,我们可以打印出来这个数组的降序内容
	{
		printf("%d ", HeapTop(&heap));//求出topk个数据,大堆中最大的前5个数据
		HeapPop(&heap);
	}

	HeapDestroy(&heap);
}
int main()
{
	//TestHeap1();
	TestHeap2();//测试过后我们的数组就被我们排成降序的了

	return 0;
}

这里说几句吧,我们可以利用HeapTop接口和HeapPop接口来组合解决topK问题,然后再测试接口里面我们是单独先创建了一个数组,然后利用堆的插入接口,讲这个数组的内容重新插入到我们动态开辟的数组array,这样就实现我们大堆的搭建了。

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

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

相关文章

MySQL的缓冲池(buffer pool)及 LRU算法

1.什么是缓冲池&#xff08;buffer pool&#xff09; buffer pool 是数据库的一个内存组件&#xff0c;里面缓存了磁盘上的真实数据&#xff0c;Java系统对数据库的增删改操作&#xff0c;主要是这个内存数据结构中的缓存数据执行的。 控制块 存的是 数据页所属的表 空间号&a…

MATLAB break语句

MATLAB中 break 语句用于终止 for 或 while 循环的执行&#xff0c;当在循环体内执行到该语句的时候&#xff0c;程序将会跳出循环&#xff0c;继续执行循环语句的下一语句。 注意&#xff1a;在嵌套循环中&#xff0c;break 退出只能在循环发生&#xff0c;后通过的声明控制循…

git关于创建/删除分支常用命令

主要用来介绍git中如何操作分支的命令&#xff1a; 1.git查看所有的分支: git branch -a 2.创建本地分支&#xff1a; git checkout -b <name> 3.有了本地分支之后推送到远程分支; git push origin <name> 4.切换分支&#xff1a; git checkout <name> 5.删除…

黑*头条_第5章_文章发布粉丝管理成形记

黑*头条_第5章_文章发布&粉丝管理成形记 文章目录黑*头条_第5章_文章发布&粉丝管理成形记文章发布&粉丝管理1 需求分析1.1 功能需求1.2 前端需求1.2.1 图文数据需求1.2.2 素材管理需求1.2.3 发布文章需求1.2.4 内容列表需求1.2.5 粉丝概况需求2 定义2.1 后端定义2.…

MacBook磁盘内存空间清理软件CleanMyMac2023

Mac电脑用的时间久了&#xff0c;Mac用户尤其是MacBook用户会经常收到“磁盘几乎已满”的提示&#xff0c;如何解决这个问题&#xff0c;当我们使用苹果MAC一段时间后&#xff0c;就会有大量的垃圾文件占用磁盘空间&#xff0c;例如系统缓存文件、应用程序缓存文件、备份和重复…

2022/11/18[指针] 关于指针的初步理解

先看一段利用指针交换a和b值得代码&#xff1a; #include<stdio.h> void swap(int* p1, int* p2); int main() {int a, b;int* pa, * pb;printf("enter:");scanf_s("%d%d", &a, &b);pa &a;pb &b;printf("%d,%d\n", pa,…

面试:线程池核心线程如何保持一直运行的

对于Java中 Thread 对象&#xff0c;同一个线程对象调用 start 方法后&#xff0c;会在执行完run 后走向终止&#xff08;TERMINATED&#xff09;状态&#xff0c;也就是说一个线程对象是不可以通过多次调用 start 方法重复执行 run 方法内容的。Java的线程是不允许启动两次的&…

frp内网穿透服务

参考博客&#xff1a; https://www.jianshu.com/p/19ea81efffc4 https://blog.csdn.net/yj222333/article/details/124752420 依赖于&#xff1a;Github开源软件FRP 下载地址&#xff1a;https://github.com/fatedier/frp/releases frp 主要由 客户端(frpc) 和 服务端(frps…

基于JAVA景区售票系统设计与实现 开题报告

本科生毕业论文 基于java框架springboot旅游景区景点购票系统 开题报告 学 院&#xff1a; 专 业&#xff1a; 计算机科学与技术 年 级&#xff1a; 学生姓名&#xff1a; 指导教师…

三维模型文件以及obj、ply格式文件生成pcd点云文件

方法一、三维模型文件生成obj文件 要想生成点云文件&#xff0c;要先将三维模型文件保存为obj文件格式&#xff0c;步骤如下&#xff1a; 通过SolidWorks将模型保存为stl文件格式打开SolidWorks的插件选择&#xff0c;在ScanTo3D前面打勾 在solidworks中以网格文件的形式打开…

通信算法之九十六:电力通信系统-HRF多载波通信系统-物理层收发信道开发

目录1.HRF通信系统-物理层收发信道开发1.1 SIG发射机算法功能模块1.2 SIG接收机算法功能模块1.3 PHR发射机算法功能模块1.4 PHR接收机算法功能模块1.5 PSDU发射接收机处理流程1.6前导LTF/STF序列发射接收处理流程1.7PPDU帧&#xff08;前导SIGPHRPSDU&#xff09;发射接收处理流…

阿里云服务器(Ubuntu)配置nextcloud个人网盘

tags: Ubuntu Server Linux 写在前面 最近迷恋上了云服务器的配置, 感觉云服务器能做的事情太多了, 不管是docker还是直接部署, 都是相当方便快捷的, 下面来看看在阿里云服务器配置nextcloud网盘的基本配置方法, 这里参考了一篇文章1, 写的相当详细了, 我这里只是做一些补充.…

【5G MAC】Msg1 TX开环功控介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

使用python编写mqtt客户端向EMQX服务器发送数据

摘要&#xff1a;本文介绍如何用python编写一个mqtt客户端向EMQX服务器发送数据&#xff0c;实现一个简易的本地物联网服务器。 上一篇文章讲到使用mqtt.fx软件来发布消息。 (1条消息) 使用mqtt.fx向EMQX服务器发送消息_TMS320VC5257H的博客-CSDN博客https://blog.csdn.net/yo…

【树莓派不吃灰】命令篇⑦ 学习awk命令

目录awk 命令1. 基本语法2. 工作原理3. 基础实例操作3.1 把用户名和Shell打印出来3.2 找到以ssh关键字开头的所有行&#xff0c;并输出用户名和shell&#xff0c;中间以“-”分割3.3 给用户 ID加上一个常量3.4 输出第一个字段为sshd所在的行4. BEGIN END 操作4.1 基于3.2&#…

智慧仓储解决方案-最新全套文件

智慧仓储解决方案-最新全套文件一、建设背景二、思路架构三、建设方案四、获取 - 智慧仓储全套最新解决方案合集一、建设背景 仓储行业目前存在的问题 仓管员需要手动录单&#xff0c;工作量大&#xff0c;易出错&#xff0c;无法保证数据的准确率和及时性。 批次属性复杂、保…

计算机里的刻度:时钟和步进器

计算机的底层逻辑很简单&#xff0c;它们被定义为完成一些简单的事情。计算机是一个复杂系统&#xff0c;复杂的是如何规划好处理这些简单的事情的时间和步骤。本节就可以了解到计算机的时间刻度和步进器的构成&#xff0c;帮助我们进一步理解计算机的底层工作原理。 时钟是什…

redis三(3-1)

分布式缓存 基于Redis集群解决单机Redis存在的问题 单机的Redis存在四大问题 一、redis持久化 - RDB持久化 - AOF持久化1.1RDB持久化 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09;&#xff0c;也被叫做Redis数据快照。简单来说就是把内存中的…

java项目-第150期ssm网络视频播放器-java毕业设计_计算机毕业设计

java项目-第150期ssm网络视频播放器-java毕业设计_计算机毕业设计 【源码请到资源专栏下载】 今天分享的项目是《ssm网络视频播放器》 该项目分为2个角色&#xff0c;管理员、用户。 用户可以浏览前台查看视频信息、系统公告、论坛信息。 并且可以进入到个人中心查看视频信息、…

计算机毕业设计java基于javaweb+ssm+vue婚纱摄影网站

本站不同于其它摄影网站&#xff0c;本网站不但可以展示本店的摄影作品&#xff0c;更可以列出众多摄影套餐供用户选择预约&#xff0c;用户看中哪款套餐了&#xff0c;可以预约时间进行拍摄&#xff0c;即增加了店内本身的业务量&#xff0c;也方便了客户直接在线订套餐。 对于…