数据结构——链式二叉树知识点以及链式二叉树数据操作函数详解!!

引言:该博客将会详细的讲解二叉树的三种遍历方法:前序、中序、后序,也同时会讲到关于二叉树的数据操作函数。值得一提的是,这些函数几乎都是建立在一个函数思想——递归之上的。这次的代码其实写起来十分简单,用不了几行就可以解决问题,但是其中的代码思想和运作方式才是难点。但只要我们搞懂了思想,手撕代码完全就没问题了,就让我们一起加油吧!

更多有关C语言和数据结构知识详解可前往个人主页:计信猫

目录

一,二叉树的遍历方式

0,三序前言

1,前序

2,中序

3,后序

二,二叉树的数据操作函数

1,二叉树节点个数

2,二叉树叶子节点个数

3,二叉树高度 

4,二叉树第k层节点个数

5,查找值为x的节点

6,二叉树的销毁

三,二叉树的层序遍历

1,  层序遍历

2,判断完全二叉树 

四,结语


一,二叉树的遍历方式

0,三序前言

        其实我们所说的三序其实就可以分为前序、中序、后序它们分别表示对一棵二叉树不同的遍历方式。我们在这里先对它们的遍历方式做一次总结:

●前序:根,左子树,右子树

●中序:左子树,根,右子树

●后序:左子树,右子树,根

        在这里,我们以如下的二叉树为例子进行三序的介绍:

        于是我们使用如下的代码在VS中直接手搓出上图的二叉树以方便我们后续对代码的检测

typedef int BTDataType;
//二叉树节点
typedef struct BinaryTreeNode
{
	BTDataType val;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;
//创建节点
BTNode* BuyNode(BTDataType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	newnode->val = x;
	newnode->left = NULL;
	newnode->right = NULL;
	return newnode;
}
//创建二叉树
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);
	//将节点按照图示连起来
	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;
	//node1为根节点
	return node1;
}

         并且,我们仍然使用三文件操作法,如下图所示:

1,前序

 ●前序:根,左子树,右子树

        那么对于该二叉树,我们应该如何遍历呢?

         按照前序遍历的要求,我们就可以将一棵二叉树不断地划分为根、左子树、右子树

        只有在我们将node1的左子树遍历完之后我们才可以进入右子树node1的左子树又可以被视作以node2,并且包含左右子树的一棵新树,所以我们又需要对新的树进行前序遍历。如此循环下去,其实递归的思想也就慢慢体现出来了。

        所以,以上的被遍历完之后的结果如下所示,其中N表示NULL空节点:

         所以前序遍历的代码如下:

//前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", root->val);
	PreOrder(root->left);
	PreOrder(root->right);
}

2,中序

●中序:左子树,根,右子树

        现在,我们以中序遍历法遍历如下二叉树:

        所以,我们还是将这棵二叉树不断地分解为根,左子树,右子树三个部分。但是我们这一次就会改变顺序,我们需要先遍历完左子树后再进行的遍历,最后才进行右子树的遍历。而以node1根节点左子树又可以继续被细分为以上三个部分,所以我们又需要进行中序遍历,直到某个节点的左子树被遍历完(为空),方可进行的遍历

        所以,以上的中序遍历完之后的结果如下:

        所以中序遍历的代码入下: 

//中序遍历
void MidOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	MidOrder(root->left);
	printf("%d ", root->val);
	MidOrder(root->right);
}

3,后序

●后序:左子树,右子树,根

        如果我们使用后序遍历法遍历如下二叉树,又会是怎样一个结果呢?

        所以我们仍然以之前学到的方式进行递归类推先遍历完node1的左子树,后遍历node1的右子树,最后在遍历根node1。而在遍历node1的子树时,它的子树又可以被视为以node2node4根节点新树,故我们又对其再进行后序遍历,直到遇到空节点

        所以我们模拟遍历之后的结果如下图所示:

        那么,后序遍历的代码入下:

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

二,链式二叉树的数据操作函数

1,二叉树节点个数

        当我们想要知道一棵二叉树中有多少个节点的时候,我们就可以使用这个函数。

//求二叉树中的节点个数
int TreeSize(BTNode* root);

        在这个函数中,我们还是使用到了递归的思想。我们可以将一棵总节点个数分为一个根节点与它的左右两子树的节点和。然后它的子树又可以再次使用这个方法将它的子树拆分,直到遇到NULL递归结束,返回值为0。所以我们的代码如下:

//求二叉树中的节点个数
int TreeSize(BTNode* root)
{
	//遇到空,递归结束开始返回
	if (root == NULL)
	{
		return 0;
	}
	//将二叉树拆分为根和左右两子树之和
	return TreeSize(root->right) + TreeSize(root->left) + 1;
}

2,二叉树叶子节点个数

        这个函数用于求的一棵二叉树之中的叶子节点的个数。

//求二叉树中叶子节点的个数
int TreeLeafSize(BTNode* root)

         首先,我们可以确定的是,当一个节点左右两子节点全为NULL时,那么这个节点就是叶节点而当我们想要找到一棵二叉树中的叶节点个数时,我们就可以将总的叶节点的个数拆分为左右两子树的叶节点之和。然后我们不停以以上方式进行拆分,直到找到叶节点递归结束,返回值为1。而要注意,空树叶节点个数为0,要特殊讨论。所以按照以上的思想进行代码实现如下:

//求二叉树中叶子节点的个数
int TreeLeafSize(BTNode* root)
{
	//空树没有叶节点
	if (root == NULL)
	{
		return 0;
	}
	//找到了叶节点,递归结束,返回1
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	//开始递归
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

3,二叉树高度 

        二叉树的高度其实就是二叉树的,也就是它的最大高度

//求二叉树的高度
int TreeHeight(BTNode* root);

        当我们想要找到一棵树的最大高度的时候,其实我们就可以将这棵的高度拆分为左子树与右子树中高度的较大值加上1(其中1代表了根也占一层高度)。如此递归下去,当我们递归叶节点时,就递归结束,返回值为1。当然最后我们也需要注意,空树的高度为0。所以我们的代码如下:

//求二叉树的高度
int TreeHeight(BTNode* root)
{
	//空树高度为0
	if (root == NULL)
	{
		return 0;
	}
	int left = TreeHeight(root->left);
	int right = TreeHeight(root->right);
	return left > right ? left + 1 : right + 1;
}

4,二叉树第k层节点个数

        该函数则专门用于求出一棵二叉树第k层节点个数

//求二叉树第k层的节点个数
int TreeLevelKSize(BTNode* root, int k);

        对于这个函数的实现,还是请出我们的老朋友——递归。对于一棵二叉树,求它第k层节点个数时,我们就可以将这个问题视为求其根节点的左右子树的(k-1)层的节点个数之和直到k持续减一,使k==1时,说明该层就是我们的第k层,此时递归结束,返回值为1就可以了。 所以我们的代码如下:

//求二叉树第k层的节点个数
int TreeLevelKSize(BTNode* root, int k)
{
	//空树则返回0
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	//根节点的左右子树的(k-1)层的节点个数之和
	return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);
}

5,查找值为x的节点

        此函数用于查找二叉树中值为x的节点,并返回该节点的地址。

//查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x);

        既然我们想要找到值为x节点时,那遍历这个二叉树就必不可少了!所以我们就可以使用之前学到的三序遍历之一的前序遍历法我们先遍历,若根节点不是x节点我们就对它的左子树进行遍历,最后再对右子树遍历。若二叉树为空或者不存在x节点我们都返回NULL。所以我们的代码方法如下:

//查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{
	//为空树就返回NULL
	if (root == NULL)
	{
		return NULL;
	}
	//先遍历根节点
	if (root->val == x)
	{
		return root;
	}
	//再遍历左子树
	BTNode* ret1 = TreeFind(root->left, x);
	if (ret1)//ret1不为空,说明x存在于左子树
	{
		return  ret1;
	}
	BTNode* ret2 = TreeFind(root->right, x);
	if (ret2)//ret2不为空,说明x存在于右子树
	{
		return ret2;
	}
	//整棵二叉树都不存在x节点,返回NULL
	return NULL;
}

6,二叉树的销毁

        当我们要结束对二叉树的操作时,我们就需要对二叉树进行销毁操作,防止内存泄漏。

//二叉树的销毁
void TreeDestroy(BTNode* root);

        在我们对二叉树进行遍历销毁时,我们就应该选择后序遍历,将根节点进行最后销毁 因为一旦将根节点先行销毁之后,那么我们就找不到根节点所对应的左右子树了,就无法进行后续销毁。所以代码如下:

//二叉树的销毁
void TreeDestroy(BTNode* root)
{
	if (root == NULL)
		//遇到空说明遍历结束,开始返回
	{
		return;
	}
	TreeDestroy(root->left);
	TreeDestroy(root->right);
	free(root);
}

三,链式二叉树的层序遍历

1,  层序遍历

        二叉树的层序遍历属于广度优先遍历(BFS),而二叉树的层序遍历其实就是对二叉树进行一层一层的遍历,完成二叉树的层序遍历需要用到我们之前学到的队列的知识。

        现在我们对队列进行一定的改装。以前我们所学到的队列中储存的为int类型的值,而这时候我们将int改变为二叉树节点的指针。经过以上操作之后,那么这个队列里所储存的就是二叉树节点的指针了。

typedef struct BinaryTreeNode* QDataType;
//二叉树的层序遍历
void TreeLevelOrder(BTNode* root);

         我们现在以以下的二叉树为例子:

        我们首先在队列中插入根节点

        然后我们取出根节点,并且同时在队列中加入其子节点2和4

        后我们取出节点2并且再次于队列中加入节点2对应的子节点(空则不进入队列)

        以此类推,直到当我们取出的节点为空时,那么此时对于这个二叉树的层序遍历就结束了。而在遍历的过程中,我们始终遵循一个思想,就是上层带下层。 所以在此思想的基础之上,我们便可以写出层序遍历的代码:

//二叉树的层序遍历
void TreeLevelOrder(BTNode* root)
{
	assert(root);
	//创建一个队列
	Queue q;
	//初始化队列
	QueueInit(&q);
	//向队列中插入第一个元素
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		//取出队首节点并且打印
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%d ", front->val);
		//加入队首节点的非空子节点
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if (front->right)
		{
			QueuePush(&q, front->right);
		}
	}
}

2,判断完全二叉树 

        当我们需要判断一棵树是否为完全二叉树时我们就可以使用该函数,它的返回值为布尔类型

//完全二叉树的判断
bool TreeComplete(BTNode* root);

        在写该函数时,我们则会用到以下的思路:

1,层序遍历这个二叉树,并且空指针也进入队列

2,取出队首节点遇到第一个空节点时,就开始进行判断,如果队列里面节点全为空就为完全二叉树,后面有非空节点就不为完全二叉树

        让我们结合下列的例子进行理解:

        我们以之前我们学到的层序遍历法来遍历这个二叉树,如下图所示:

        现在就是我们取出第一个空节点的时候,此时我们就对队列里的元素进行判断,如果队列里边全为空指针,那么这个二叉树就为完全二叉树,否则就不为完全二叉树。而由我们的例子可见,该二叉树就为完全二叉树。 

        所以我们就可以将此方法转变为如下的代码:

//完全二叉树的判断
bool TreeComplete(BTNode* root)
{
	assert(root);
	//创建一个队列
	Queue q;
	//初始化队列
	QueueInit(&q);
	//向队列中插入第一个元素
	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 != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}

四,结语

        其实到了这里,关于二叉树的基本知识我们就学完了。在二叉树中,递归知识就十分的重要,也比较烧脑,所以在学习这部分知识的时候我们就需要多画图进行理解

        后续我们也将慢慢进入排序的学习,一起加油吧!

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

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

相关文章

【Springboot系列】SpringBoot 中的日志如何工作的,看完这一篇就够了

文章目录 强烈推荐引言Spring Boot 中的日志是怎么工作日志框架选择配置文件日志级别自定义日志配置集成第三方日志库实时监控和日志管理 Log4j2工作原理分析1. 核心组件2. 配置文件3. Logger的继承和层次结构4. 日志事件处理流程5. 异步日志 总结强烈推荐专栏集锦写在最后 强烈…

免费图片文字转换成文本,ocr文字识别软件免费版,真的太实用了!

截屏短视频上一段扎心文字,想把它发到朋友圈怎么办?这时候就需要一个OCR识别软件。 它就像一个聪明的小助手,它可以帮助电脑“看懂”书本上或者图片里的字。就像我们用眼睛看字一样,OCR软件用它的“眼睛”扫描图片,识…

C语言代码错误(一)

今天在写选择排序代码时&#xff0c;在测试数据发现不能显示结果 1、代码如下&#xff1a; #include <stdio.h>int main(void) {int i, j; // 循环变量int MinIndex; // 保存最小的值的下标int buf; // 互换数据时的临时变量int n;printf("你想输入多少个数据n:\n…

乐理学习-音及音名

1. 我觉得练习题很重要。我要得到一个反馈 所以我想没学习完书中的一节就要把练习题做下来&#xff0c;虽然慢点也可以。 2. 做个小计划。 今天计算了一下学完《基本乐理-李重光》如果每天3张。也要80天干完&#xff0c;希望能有一天可以学习7张的速度的时候。 3. 练习记录…

【除自身以外数组的乘积】python

目录 思路&#xff1a; 代码&#xff1a; 思路&#xff1a; 直接计算前缀乘积&#xff0c;后缀乘积&#xff0c;然后相乘即可 开始我还在想&#xff0c;遍历一次i&#xff0c;怎么能同时计算前缀乘积和后缀乘积&#xff0c;事实上分开计算比较方便。。 代码&#xff1a; cl…

Spark运行模式详解

Spark概述 Spark 可以在多种不同的运行模式下执行&#xff0c;每种模式都有其自身的特点和适用场景。 部署Spark集群大体上分为两种模式&#xff1a;单机模式与集群模式。大多数分布式框架都支持单机模式&#xff0c;方便开发者调试框架的运行环境。但是在生产环境中&#xff…

聊聊变异测试

软件质量保障 所寫即所思&#xff5c;一个阿里质量人对测试的所感所悟。 1. 介绍 有句话说&#xff1a;证实容易&#xff0c;证伪难。正如测试一样&#xff0c;证明缺陷存在容易&#xff0c;但证明不存在缺陷难。而变异测试颠覆了这一原则&#xff0c;如果我们知道存在缺陷&am…

开发依赖与运行依赖

1. 概念 开发依赖&#xff1a;devDependencies 运行依赖&#xff1a;dependencies 2. 理解 &#xff08;1&#xff09;devDependencies 在线上状态不需要使用的依赖&#xff0c;就是开发依赖。为什么 npm 要把它单独分拆出来呢&#xff1f;最终目的是为了减少 node_modul…

ElasticSearch插件版本与ES版本不对应的解决方案

一、背景 最近需要给es安装ik、hanlp分词器和ingest-attachment管道&#xff0c;服务器已有的es版本为8.5.3&#xff08;似乎太新了&#xff09;&#xff0c;hanlp和ingest-attachment都没有这么高的版本&#xff0c;因此只能下载相对老的版本&#xff0c;然后自己修改配置文件…

Linux定时计划

定时计划 一、计划任务种类 突发性&#xff1a;临时决定只执行一次的任务 at&#xff1a;处理执行一次任务就结束定时性&#xff1a;每隔一定时间需要重复执行此命令 crontab&#xff1a;指定任务&#xff0c;按照设定的周期一直循环执行二、作用 定时任务可以用于自动备份…

Java Swing + MySQL图书借阅管理系统

系列文章目录 Java Swing MySQL 图书管理系统 Java Swing MySQL 图书借阅管理系统 文章目录 系列文章目录前言一、项目展示二、部分代码1.Book2.BookDao3.DBUtil4.BookAddInternalFrame5.Login 三、配置 前言 项目是使用Java swing开发&#xff0c;界面设计比较简洁、适合作…

冯喜运:5.27黄金暴跌大阴后出现“暂定符”今日黄金原油操作策略

【黄金消息面分析】&#xff1a;金价虽然有大阴线暴跌&#xff0c;但依然属于超买后的调整而非熊市&#xff0c;对中长线投资者来说只是市场洗牌。因此&#xff0c;在出现企稳迹象之后&#xff0c;随时关注反弹时机的启动。未来几日&#xff0c;黄金空头可能在进一步发力之前需…

【机器学习结合AI绘画工具】——开启艺术创作的新纪元

目录 一、AI绘画工具的发展历程 二、AI绘画工具的技术原理 实例说明 三、AI绘画工具在艺术创作中的应用 实例网站 四、AI绘画工具的影响与未来展望 结论 机器学习和人工智能&#xff08;AI&#xff09;在过去的十年里取得了显著的进展。特别是在艺术创作领域&#xff0c…

Qt 对话框或者QMainWindow等类中调用自定义QWidget继承组件

简单的方法如下所示 1、创建一个ui文件&#xff0c;界面布局放入QVBoxLayout或者QHBoxLayout 使用他来放入自定义组件&#xff0c;类似如下 2、代码如下&#xff1a; ui.setupUi(this); { //自定义组价如下 KwTable *Table new KwTable(this); ui.vertical…

Java中的类加载器

类加载器 1.什么是类加载器&#xff1f; 启动类加载器&#xff08;Bootstrap ClassLoader&#xff09;&#xff1a;这是JVM自带的类加载器&#xff0c;负责加载Java的核心类库&#xff0c;如rt.jar等。由于安全原因&#xff0c;启动类加载器加载的类不能被其他类加载器加载的类…

python 两个表格字段列名称值,对比字段差异

支持xlsx,xls文件&#xff0c;相互对比字段列 输出两个表格文件相同字段&#xff0c;置底色为绿色 存在差异的不同字段&#xff0c;输出两个新的表格文件&#xff0c;差异字段&#xff0c;置底色为红色 import pandas as pd from openpyxl import load_workbook from openpy…

【云原生】Kubernetes-----POD资源限制与探针机制

目录 引言 一、资源限制 &#xff08;一&#xff09;基本定义 &#xff08;二&#xff09;资源单位 1.CPU资源 2.内存资源 &#xff08;三&#xff09;请求与限制 &#xff08;四&#xff09;定义方式 1.编写yaml文件 2.查看资源情况 &#xff08;五&#xff09;资源…

Gradient-checkpointing的原理

原文&#xff1a; 将更大的网络安装到内存中。|by 雅罗斯拉夫布拉托夫 |张量流 |中等 (medium.com) 前向传播时&#xff0c;隔几层就保留一层activation数据&#xff0c;其余层的activation都释放掉&#xff1b; 反向传播时&#xff0c;从最近的checkpoint去重新跑forward&…

Cohere继Command-R+之后发布大模型Aya-23,性能超越 Gemma、Mistral 等,支持中文

前言 近年来&#xff0c;多语言大模型&#xff08;MLLM&#xff09;发展迅速&#xff0c;但大多数模型的性能依然存在显著差距&#xff0c;尤其是在非英语语言方面表现不佳。为了推动多语言自然语言处理技术的发展&#xff0c;Cohere团队发布了新的多语言指令微调模型家族——…

cpolar内网穿透工具—无需部署,远程访问网址

文章目录 cpolar介绍安装教程隧道管理VIP客户cpolar介绍 cpolar是一种安全的内网穿透服务,它将局域网下面的本地服务器通过加密隧道暴露至公网,使得公网用户可以正常访问内网服务。 只需一行命令,就可以将内网站点发布至公网,方便给客户演示。高效调试微信公众号、小程序…