二叉树的链式结构和遍历(下)

又见面了,小伙伴们。今天我们继续来学习二叉树,今天的内容相对来说比较容易理解,前提是需要你们自己动手画图才会好理解。眼过千遍不如手过一遍。所以小伙伴们要多动手哦。直接开始今天的学习吧

1.二叉树链式结构的实现

1.1 前置说明
在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在我们对二叉树结构掌握还不够深入,为了降低学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	node->data = x;
	node->left = NULL;
	node->right = NULL;

	return node;
}

BTNode* CreatBinaryTree()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);
	BTNode* node7 = BuyNode(7);


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

	return node1;
}
注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。
我们前面学过二叉树的概念是
1. 空树
2. 非空:根节点,根节点的左子树、根节点的右子树组成的
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
任何一个二叉树都可以看成有3个部分组成:根,左子树,右子树。每个子树又可以分成上述3个部分,一直分到它们的左右子树都为空时停止。
1.2 二叉树的遍历
1.2.1前序、中序以及后序遍历
学习二叉树结构,最简单的方式就是遍历。所谓 二叉树遍历 (Traversal) 是按照某种特定的规则,依次对二叉 树中的节点进行相应的操作,并且每个节点只操作一次 。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有: 前序 / 中序 / 后序的递归结构遍历
1. 前序遍历 (Preorder Traversal 亦称先序遍历 )—— 访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历 (Inorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历 (Postorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之后。
由于被访问的结点必是某子树的根, 所以 N(Node )、 L(Left subtree )和 R(Right subtree )又可解释为 根、根的左子树和根的右子树 NLR LNR LRN 分别又称为先根遍历、中根遍历和后根遍历。
下面来看一个二叉树,小伙伴们可以先试试它的前序,中序,后序都是什么。
第一次写这个的时候小伙伴们可能还搞不懂,就是直到左右子树都是空的时候才会停止,只要还能继续往下走,就要一直继续走。记住这句话应该就没有问题了。建议一定要自己画图才能理解其中的意思。
来看代码吧(如果自己想在电脑上试一下的话,要把开头给的代码补上才完整)
//前序
void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	printf("%d ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

//中序
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

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

//后序
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

int main()
{
	BTNode* root = CreatBinaryTree();
	PrevOrder(root);
	printf("\n");

	InOrder(root);
	printf("\n");

	PostOrder(root);
	printf("\n");
}

前序遍历结果: 1 2 3 4 5 6
中序遍历结果: 3 2 1 5 4 6
后序遍历结果: 3 2 5 6 4 1

总体的思想就是递归思想,我会画一个前序的递归图帮助小伙伴们更好的理解,其它的遍历小伙伴们可以自己试一下哦。

1.2.2 层序遍历
层序遍历 :除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1 ,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第 2 层 上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。(不能用递归来表示)
代码实现:
void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root)
		QueuePush(&q, root);

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

		printf("%d ", front->data);

		if (front->left)
			QueuePush(&q, front->left);
		if (front->right)
			QueuePush(&q, front->right);
	}
	printf("\n");
	QueueDestroy(&q);
}

如果想添加以前写的队列的代码的话,可以把队列文件的.c和.h 文件复制然后添加到二叉树文件的里面,如图所示

添加完之后需要对头文件做一些修改

2.求二叉树的各种节点问题

2.1计算节点个数

计算节点个数有2个方法:

方法1:把size定义成全局变量,然后遍历整棵数,如果不空的话,size++

int size = 0;
void BTreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;

	++size;

	BTreeSize(root->left);
	BTreeSize(root->right);
}
BTreeSize(root);//方法1
printf("BTreeSize:%d\n", size);

方法2:分治法。左子树+右子树+1(1代指的是根)

举个例子,假如学校校长想要统计学生个数,那么是不是校长先给院长下达命令,然后院长在给辅导员下达命令,最后辅导员再给各班班长下达命令,让班长统计人数,然后依次上报,最后校长就知道学生有多少人了。递归思想就是这样

int BTreeSize(BTNode* root)
{
	/*if (root == NULL)
	{
		return 0;
	}
	return BTreeSize(root->left) 
		+ BTreeSize(root->right) + 1;*/

	return root==NULL?0: BTreeSize(root->left) + BTreeSize(root->right) + 1; 
}
printf("BTreeLeafSize:%d\n", BTreeLeafSize(root));
2.2 计算叶子节点个数

这个我们应该很好想到,就是当左子树和右子树为空的时候就是到叶子节点了。递归的终止条件有2个。当树为空时返回0,当左子树和右子树都为空的时候返回1

int BTreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	if (root->left == NULL && root->right == NULL)
		return 1;
	return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}
printf("BTreeLeafSize:%d\n", BTreeLeafSize(root));
2.3 计算二叉树高度

二叉树的高度由最长路径决定的,哪个路线最长,树的高度就是多少

先定义2个变量分别计算左右子树的长度,哪个树长树的高度就是它。

int BTreeHight(BTNode* root)
{
	if (root == NULL)
		return 0;
	int leftHight = BTreeHight(root->left);
	int rightHight = BTreeHight(root->right);
	return leftHight > rightHight ? BTreeHight(root->left) +1: BTreeHight(root->right)+1;

}

当然也可以这样写,不过这种写法的效率非常低,当数据量非常大的时候就会浪费很大的时间

int BTreeHight(BTNode* root)
{
	if (root == NULL)
		return 0;
	return BTreeHight(root->left) > BTreeHight(root->right) 
		? BTreeHight(root->left) + 1 
		: BTreeHight(root->right) + 1;
}
2.4 计算第K层节点个数

可以转换成左子树的第k-1层和右子树的第k-1层。递归的结束条件是k==1且节点不为空。

int BTreeLevelKSize(BTNode* root, int k)
{
	assert(k);
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	return BTreeLevelKSize(root->left, k - 1) + BTreeLevelKSize(root->right, k - 1);
}
	printf("BTreeLevelKSize:%d\n", BTreeLevelKSize(root,3));

2.5 查找值为x的节点

我先展示一下经典的错位写法,当然我开始也是这样想的

BTNode* BTreeFind(BTNode* root, BTDataType x)//错误写法,找到还要返回上一层
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	BTreeFind(root->left, x);
	BTreeFind(root->right, x);

}

错误的原因就是找到值的话不是直接退出递归,而是要返回上一层,一直到开头的地方。其实只要自己画一个递归展开图就知道是怎么回事了。讲递归的时候我们就知道是有去有回,不是直接结束

正确思路就是定义变量要记录找到的值,然后直接返回就行。

BTNode* BTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;

	BTNode* ret1 = BTreeFind(root->left, x);
	if (ret1)
		return ret1;
	BTNode* ret2 = BTreeFind(root->right, x);
	if (ret2)
		return ret2;

	return NULL;
}
2.6 判断是否为完全二叉树

这时候就会有人想可不可以用节点个数来判断是不是完全二叉树,只要在范围之内就是完全二叉树,那么这种想法是错误的,这个想法只能用来判断是不是满二叉树。我们已经知道完全二叉树的特征是最后一层可以不满,但叶子节点必须是连续的,所以说用节点个数来判断是行不通的。

具体实现如下:我们可以通过层序遍历来判断,只要队列不为空时,就继续往下走

代码如下:

bool BTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root)
		QueuePush(&q, root);

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

		//遇到空就跳出
		if (front == NULL)
			break;

		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}
	//检查后面的节点有没有非空
	//有非空,就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

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

	QueueDestroy(&q);
	return true;
	
}

 好了,小伙伴们,今天的学习就到这里,下一节我们来练习一些二叉树有关的习题,关于二叉树的初级部分就学完了。高级部分要等到我们学完C++后才能更好的理解。感谢大家的阅读。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Stability AI发布Stable Video 3D模型:可从单张图像创建多视图3D视频,视频扩散模型史诗级提升!

Stability AI发布了Stable Video 3D (SV3D),这是一种基于稳定视频扩散的生成模型,推动了3D技术领域的发展,并大大提高了质量和视图一致性。 该版本有两个版本: SV3D_u:该变体基于单图像输入生成轨道视频,无需相机调节。 SV3D_p:扩…

鸿蒙Harmony应用开发—ArkTS(@Link装饰器:父子双向同步)

子组件中被Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。 说明: 从API version 9开始,该装饰器支持在ArkTS卡片中使用。 概述 Link装饰的变量与其父组件中的数据源共享相同的值。 限制条件 Link装饰器不能在Entry装饰的自定义组件中使用…

伊理威科技:抖音开网店新手刚做选啥品

在数字浪潮中,抖音不仅是展示才艺的舞台,更是创业者的新天地。新手若想在这片热土上开垦网店,选品便是首要课题。选择产品如同种下希望的种子,既要考量土壤肥沃度,也得预测风雨适宜期。 兴趣与专长是选品的罗盘。热爱所…

STM32之HAL开发——RCC外设CubeMX配置时钟

RCC外设介绍 RCC是Reset and Clock Control (复位和时钟控制)的缩写,它是STM32内部的一个重要外设,负责管理各种时钟源和时钟分频,以及为各个外设提供时钟使能。RCC模块可以通过寄存器操作或者库函数来配置。 RCC是复位和时钟控制模块&#…

GeoAI 简明教程

想象一下,能够在野火发生后立即发现它,可视化全球人口变化,或者立即从地图中提取线条。 GeoAI,即地理空间人工智能,是指地理信息系统 (GIS)、人工智能 (AI) 和机器学习 (ML) 的交叉点。 这个领域正在彻底改变我们与世界…

数据结构 - 二叉树非递归遍历

文章目录 前言一、前序二、中序三、后序 前言 本文实现二叉树的前中后的非递归遍历,使用栈来模拟递归。 文字有点简略,需要看图和代码理解 树节点: typedef char DATA; //树节点 typedef struct Node {DATA data; //数据struct Node* left…

基于springboot+vue的物资仓储物流管理系统(源码+论文)

作者主页:Java码库 主营内容:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】:Java 【框架】:spring…

数据治理的迷失:揭开“屎上雕花”现象的真相

数据治理是企业信息化建设的核心环节,它直接关系到数据的质量、安全性和价值实现。然而,在实际操作中,不少企业却陷入了“屎上雕花”的误区,即在数据本身存在问题的情况下,试图通过表面的修饰来提升数据的外在表现&…

QT:三大特性

QT的三大特性: 1、信号与槽 2、内存管理 3、事件处理 1、信号与槽 当信号产生时,就会自动调用绑定的槽函数。 自定义信号: 类中需要添加O_OBJECT宏 声明: signals标签之下进行声明 定义: 信号不需要定义 …

使用 PyOpenGL 进行 2D 图形渲染总结

一、说明 OpenGL是一个广泛使用的开放式跨平台实时 3D 图形库,开发于二十多年前。它提供了一个低级API,允许开发人员以统一的方式访问图形硬件。在开发需要硬件加速且需要在不同平台上运行的复杂 2D 或 3D 应用程序时,它是首选平台。它可以在…

Day 14 JDBC

JDBC 1、简单入门 Statement2、preparedStatement3、主键回显4、批量操作5、事务6、Druid6.1 工具类V16.2 工具类V26.3 1、简单入门 Statement 步骤: 1、注册驱动 2、创建连接 3、创建 Statement对象 4、编写sql语句 并且发送sql语句获得结果集 5、解析结果集 6、释放资源 注意…

1、Dev软件的安装

预先善其事,必先利其器,想要学习编程语言的第一步就是学会使用编译软件,在这里我们所使用的编译软件为 Dev-cpp 5.11 ,在这一章节,我们将讲述如何下载并安 Dev-cpp 5.11。 一、下载 首先,我们要先学会下载 Dev-cpp 5.11,这里我们点击:Dev-cpp 5.11,即可完成下载,注…

Appium —— 移动应用自动化测试开源工具!

Appium介绍 Appium是一个用于自动化移动应用程序的开源工具,它支持iOS和Android平台。通过Appium,开发人员可以使用各种编程语言(如Java、Python、Ruby等)编写测试脚本,以自动化测试移动应用程序的功能和用户界面。Ap…

pytest运行结果解析及其改造

简介:场景假设 - 当运行pytest完成后,需要针对运行的结果进行即时的反馈,打印 PASS 或者 FAIL,及其运行失败的原因,最后将结果推送给消息机器人。 历史攻略: pytestallure安装和使用 pytest:…

C# 对App.config、Web.config的appSettings节点数据进行加密

appSettings加密原因,就是因为容易暴露服务器账号和密码,而且客户也不允许 使用ASP.NET提供的命令工具aspnet_regiis来创建加密命令;aspnet_regiis是提供了直接对配置文件加密的功能的;并且使用aspnet_regiis加密的配置节点在读取…

贪吃蛇(C语言超详细版)

目录 前言: 总览: API: 控制台程序(Console): 设置坐标: COORD: GetStdHandle: STD_OUTPUT_HANDLE参数: SetConsoleCursorPosition: …

Springboot测试找不到bean

1.没有加注解 service类上需要加注解 2.Test类引用错误 3.测试类与我们使用的包名不同,两个都是com.travel才可以,否则扫描不到 4.引入的启动类错误 5.不是很确定,但是也是我犯的错误 6.没有配置好XML文件 有的话再补充

【并发编程】锁相关公平锁和非公平锁?可重入锁锁的升级乐观锁和悲观锁版本号机制CAS 算法乐观锁有哪些问题?

目录 ​编辑 锁相关 公平锁和非公平锁? 可重入锁 锁的升级 乐观锁和悲观锁 版本号机制 CAS 算法 乐观锁有哪些问题? 锁相关 公平锁和非公平锁? 公平锁 : 锁被释放之后,先申请的线程先得到锁。性能较差一些,因…

Nacos介绍和统一配置管理

Nacos(全称为 Alibaba Cloud Nacos,或简称为 Nacos)是一个开源的分布式服务发现和配置管理系统。它由阿里巴巴集团开发并开源,旨在帮助开发人员简化微服务架构下的服务注册、发现和配置管理。 一、Nacos 提供了以下主要功能&…

Deconstructing Denoising Diffusion Models for Self-Supervised Learning

开头说点题外话:这篇可谓是大咖云集啊,刘壮、谢赛宁、何凯明这些耳熟能详的名字,并且这篇论文一些人也觉得分析特别到位,不愧是大佬视角,配得上“解构”两个字;很巧的是,本科阶段的团队导师也是…