【数据结构】二叉树的模拟实现

news2024/12/23 1:57:10

前言:前面我们学习了堆的模拟实现,今天我们来进一步学习二叉树,当然了内容肯定是越来越难的,各位我们一起努力!

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:数据结构 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


什么是树

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。光看文字可能大家理解不了,我们看下图。在这里插入图片描述


树的相关概念

  1. 节点的度:一个节点含有的子树的个数称为该节点的度。 如上图:A的为3。
  2. 叶节点或终端节点:度为0的节点称为叶节点; 如上图:F、G、H、I…等节点为叶节点。
  3. 非终端节点或分支节点:度不为0的节点; 如上图:B、C、D…等节点为分支节点.
  4. 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。 如上图:A是B的父节点。
  5. 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点。 如上图:B是A的孩子节点。
  6. 兄弟节点:具有相同父节点的节点互称为兄弟节点。 如上图:B、C是兄弟节点。
  7. 树的度:一棵树中,最大的节点的度称为树的度。 如上图:树的度为5。
  8. 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。
  9. 树的高度或深度:树中节点的最大层次。如上图:树的高度为4。
  10. 堂兄弟节点:双亲在同一层的节点互为堂兄弟。如上图:G、G互为兄弟节点 。
  11. 节点的祖先:从根到该节点所经分支上的所有节点。如上图:A是所有节点的祖先。
  12. 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙。
  13. 森林:由m(m>0)棵互不相交的树的集合称为森林。

什么是二叉树

二叉树是一种常见的树型数据结构,由若干个节点组成,每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树有以下特点(如图所示):

  1. 每个节点最多有两个子节点,且子节点的位置是固定的。
  2. 左子节点在父节点的左边,右子节点在父节点的右边。
  3. 二叉树的子树也是二叉树。
  4. 二叉树的每个节点都有一个值,可以是任意类型的数据。
    在这里插入图片描述

讲完了二叉树的基本性质,我们开始模拟实现二叉树吧!

二叉树的基本功能

  1. 前序遍历–根、左、右 – 深度优先遍历
  2. 计算树节点。
  3. 中序遍历–左、根、右
  4. 计算叶子节点
  5. 树的高度
  6. 求第k层结点的个数
  7. 二叉树查找值为x的结点
  8. 通过前序遍历的数组构建二叉树。
  9. 销毁二叉树
  10. 层序遍历 – 广度优先遍历
  11. 判断二叉树是否是完全二叉树

二叉树的初始化(手搓二叉树)

思路导读:由于通过数组创建二叉树比较难,我们先放在博客的后面在去讲解,但是我们又需要二叉树怎么办,那就手搓一个。
代码演示:

typedef int BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;

}TreeNode;

TreeNode* BuyNode(int x)
{
	TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
	assert(node);
	node->data = x;
	node->left = NULL;
	node->right = NULL;
	return node;
}


TreeNode* CreateTree()//创建二叉树
{
	TreeNode* node1 = BuyNode(1);
	TreeNode* node2 = BuyNode(30);
	TreeNode* node3 = BuyNode(2);
	TreeNode* node4 = BuyNode(71);
	TreeNode* node5 = BuyNode(99);
	TreeNode* node6 = BuyNode(11);
	TreeNode* node7 = BuyNode(21);

	node1->left = node2;
	node1->right = node3;
	node2->left = node4;
	node2->right = node5;
	node3->right = node7;
	node3->left = node6;
	return node1;
}

创建好后的树的结构如下图所示
在这里插入图片描述


二叉树的前序遍历

思路导读:二叉树的前序遍历指先遍历二叉树的根,左子树,在遍历右子树。我们可以先画个图来模拟一下它遍历的顺序如下图:
在这里插入图片描述
既然图都画出来了,我们肯定思路都大致清晰了,那用什么方式去遍历呢?循环还是什么?今天我们要讲的方式是递归解决二叉树的遍历,这种是目前比较简单的方式。
代码实现:对于刚刚接触二叉树的友友们可能还不能理解这个递归的方式,没事可以看下图的递归展开图帮助你进一步理解

void PrevOrder(TreeNode* root)//前序遍历--根、左、右 -- 深度优先遍历
{
	if (root == NULL)
	{
		
		return NULL;
	}
	printf("%d ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

在这里插入图片描述
运行结果:在这里插入图片描述


二叉树的中序遍历

思路导读:二叉树的中序遍历指先遍历左子树,再遍历根,最后遍历右子树。这个就不为大家再画递归展开图了,看代码!
代码演示

void InOrder(TreeNode* root)//中序遍历--左、根、右
{

	if (root == NULL)
	{

		return NULL;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

运行结果在这里插入图片描述


计算树节点

思路导读:我们要想计算树节点的个数,我们只需要将其左子树,右子树,还有根的值全部算出有多少即可,我们只需通过像前序遍历的思想来计算。如果不懂的话可以看下面的递归展开图(博主就画了前几步)。在这里插入图片描述
代码实现:

int TreeSize(TreeNode* root)//计算树节点
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeSize(root->right) + TreeSize(root->left) + 1;

}

计算叶子节点

思路导读:我们可以通过遍历该树的左子树和右子树,如果左子树和右子树同时为NULL我们就让它返回1,以此类推,我们可以通过像前面一样的方式遍历二叉树达到计算叶子节点的效果(部分递归展开图)。
在这里插入图片描述
代码实现:

int TreeLeafSize(TreeNode* root)//计算叶子节点
{
	if (root == NULL)
	{
		return 0;
	}
	if((root->left)== NULL &&(root->right) == NULL)
	{
		return 1;
	}
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}


计算树的高度

思路导读:计算树的高度我们可以通过比较它的左子树和右子树找出其树高度中较大的那个然后加上一即可算出来这个树的高度,怎么比较呢?那我们就可以通过利用fmax这个函数来比较其找到最大值。
(部分递归展开图如下)在这里插入图片描述
代码实现:

int TreeHight(TreeNode* root)//树给高度
{
	if (root == NULL)
	{
		return 0;
	}
	//fmax函数,它是C语言(C99)中的一个内置函数,用于比较两个浮点数的大小并返回较大值。
	return fmax(TreeHight(root->left), TreeHight(root->right)) + 1;
}

求第k层节点的个数

思路导读:要想求第k层节点的个数我们需要将该层中左子树和右子树的位置分别记录下来,然后将其相加就是该层的个数。那么另一个问题来了,我们如何找到是这一层呢?我们可以通过每让它往下走一层时,就让k-1,依次递归,当k完全等于1的时候说明已经到了这一层,再返回1即可。(递归展开图如下)
在这里插入图片描述
代码实现:

int TreeLevelK(TreeNode* root, int k)//求第k层结点的个数
{
	assert(k > 0);
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;

	

二叉树查找值为x的节点

思路导读:我们通过前面写了这么多的二叉树的接口,这里我们可以想到,我们先查左子树中是否有相同的,然后我们再去查看右子树中是否有相同的,如果左子树找到了就将该值返回即可。(部分递归展开图如下)
在这里插入图片描述
代码实现:

TreeNode* TreeFind(TreeNode* root, BTDataType x)//二叉树查找值为x的结点
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	} 
	TreeNode* cur = TreeFind(root->left,x);//先找左子树,通过指针记录,防止找到了接着往下走
	if (cur)
	{
		return cur;
	}
	TreeNode* cur1 = TreeFind(root->right, x);//再找右子树,同理指针记录
	if (cur1)
	{
		return cur1;
	}
	return NULL;
}

通过前序遍历的数组构建二叉树

我们先假定传来的数组为:char arr[] = { 'A','B','D','#','#','E','#','H','#','#','C','F','#','#','G','#','#' };
该’#'代表NULL,因此我们先画出其前序遍历的展开图(如下):
在这里插入图片描述
然后我们只需要像前序遍历一样,将其按照根、左子树、右子树一样依次开辟结点赋值,此时我们要注意的是一定要使用指针,防止在递归的过程中其值不会变化。
代码实现如下:

TreeNode* TreeCreate2(char* a, int* pi)//通过前序遍历的数组构建二叉树
{
	if (*(a + *pi) =='#')
	{
		(*pi)++;
		return NULL ;
	}
	TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
	root->data = *(a + (*pi)++);
	root->left = TreeCreate2(a, pi);
	root->right = TreeCreate2(a, pi);
	return root;
}

运行结果:
在这里插入图片描述


二叉树的销毁

思路导读:二叉树的销毁可以像二叉树的后序遍历一样先左子树、右子树
代码实现:

void DestoryTree(TreeNode* root)//销毁
{
	if (root == NULL)
	{
		return NULL;
	}
	DestoryTree(root->left);
	DestoryTree(root->right);
	free(root);

}

层序遍历 – 广度优先遍历

思路导读:我们知道二叉树的一个特性,每一个节点都有俩个子节点,因此我们在可以充分的利用这个特性来实现我们层序遍历,我们可以模拟实现一个队列,让二叉树的根先存进去队列,然后打印其数据,就打印了第一层的数据,此时我们要打印第二层的数据,我们只需要将根的左子树和右子树在分别存入队列,在第二次循环中在依次打印即可实现层序遍历了。记住一定在队列中存的是二叉树根的地址而不是值(如下图所示)在这里插入图片描述
代码实现:

void levelorder(TreeNode* root)//层序遍历 -- 广度优先遍历
{
	QNode q1;
	QueueInit(&q1);
	// TreeHight(root);
	if (root)
	{
		//QueuePush(Queue * pq, QDataType x)
		QueuePush(&q1, root);
	}
	int level = 1;
	while (!QueueEmpty(&q1))
	{
		while (level--)
		{
			TreeNode* front = QueueFront(&q1);//将头元素地址保存在二叉树中
			QueuePop(&q1);
			printf("%c ", front->data);
			if (front->left)
			{
				QueuePush(&q1, front->left);
			}
			if (front->right)
			{
				QueuePush(&q1, front->right);
			}
		}
		level = QueueSize(&q1);
		printf("\n");
	}
	QueueDestroy(&q1);

测试函数:

void Test1()
{
	TreeNode* root = CreateTree1();
	char arr[] = { 'A','B','D','#','#','E','#','H','#','#','C','F','#','#','G','#','#' };
	int i = 0;
	levelorder(TreeCreate2(arr, &i));
}

运行结果:在这里插入图片描述


判断是否是完全二叉树

思路导读:我们可以像前面一样,让它们依次层次入队列,如果在入队列的过程中就碰到了NULL,就说明其不是完全二叉树,然后再将最后一层中队列的元素依次出队列,如果出队列的途中也碰到了NULL也就说明其不是完全二叉树。如果都没碰到则是完全二叉树。
在这里插入图片描述
代码实现

bool TreeComplete(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front == NULL)
			break;
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}
	// 前面遇到空以后,后面还有非空就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}

结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。


🫵🫵🫵 祝各位接下来好运连连 💞

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

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

相关文章

创建个人网站(二)前端主页设计和编写一(太阳移动)

前言 以下内容纯纯当乐子来看就行,知识分享一下这样设计的原因,想看正文直接见下一节 为什么创建个人网站一之后几天没有动静了呢,一个是家里有事实在比较忙,第二个原因是没想到主页要设计成什么样,知道前两天问我姐什…

0基础学java-day22(多用户即时通信系统)

一、QQ 聊天项目演示 聊天通讯系统 在运运行过程出现的异常,应该是类的序列化不一致导致的 1 项目 QQ 演示 2 为什么选择这个项目 只做核心部分,界面相对弱化 3 项目开发流程 3.1 需求分析 3.2 界面设计 3.2.1 用户登录 3.2.2 拉取在线用户列表 …

对偶问题笔记(1)

目录 1 从 Lagrange 函数引入对偶问题2. 强对偶性与 KKT 条件3. 对偶性的鞍点特征 1 从 Lagrange 函数引入对偶问题 考虑如下优化问题 { min ⁡ f 0 ( x ) s . t f i ( x ) ≤ 0 , i 1 , ⋯ , p , h j ( x ) 0 , j 1 , ⋯ , q , x ∈ Ω , \begin{align} \begin{cases}\min…

Mimikatz 的使用(黄金票据的制作)

#江南的江 #每日鸡汤:孤独没有什么反义词,但他的近义词是自由,人生成功的道路上充满了孤独,那么也同样告诉你,你离成功后的自由不远了。 #初心和目标:在网络安全里高出名堂。。。 Mimikatz 本文分为两种…

kafka启动报错“输入行太长。 命令语法不正确“

下午想试一下本地安装启动一下 kafak,参考网上的安装文档,结果一直报错,最开始报的是这个错误: Classpath is empty. Please build the project first e.g. by running gradlew jarAll参考了很多网上的解决办法,最后…

科创金融的向善力量:浙商银行多措并举赋能科创企业,打造科技金融服务生态圈

近日,浙商银行科技金融服务发布会在杭州举行。 发布会以“智汇科创,善行未来”为主题,围绕科技金融服务“向善”新生态,浙商银行重磅推出科创企业全图景服务方案,正式发布科创积分贷,与浙江大学联合发布人…

【开源软件】最好的开源软件-2023-第五名 JHipster

自我介绍 做一个简单介绍,酒架年近48 ,有20多年IT工作经历,目前在一家500强做企业架构.因为工作需要,另外也因为兴趣涉猎比较广,为了自己学习建立了三个博客,分别是【全球IT瞭望】,【…

翻译: LLMs大语言模型影响到高工资的的白领知识工作者 加速各行各业的自动化潜力 Automation potential across sectors

我们已经探讨了生成人工智能可能对您的工作有用,也讨论了分析其对企业的影响。现在,让我们拉远镜头,看看它对不同公司的工作角色以及对不同行业部门的影响。这个视频的结果对特定企业可能不那么直接可行,但也许这会帮助您思考并尝…

IDEA tomcat内存不足

-Xms256m -Xmx256m -XX:MaxNewSize256m -XX:MaxPermSize256m

LLMs推理框架总结

总结一下这些框架的特点,如下表所示: LLM推理有很多框架,各有其特点,下面分别介绍一下表中七个框架的关键点: vLLM:适用于大批量Prompt输入,并对推理速度要求高的场景;Text generat…

14、Kafka 请求是怎么被处理的

Kafka 请求是怎么被处理的 1、处理请求的 2 种常见方案1.1、顺序处理请求1.2、每个请求使用单独线程处理 2、Kafka 是如何处理请求的?3、控制类请求和数据类请求分离 无论是 Kafka 客户端还是 Broker 端,它们之间的交互都是通过 “请求 / 响应” 的方式完…

二叉搜索树第大K节点,剑指offer,力扣

目录 题目地址: 题目: 我们直接看题解吧: 解题方法: 难度分析: 审题目事例提示: 解题分析: 解题思路: 代码实现: 代码补充: 代码实现(非递归&…

20倍压缩比!微软提出大模型提示压缩框架LLMLingua

近期,越来越多研究在探索大型语言模型(LLM)在实际应用中的推理和生成能力。随着 ChatGPT 等模型的广泛研究与应用,如何在保留关键信息的同时,压缩较长的提示成为当前大模型研究的问题之一。 为了加速模型推理并降低成本…

ViewBinding与DataBinding(视图绑定与数据双向绑定)

前言:心中纵是有所盼 严寒没有减 风很冷 我的手已渐蓝 前言 控件查找对于Android开发来说也是一部血泪史,一直为更有效的方案进行了多种方案的研究和探讨。findViewById() 过于繁琐,强制转换不安全;butterkniife 会存在众多臃肿的…

【【UART 传输数据实验】】

UART 传输数据实验 通信方式在日常的应用中一般分为串行通信(serial communication)和并行通信(parallel communication)。 我们再来了解下串行通信的特点。串行通信是指数据在一条数据线上,一比特接一比特地按顺序传…

随笔记录-springboot_LoggingApplicationListener+LogbackLoggingSystem

环境:springboot-2.3.1 加载日志监听器初始化日志框架 SpringApplication#prepareEnvironment SpringApplicationRunListeners#environmentPrepared EventPublishingRunListener#environmentPrepared SimpleApplicationEventMulticaster#multicastEvent(Applicati…

字符设备驱动的加载与卸载

一. 简介 前面几篇文章编写了 字符设备驱动模块加载与卸载框架代码,设置了开发板启动方式。文章地址如下: 字符设备驱动框架的编写-CSDN博客 字符设备驱动模块的编译-CSDN博客 字符设备驱动的加载与卸载前工作-CSDN博客 本文学习如何加载与卸载驱动…

windows10 固定电脑IP地址操作说明

windows10 固定电脑IP地址操作说明 一、无线网络的IP地址设置方法二、有线网络的IP地址设置方法 本文主要介绍,windows10操作系统下,不同的网络类型,对应的电脑IP地址设置方法。 一、无线网络的IP地址设置方法 在桌面右下角,点击…

st.pp.normalize_total(data) # NOTE: no log1p

这段代码在使用 stlearn 包中的 st.pp.normalize_total 函数对数据进行总体计数标准化。标准化后,每个细胞的总计数都将等于 median(total_counts)。 NOTE: no log1p 这行注释表示在标准化后,数据不会进行 log1p 转换。log1p 转换将每个计数值增加 1&a…

Java如何创建线程?到底有几种方式创建线程?

文章目录 继承Thread类实现Runnable接口实现Callable接口匿名内部类形式的线程创建实现接口 VS 继承Thread到底有几种创建线程的方式?参考 继承Thread类 定义一个线程类,重写实现run方法(因为 Thread类也实现了 Runable接口),在其中定义线程…