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

news2024/11/17 2:36:27

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

更多有关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;
}

四,结语

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

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

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

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

相关文章

【C++】二分查找:在排序数组中查找元素的第一个和最后一个位置

1.题目 难点&#xff1a;要求时间复杂度度为O(logn)。 2.算法思路 需要找到左边界和右边界就可以解决问题。 题目中的数组具有“二段性”&#xff0c;所以可以通过二分查找的思想进行解题。 代码&#xff1a; class Solution { public:vector<int> searchRange(vect…

【传知代码】无监督动画中关节动画的运动表示-论文复现

文章目录 概述动画技术的演进原理介绍核心逻辑环境配置/部署方式小结 本文涉及的源码可从无监督动画中关节动画的运动表示该文章下方附件获取 概述 该文探讨了动画在教育和娱乐中的作用&#xff0c;以及通过数据驱动方法简化动画制作的尝试。近期研究通过无监督运动转移减少对…

【数据结构与算法 经典例题】判断链表是否带环

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;数据结构与算法刷题系列&#xff08;C语言&#xff09; 期待您的关注 目录

数据结构和算法|排序算法系列(三)|插入排序(三路排序函数std::sort)

首先需要你对排序算法的评价维度和一个理想排序算法应该是什么样的有一个基本的认知&#xff1a; 《Hello算法之排序算法》 主要内容来自&#xff1a;Hello算法11.4 插入排序 插入排序的整个过程与手动整理一副牌非常相似。 我们在未排序区间选择一个基准元素&#xff0c;将…

社交媒体数据恢复:聊天宝

请注意&#xff0c;本教程仅针对聊天宝应用程序&#xff0c;而非其他聊天软件。以下是详细的步骤&#xff1a; 首先&#xff0c;请确保您已经登录了聊天宝应用程序。如果您尚未登录&#xff0c;请使用您的账号登录。 在聊天宝主界面&#xff0c;找到您希望恢复聊天记录的对话框…

深度学习复盘与小实现

文章目录 一、查漏补缺复盘1、python中zip()用法2、Tensor和tensor的区别3、计算图中的迭代取数4、nn.Modlue及nn.Linear 源码理解5、知识杂项思考列表6、KL散度初步理解 二、处理多维特征的输入1、逻辑回归模型流程2、Mini-Batch (N samples) 三、加载数据集1、Python 魔法方法…

YOLOv8_pose预测流程-原理解析[关键点检测理论篇]

YOLOv8_seg的网络结构图在博客YOLOv8网络结构介绍_CSDN博客已经更新了,由网络结构图可以看到相对于目标检测网络,实例分割网络只是在Head层不相同,如下图所示,在每个特征层中增加了KeyPoint分支(浅绿色),通过两个卷积组和一个Conv卷积得到得到通道数为51的特征图,51表示…

python内置函数map/filter/reduce详解

在Python中&#xff0c;map(), filter(), 和 reduce() 是内置的高级函数(实际是class)&#xff0c;用于处理可迭代对象&#xff08;如列表、元组等&#xff09;的元素。这些函数通常与lambda函数一起使用&#xff0c;以简洁地表达常见的操作。下面我将分别解释这三个函数。 1. …

【C++课程学习】:命名空间的理解(图文详解)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 &#x1f4f7;1.命名冲突 &#x1f4f7;2.重定义 &#x1f4f7;3.命名空间 &#x1f37a;命名空间可…

山东大学软件学院数据库实验1-9(全部)

目录 前言 实验代码 实验一 1-1 1-2 1-3 1-4 1-5 1-6 实验二 2-1 2-2 2-3 2-4 2-5 2-6 2-7 2-8 2-9 2-10 实验三 3-1 3-2 3-3 3-4 3-5 3-6 3-7 3-8 3-9 3-10 实验四 4-1 4-2 4-3 4-4 4-5 4-6 4-7 4-8 4-9 4-10 实验五 5-1…

CSRF跨站请求伪造实战

目录 一、定义 二、与XSS的区别 三、攻击要点 四、实战 一、定义 CSRF (Cross-site request forgery&#xff0c;跨站请求伪造)&#xff0c;攻击者利用服务器对用户的信任&#xff0c;从而欺骗受害者去服务器上执行受害者不知情的请求。在CSRF的攻击场景中&#xff0c;攻击…

移动云以深度融合之服务,令“大”智慧贯穿云端

移动云助力大模型&#xff0c;开拓创新领未来。 云计算——AI模型的推动器。 当前人工智能技术发展的现状和趋势&#xff0c;以及中国在人工智能领域的发展策略和成就。确实&#xff0c;以 ChatGPT 为代表的大型语言模型在自然语言处理、文本生成、对话系统等领域取得了显著的…

等价关系、偏序关系与哈斯图

一、等价关系的定义 设R是集合A上的一个二元关系&#xff0c;如果R满足自反性、对称性和传递性&#xff0c;则称R是一个等价关系。 二、等价类和商集 哪些元素有关系&#xff0c;就构成一个等价类。 所有等价类构成的集合就是商集。 集合的划分&#xff1a;就是对集合中的元…

Liunx系统中修改文件的创建时间以及访问时间

在Linux系统中&#xff0c;可以使用touch命令来修改文件的时间戳。以下是一些常用的touch命令选项&#xff1a; &#xff08;其实在MacOS中也适用&#xff09; 修改访问时间&#xff08;Access Time&#xff09;和修改时间&#xff08;Modification Time&#xff09;&#xf…

【代码随想录】动态规划经典题

前言 更详细的在大佬的代码随想录 (programmercarl.com) 本系列仅是简洁版笔记&#xff0c;为了之后方便观看 做题步骤 含义公式初始化顺序检查 确定dp数组以及下标的含义递推公式dp数组如何初始化遍历顺序打印dp数组&#xff08;看哪里有问题&#xff09; 斐波那契数 c…

百亿级流量红包系统,如何做架构?(字节面试真题)

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的架构类/设计类的场景题&#xff1a; 1.如何设计高并发红包系统 &#xff0…

Linux 编译器gcc/g++使用

gcc/g同理 编译器运行过程 1. 预处理&#xff08;进行宏替换) gcc -E a.c -o a.i 预处理后还是c语言 -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面 告诉gcc&#xff0c;从现在开始进行程序的翻译&#xff0c;将预处理工作做完停下 2. 编译&#x…

电缆厂可视化:提升生产透明度与运营效率

图扑电缆厂可视化系统通过实时监控和数据分析&#xff0c;提高生产过程的透明度和可控性&#xff0c;优化资源配置和质量管理&#xff0c;显著提升运营效率和产品质量。

力扣739. 每日温度

Problem: 739. 每日温度 文章目录 题目描述思路复杂度Code 题目描述 思路 若本题目使用暴力法则会超时&#xff0c;故而使用单调栈解决&#xff1a; 1.创建结果数组res&#xff0c;和单调栈stack&#xff1b; 2.循环遍历数组temperatures&#xff1a; 2.1.若当stack不为空同时…

如何使用ssh将vscode 连接到服务器上,手把手指导

一、背景 我们在开发时&#xff0c;经常是window上安装一个vscode编辑器&#xff0c;去连接一个虚拟机上的linux&#xff0c;这里常用的是SSH协议&#xff0c;了解其中的操作非常必要。 二、SSH协议 SSH&#xff08;Secure Shell&#xff09;是一种安全协议&#xff0c;用于…