【数据结构】二叉树之链式结构

news2024/12/28 21:43:22

在这里插入图片描述
🔥博客主页 小羊失眠啦.
🎥系列专栏《C语言》 《数据结构》 《Linux》《Cpolar》
❤️感谢大家点赞👍收藏⭐评论✍️


在这里插入图片描述

文章目录

  • 一、前置说明
  • 二、二叉树的遍历
    • 2.1 前序遍历
    • 2.2 中序遍历
    • 2.3 后序遍历
    • 2.4 层序遍历
  • 三、二叉树的结点个数
    • 3.1 二叉树的总结点数
    • 3.2 二叉树的叶子结点数
    • 3.3 二叉树第k层结点数
  • 四、二叉树的高度/深度
  • 五、二叉树的查找
  • 六、二叉树的创建和销毁

一、前置说明

在学习二叉树各种各样的操作前,我们先来回顾一下二叉树的概念:

二叉树是度不超过2的树,由根结点和左右2个子树组成,每个子树也可以看作一颗二叉树,又可以拆分为根结点和左右两颗子树…

在这里插入图片描述

是不是很熟悉,一个大问题可以拆分为两个子问题,每个子问题又可以拆分为更小的子问题,这样层层拆分到不可拆分(遇到空树)的过程,不就是递归吗!因此,我们可以得出:

树是递归定义的,后续树的各种操作正是围绕着这一点进行的。

二、二叉树的遍历

我们先从最简单的操作----遍历学起。所谓二叉树遍历(Traversal)就是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个节点有且只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。二叉树的遍历分为四种:前序遍历中序遍历后序遍历层序遍历

2.1 前序遍历

前序遍历(Preorder Traversal)又称先根遍历,即先遍历根结点,再遍历左子树,最后遍历右子树。而对于子树的遍历,也服从上述规则。利用递归,我们可以很快地写出代码:

//前序遍历
void PrevOrder(BTNode* root) {
	//遇到空树,递归终点
    if (root == NULL) {
		printf("NULL ");

		return;
	}
    //对根节点进行操作(此处为打印)
	printf("%d ", root->val);
    //递归遍历左子树
	PrevOrder(root->left);
    //递归遍历右子树
	PrevOrder(root->right);
}

为了更好地理解这个过程,我们可以画出递归展开图如下:

在这里插入图片描述

2.2 中序遍历

中序遍历(Inorder Traversal)又称中根遍历,即先遍历左子树,再遍历根结点,最后遍历右子树。同样,子树的遍历规则也是如此。递归代码如下:

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

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

2.3 后序遍历

后序遍历(Inorder Traversal)又称后根遍历,即先遍历左子树,再遍历右子树,最后遍历根结点。照葫芦画瓢,递归代码如下:

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

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

2.4 层序遍历

除了上面的前中后序遍历,还可以对二叉树进行层序遍历。所谓层序遍历就是从所在二叉树的根节点出发,首先访问第1层的根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推。这样自上而下,自左向右逐层访问树的结点的过程就是层序遍历。

在这里插入图片描述

与前面三种遍历不同,层序遍历属于广度优先遍历,因此我们可以利用队列先进先出的特性,将每个结点一层一层依次入队,然后依次出队进行操作即可。具体演示及代码如下:

img

void LevelOrder(BTNode* root)
{
	Que q;
	QueueInit(&q);

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

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		printf("%d ", front->val);
		if (front->left)
			QueuePush(&q, front->left);

		if (front->right)
			QueuePush(&q, front->right);

		QueuePop(&q);
	}
	printf("\n");

	QueueDestroy(&q);
}

在这里插入图片描述

三、二叉树的结点个数

3.1 二叉树的总结点数

一颗二叉树的结点数我们可以看作是根结点+左子树结点数+右子树结点数,那左右子树的结点数又是多少呢?按照相同的方法继续拆分,层层递归直到左右子树为空树,返回空树的结点数0即可。递归代码如下:

int TreeSize(BTNode* root)
{
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

3.2 二叉树的叶子结点数

左右子树都为空的结点即是叶子结点。这里分为两种情况:左右子树都为空左右子树不都为空

  1. 当左右子树都为空时,则这颗树的叶子结点数为1(根节点)。

  2. 当左右子树不都为空,即根结点不是叶子结点时,这棵树的叶子结点数就为左子树叶子结点数+右子树叶子结点数(空树没有叶子结点)。

int TreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;

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

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

3.3 二叉树第k层结点数

类似的,一颗树第k层的结点数我们可以拆分为其左子树第k-1层结点+右子树第k-1层结点。这样层层递归下去,直到k==1求树的第1层结点数时返回1(树的第1层只有根结点),而如果在递归过程中遇到空树就返回0(空树没有结点)。例如下面一颗树:

在这里插入图片描述

int TreeKLevel(BTNode* root, int k)
{
	assert(k > 0);

	if (root == NULL)
		return 0;

	if (k == 1)
	{
		return 1;
	}

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

在这里插入图片描述

四、二叉树的高度/深度

树中结点的最大层次称为二叉树的高度。因此,一颗二叉树的高度我们可以看作是

1(根结点)+左右子树高度的较大值。层层递归下去直到遇到空树返回0即可,递归代码如下:

int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;

	return fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1;
}

在这里插入图片描述

五、二叉树的查找

二叉树的查找本质上就是一种遍历,只不过是将之前的打印操作换为查找操作而已。我们可以使用前序遍历来进行查找,先比较根结点是否为我们要查找的结点,如果是,之间返回;如果不是,遍历左子树和右子树,返回其查找的结果;如果都找不到,返回空指针。代码如下:

// 二叉树查找值为x的结点
BTNode* TreeFind(BTNode* root, int x)
{
	if (root == NULL)
		return NULL;

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

	BTNode* ret = NULL;
	ret = TreeFind(root->left, x);
	if (ret)
		return ret;

	ret = TreeFind(root->right, x);
	if (ret)
		return ret;

	return NULL;
}

六、二叉树的创建和销毁

最后,我们再来看看如何来创建和销毁一颗二叉树。我们前面说过:二叉树是递归定义的。有了前面的基础,二叉树的创建和销毁也就不是什么难事了。

BTNode* BuyNode(int x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

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

	return node;
}

// 二叉树销毁
void TreeDestroy(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	TreeDestroy(root->left);
	TreeDestroy(root->right);
	free(root);
	//root = NULL;
}

本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位铁汁们的支持。文章有任何问题可以在评论区留言,小羊一定认真修改,写出更好的文章~~

在这里插入图片描述

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

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

相关文章

mysql处理40w数据脚本执行慢问题

需求背景: 2张表 SS_ZYXX 1w数据,WD_GZPZ 50w数据 SS_ZYXX.id WD_GZPZ.zyxx_id 找到SS_ZYXX表有数据,关联表WD_GZPZ没有数据的SS_ZYXX表的id 处理方案 方案一: 联合查询: 下面sql,在mysql执行时间3…

netty(三) taskQueue自定义任务,http服务器快速入门,netty核心模块,Unpooled

如果执行某些业务比较复杂,比较耗时,可以使用异步来完成 当然可以有多个任务 上面的结果是,在第一个任务处理完,再等20秒执行,简单来说,就是第一个在10秒执行,第二个在第30秒的时候执行&#…

vue2使用ts vue-class-component

目前,对于Vue3来说,TypeScript的支持已经相当成熟,但公司的老项目一直处于迭代和维护无法从v2重构成v3,并且重构的成本也是很大的一个问题,所以记录一下vue2如何去搭配TypeScript。 目录 一、脚手架创建项目 二、vu…

二叉树的递归套路(2)

与其明天开始,不如现在行动! 文章目录 最大二叉搜索树 💎总结 最大二叉搜索树 题目 给定一颗二叉树的头节点head,返回这颗二叉树中最大的二叉搜索子树的节点数量 搜索二叉树:整棵树上没有重复值,左树的值都…

解决ansible批量加入新IP涉及known_hosts报错的问题

我们把一批新的IP加入到ansible的hosts文件,比如/etc/ansible/hosts,往往会有这样的提示, 因为本机的~/.ssh/known_hosts文件中并有fingerprint key串,使用ssh连接目标主机时,一般会提示是否将key字符串加入到~/.ssh/…

【密码学引论】分组密码

第三章 分组密码 DES、IDEA、AES、SM4 1、分组密码定义(按照五个组成部分答) 密钥空间:属于对称加密算法kekd明密文空间:将明文划分为m比特的组,每一块依次进行加密加解密算法:由key决定一个明文到密文的…

Redis多机数据库

文章目录 Redis多机数据库一、主从复制1、旧版复制功能的实现a、同步b、命令传播 2、旧版复制功能的缺陷3、新版复制功能的实现a、部分同步功能b、复制实现步骤 4、心跳检测 二、哨兵1、Sentinel概念2、Sentinel初始化流程3、故障转移过程 三、集群1、几个概念2、集群创建流程a…

西南科技大学(数据结构A)期末自测练习一

一、填空题(每空0.5分,共5分) 1、数据结构是指( A )。 A、数据元素的组织形式 B、数据类型 C、数据存储结构 D、数据定义 2、数据结构被形式地定义为(D,R),其中D是( B )的有限集合,R是D上( D )的有限集合。 (1)A.算法B.数据元素C.数据操作D.逻辑结构 (2)A.操作B.…

springboot启动Table ‘xxx‘ already exists

jpa.generate-ddl和jpa.hibernate.ddl-auto都可以控制是否执行datasource.schema脚本,来初始化数据库结构,只要有一个为可执行状态就会执行,比如jpa.generate-ddl:true或jpa.generate-ddl:update,并没有相互制约上下级的关系。 要…

使用com组件编辑word

一个普通的窗体应用,6个button using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; u…

Kafka 如何保证消息消费的全局顺序性

哈喽大家好,我是咸鱼 今天我们继续来讲一讲 Kafka 当有消息被生产出来的时候,如果没有指定分区或者指定 key ,那么消费会按照【轮询】的方式均匀地分配到所有可用分区中,但不一定按照分区顺序来分配 我们知道,在 Kaf…

【操作宝典】SQL Server Management

目录 ⛳️【SQL Server Management】 ⛳️1. 启动登录 ⛳️2. 忘记密码 ⛳️3. 操作数据库和表 3.1 新建数据库text 3.2 新建表 3.3 编辑表 3.4 编写脚本 ⛳️【SQL Server Management】 ⛳️1. 启动登录 需要开启服务 ⛳️2. 忘记密码 登录windows--> 安全性 -->…

优雅使用docker-compose部署Skywalking

Skywalking使用docker-compose部署 version: 3.1 services: // 部署elasetic search 用于存储获取的应用信息与日志elasticsearch:image: elasticsearch:7.13.3container_name: elasticsearchprivileged: trueenvironment:- "cluster.nameelasticsearch" #设置集群名…

Vue3使用kkFileView预览文件pdf

kkFileView - 在线文件预览kkFileView官网 - kkFileView使用Spring Boot搭建,易上手和部署,基本支持主流办公文档的在线预览,如doc,docx,Excel,pdf,txt,zip,rar,图片等等https://kkfileview.keking.cn/zh-cn/docs/usage.html业务场景&#xf…

powershell获取微软o365 21v日志

0x00 背景 o365 21v为o365的大陆版本,主要给国内用户使用。微软提供了powershell工具和接口获取云上日志。微软o365国内的代理目前是世纪互联。本文介绍如何用powershell和配置证书拉取云上日志。 0x01 实践 第一步,ip权限开通: 由世纪互联…

生命科学领域 - 新药从研发到上市全流程

新药是指新研制的、临床尚未应用的药物,其化学本质应为新的化合物或称新化学实体、 新 分子实体、新活性实体。新药研发的根本目的是治疗疑难危重疾病,研制出来的药物即使是全新的化学结构,但是疗效或安全性却不及现有的药物便失去新药价值&a…

JAVAEE 初阶 多线程基础(三)

启动,中断,等待基础 一.如何创建线程二.启动线程 start()1.1 调用两次start方法1.2 创建新对象解决多个start问题2.1 经典面试题 run和start的区别 三.中止线程3.1 引入标志位3.2 高级写法3.3高级写法的错误之处3.4问题的解决3.5 将标志位放在main的局部变量中是否可行 四.等待…

Android 单元测试初体验(二)-断言

[TOC](Android 单元测试初体验(二)-断言) 前言 当初在学校学安卓的时候,老师敢教学进度,翻到单元测试这一章节的时候提了两句,没有把单元测试当重点讲,只是说我们工作中几乎不会用到,果真在之前的几年工作当中我真的没…

flutter布局详解及代码示例(上)

布局 基本布局 Row(水平布局):在水平(X轴)方向上排列子widget的列表。Column(垂直布局):在垂直(Y轴)方向上排列子widget的列表。Stack(可重叠布…

【九章斩题录】Leetcode:面试题 01.03. URL化(C/C++)

精品题解 🔥 《九章斩题录》 👈 猛戳订阅 面试题 01.03. URL化 📚 题目:URL化。编写一种方法,将字符串中的空格全部替换为%20。假定该字符串尾部有足够的空间存放新增字符,并且知道字符串的“真实”长度。…