二叉树的链式结构

1.二叉树的构建

想要对二叉树进行操作,我们得先构建一个二叉树

因此,这里的问题就是给定数据,如何将数据在逻辑结构上变成二叉树?

结构定义:

typedef int BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType val;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

我们想构建上图的二叉树(#代表空),有两种方式

方法1:手动构建

  • 手动创建每一个结点,再手动将它们连起来
BTNode* CreateTreeNode(BTDataType x)
{
	BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
	if (newNode == NULL)
	{
		perror("CreateTreeNode:malloc fail");
		exit(-1);
	}
	newNode->val = x;
	newNode->left = NULL;
	newNode->right = NULL;

	return newNode;
}

//方法1:手动构建
BTNode* CreateBinaryTree(BTDataType* a)
{
	BTNode* n1 = CreateTreeNode('a');
	BTNode* n2 = CreateTreeNode('b');
	BTNode* n3 = CreateTreeNode('c');
	BTNode* n4 = CreateTreeNode('d');
	BTNode* n5 = CreateTreeNode('e');
	BTNode* n6 = CreateTreeNode('f');
	BTNode* n7 = CreateTreeNode('g');

	n1->left = n2;
	n2->left = n3;
	n2->right = n4;
	n4->left = n5;
	n4->right = n6;
	n5->right = n7;

	return n1;
}

很显然,这种方法不够高效,如果有上百个结点,每个结点都要我们手动去连接,效率实在太低;

我们的第二种方法是给你一个二叉树的前序/中序/后序遍历,用递归去构建二叉树;

当然了,在讲这种方法前,我们得先知道一棵树的前序/中序/后序遍历是什么


1.1前序/中序/后序遍历

对于任意一颗二叉树,都有三种遍历,对应着不同的结点访问顺序

  • 前序遍历:->左子树->右子树
  • 中序遍历:左子树->->右子树
  • 后序遍历:左子树->右子树->

可以看到,三种遍历的本质是根结点的访问顺序

  • 在以后的题目当中,任何一棵树我们都要分成三部分:根 左子树 右子树
  • 前序遍历,先访问根,也就是先将根打印出来,再去递归左子树和右子树
  • 中序/后序同理
//前序遍历
void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	printf("%c ", root->val);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

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

	InOrder(root->left);
	printf("%c ", root->val);
	InOrder(root->right);
}

//后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->val);
}

讲完了二叉树的遍历,就可以试着用递归去构建二叉树了

方法2:递归构建

  • 假设一个二叉树的前序遍历”a b c # # d e # g # # f # # # “,根据该结果去构建二叉树,#代表空树
  • 用指针pi记录字符串的下标
  • 由于是前序遍历,我们的构建顺序为先构建根,再构建左子树和右子树,遇到空pi往后走一步再返回
//方法2:代码自动构建
BTNode* CreateBinaryTree(BTDataType* a, int* pi)
{
	if (a[(*pi)] == '#')
	{
		(*pi)++;
		return NULL;
	}

	BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
	if (newNode == NULL)
	{
		perror("CreateBinaryTree:malloc fail");
		exit(-1);
	}
	newNode->val = a[(*pi)++];

	newNode->left = CreateBinaryTree(a, pi);
	newNode->right = CreateBinaryTree(a, pi);

	return newNode;
}

2.二叉树的基本操作

2.1求二叉树的结点个数

  • 二叉树的结点个数 = 左子树的结点个数 + 右子树的结点个数 + 1
//结点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;

	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

2.2求二叉树的叶子结点个数

  • 如果一个结点的左右子树都为空,则该结点为叶子结点
  • 总叶子结点个数 = 左子树叶子节点个数 + 右子树叶子节点个数
//叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;

	if (root->left == NULL && root->right == NULL)
		return 1;

	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

2.3求二叉树第k层的结点个数

  • 第k层的结点个数 = 左子树的k-1层结点个数 + 右子树的k-1层结点个数
//第k层结点个数
int BinaryTreeKSize(BTNode* root, int k)
{
	if (root == NULL)
		return 0;

	if (k == 1)
		return 1;

	return BinaryTreeKSize(root->left, k - 1) + BinaryTreeKSize(root->right, k - 1);
}

2.4二叉树查找

  • 先在当前结点找,如果找不到,再去左子树和右子树找,只要在其中一颗子树中找到了,就直接返回,不需要再往下找了
//查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;

	if (root->val == x)
		return root;

	BTNode* ret1 = BinaryTreeFind(root->left, x);
	if (ret1)
		return ret1;

	BTNode* ret2 = BinaryTreeFind(root->right, x);
	if (ret2)
		return ret2;

	return NULL;
}

3.二叉树的层序遍历

3.1层序遍历的定义

上面介绍的前序/中序/后序遍历属于深度优先遍历(DFS),准确点说前序遍历属于深度优先遍历;

而层序遍历属于广度优先遍历(BFS)

  • 深度优先遍历:先访问完左子树在返回去访问右子树

  • 广度优先遍历:先访问完一层,再去访问下一层

3.2层序遍历的实现

  • 我们的要求是先打印当前层结点,再打印下一层的结点
  • 思路:创建一个队列,对于每一个结点,有两种情况:
    (1)为空,此时什么都不用做(如果想打印空,加个打印即可)
    (2)为正常值,此时将该结点记录下来后出队列,再将该节点的左结点和右节点入队列
    结束条件是队列为空

//层序遍历
void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root != NULL)
		QueuePush(&q, root);

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

		if (front)
		{
			printf("%c ", front->val);
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
	}

    QueueDestroy(&q);
}

如果想打印完一层后换行,再打印下一层,该怎么实现?

  • 用一个变量来记录每一层的结点个数,当该变量递减到0,表示该层出完了,换行后赋予变量下次层的结点个数,直到队列为空
  • 怎么知道下一层的结点个数呢?当一层的结点出完后,此时队列数据的个数就是下一层的结点数

//层序遍历
void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root != NULL)
		QueuePush(&q, root);

	int levelSize = 1;

	while (!QueueEmpty(&q))
	{
		while (levelSize--)
		{
			QDataType front = QueueFront(&q);
			QueuePop(&q);

			if (front)
			{
				printf("%c ", front->val);
				QueuePush(&q, front->left);
				QueuePush(&q, front->right);
			}
		}
		printf("\n");
		levelSize = QueueSize(&q);
	}

    QueueDestroy(&q);
}

3.3完全二叉树的判断

  • 思路:同样是利用队列的性质,假如是一个完全二叉树,那么遇到第一个空结点,队列后面的数据就全是空
  • 因此判断是不是完全二叉树就变成了判断遇到第一个空时,队列中后面的数据有没有非空结点,如果有,则不是完全二叉树
  • 有没有这样的可能性,遇到第一个非空时,只是因为二叉树非空数据还没入到队列中才导致队列中后面的数据都是空,而不是该树本身是完全二叉树?答案是不可能
  • 我们考虑极端情况
//判断一颗二叉树是否为完全二叉树
bool IsCompleteBinaryTree(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root != NULL)
		QueuePush(&q, root);

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

		if (front)
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
		else
		{
			break;
		}
	}

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

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

	QueueDestroy(&q);
	return true;
}

4.二叉树的销毁

  • 这里我们用后序遍历销毁
  • 如果用前序/中序遍历销毁,free后root->left或root->right就是野指针了
  • 记得在外面将root置空,避免野指针
//二叉树的销毁
void BinaryTreeDestroy(BTNode* root)
{
	if (root == NULL)
		return;

	BinaryTreeDestroy(root->left);
	BinaryTreeDestroy(root->right);
	free(root);
}

需要源码的小伙伴可以去我的Gitee主页查看

BInaryTree/BInaryTree · baiyahua/LeetCode - 码云 - 开源中国 (gitee.com)

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

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

相关文章

【go语言开发】go项目打包成Docker镜像,包括Dockerfile命令介绍、goctl工具生成

本文主要介绍如何将go项目打包成镜像,首先介绍Dockerfile常用命令介绍,然后介绍使用工具goctl用于生成Dockerfile,还可以根据需求自定义指令内容,最后讲解如何将go-blog项目打包成镜像,以及如何运行等 文章目录 前言Do…

助力乡村振兴:VR全景技术能在乡村发展哪些方面提供帮助

引言: VR全景技术是具有巨大潜力的创新技术,它能够为乡村振兴带来许多机会。通过创建沉浸式的虚拟现实环境,VR全景技术在乡村旅游、农业、教育和文化推广等方面都能发挥重要作用。 一、乡村旅游 1.提升乡村景区宣传效果 通过使用VR全景技术…

IDEA 2023版本解决Error running Command line is too long. Shorten the command line

报错信息 Error running XXXApplication. Command line is too long. Shorten the command line via JAR manifest or via a classpath file and rerun. 解决方法 1.右键服务 选择Edit Configuration 2. 点击 Modify options 3. 勾选Shorten commnand line 4. 修改并保存

Java 实现 图片 添加 文字水印、图片水印 工具类

一、话不多说,直接上代码 1.1,水印类型枚举 import lombok.AllArgsConstructor; import lombok.Getter;/*** author: wangjing* createTime: 2023-12-05 15:01* version: 1.0.0* Description: 水印类型枚举*/ Getter AllArgsConstructor SuppressWarni…

已解决error: (-215:Assertion failed) inv_scale_x > 0 in function ‘cv::resize‘

需求背景 欲使用opencv的resize函数将图像沿着纵轴放大一倍,即原来的图像大小为(384, 512), 现在需要将图像放大为(768, 512)。 源码 import cv2 import numpy as np# 生成初始图像 img np.zeros((384, 512), dtypenp.uint8) img[172:212, 32:-32] 255 H, W …

天池SQL训练营(二)-SQL基础查询与排序

-天池龙珠计划SQL训练营 Task02:SQL基础查询与排序 SQL训练营页面地址:https://tianchi.aliyun.com/specials/promotion/aicampsql 一、SELECT语句基础 1.1 从表中选取数据 SELECT语句 从表中选取数据时需要使用SELECT语句,也就是只从表…

机器学习---线性回归算法

1、什么是回归? 从大量的函数结果和自变量反推回函数表达式的过程就是回归。线性回归是利用数理统计中回归分析来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。 2、一元线性回归 3、多元线性回归 如果回归分析中包括两个或两个以上的自变量&a…

基于springboot校园二手平台的开发与设计

目 录 1 绪论 1 1.1 课题研究背景 1 1.2 研究意义 1 1.3 研究的目标 2 2 系统技术选型 3 2.1 数据库选择 3 2.2 开发工具的选择 3 2.3 后端框架选择 3 2.4 前端框架选择 3 3 系统需求和可行性分析 4 3.1 总体设计原则 4 3.2 需求分析 4 3.3 可行性分析 5 3.3.1 技术可行性 5 3…

我想修改vCenter IP地址

部署vCenter Server Appliance后,您可以在vCenter修改DNS设置并选择域名服务器使用。您可以编辑vCenter Server Appliance的IP地址设置。从vSphere 6.5开始正式支持vCenter修改IP地址。因此可以更改vCenter Server Appliance的IP地址和DNS设置。 注意:更…

shein、亚马逊卖家测评如何利用自养买家号提升购买转化率?

自养买家号测评的作用,对于众多卖家来说,恐怕并不陌生。它可以快速注入评论,提升排名,创建热卖商品的一种催化剂。在当前电子商务行业蓬勃发展的背景下,买家号的重要性越来越凸显。 买家会花时间浏览店铺的评价&#…

Unity随笔1 - 安卓打包JDK not found

今天遇到一个很奇怪的事情,之前可以正常打安卓包,但是突然报错如下: 提示很明显,找不到JDK了。可是我在下载Unity的时候明明安装了所有需要的组件,为什么今天突然不行。 看了眼Unity hub里面,没问题。 那就…

一文了解直流稳压电源调试方法

直流稳压电源是是一种将交流或直流电源转换为稳定直流电压的电子装置,被广泛应用于国防、科研、医疗、通讯、工业自动化设备等领域。 直流稳压电源一般由变压器、整流电路、滤波电路和稳压电路组成。变压器会将交流电源转换为所需的电源电压,整流电路将交…

Leetcode—383.赎金信【简单】

2023每日刷题(五十) Leetcode—383.赎金信 实现代码 class Solution { public:int arr[26] {0};int arr2[26] {0};bool canConstruct(string ransomNote, string magazine) {int len ransomNote.size();int len2 magazine.size();for(int i 0; i …

csv文件中账号数字显示为科学计数法1.23E+16

参见: 如何解决导出csv文件数字自动变为科学计数法(数值中带E)_csv格式数字老是变成什么e-CSDN博客 其中,在倒数第2张图 里 选“新工作表”。

什么是LIMS实验室信息管理系统 LIMS系统功能介绍

实验室信息管理系统,也称为 LIMS系统,是一种软件解决方案,它通过自动化手动流程来支持现代实验室操作。因此,生命科学专业人员可以实时访问准确无误的信息。该软件使用户能够更有效地管理样本、分析相关数据并根据相关数据采取行动…

智能优化算法应用:基于人工水母算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于人工水母算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于人工水母算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.人工水母算法4.实验参数设定5.算法结果6.参考…

云架构的思考2--云上架构

目录 1 云架构考虑因素2 云架构设计步骤2.1 梳理需求2.1.1 功能需求2.1.2 非功能需求 2.2 业务架构2.3 设定合理预期2.4 确认应用程序迁移云端、托管或者重新编写(可选)2.5 选择云服务模式和部署模式2.5.1 选择云服务模式2.5.2 选择部署模式2.5.3 格外的…

海思3516DV500下的目标识别算法运行评估,包含yolov7,yolov8

目前在3516DV500下,自己训练的模型的评估实测结果。根据实际模型会有些许差异。 涉及到技术细节的部分因为商业用途,有部分省略。如需相关技术服务项目合作可私信联系。 我司推出的目标识别跟踪模块,支持热红外、可见光主流多光谱视频输入与目…

异常追踪与 JIRA 实现双向联动

前言 当应用程序或系统出现异常时,通常需要及时处理以保证系统的正常运行。通过异常追踪与 JIRA 双向联动,可以让企业内部相关人员快速了解、分析问题故障发生的原因、追溯并记录故障的处理过程,有效提高人员的沟通效率,极大降低…

java--成员内部类、静态内部类、局部内部类

1.内部类 ①是类中的五大成分之一(成员变量、成员方法、构造器、内部类、代码块),如果一个类定义另外一个类的内部,这个类就是内部类。 ②场景:当一个类的内部,包含了一个完整的事物,且这个事务没必要单独设计时&…