「数据结构」二叉树2

news2025/1/17 21:52:52

🎇个人主页:Ice_Sugar_7
🎇所属专栏:初阶数据结构
🎇欢迎点赞收藏加关注哦!

文章目录

  • 🍉前言
  • 🍉链式结构
  • 🍉遍历二叉树
    • 🍌前序遍历
    • 🍌中序遍历
    • 🍌后序遍历
  • 🍉计数
    • 🍌求结点数
    • 🍌求叶子结点数
    • 🍌求第k层结点数
  • 🍉树的深度
  • 🍉查找结点
  • 🍉构建二叉树
  • 🍉销毁二叉树
  • 🍉层序遍历
  • 🍉判断是否为完全二叉树
    • 🍌补充
  • 🍉写在最后

🍉前言

在上一篇文章中我们讲了二叉树的顺序结构,但是普通二叉树因为结点不连续,没法使用顺序结构,这就需要使用链式结构进行存储。本文将讲解二叉树的链式结构及常见函数的实现


🍉链式结构

一个结点分为三个部分:存放当前结点的数值的数据域、分别指向左、右子树的指针

typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;

🍉遍历二叉树

二叉树有三种遍历方式,通过递归实现
需要根据使用场景选择不同的遍历方式

🍌前序遍历

先访问根结点,接着是左子树,最后访问右子树(这里的“访问”指的是把结点的数值打印出来)
采用递归,那就要将大问题拆分为小问题,直到问题无法再继续拆分
●现在要访问左子树,那可以把问题拆解为“依次访问它的根结点、左子树、右子树”,拆解若干次之后,会抵达叶子结点,再往下就是空结点了,此时没法继续拆解问题,也就是到达递归的终点了,需要回归
●右子树也同理

void BinaryTreePrevOrder(BTNode* root) {
	if (!root) {  
		cout << "NULL" << " ";  //为了方便观察,所以把NULL也打印出来
		return;
	}
	cout << root->_data << " ";
	BinaryTreePrevOrder(root->_left);
	BinaryTreePrevOrder(root->_right);
}

有了前序遍历作为参照,那中后序遍历也就很轻松就能写出来了,只要调整一下打印语句的位置就ok了,下面直接上代码

🍌中序遍历

void BinaryTreePrevOrder(BTNode* root) {
	if (!root) {  
		cout << "NULL" << " ";  //为了方便观察,所以把NULL也打印出来
		return;
	}
	BinaryTreePrevOrder(root->_left);
	cout << root->_data << " ";
	BinaryTreePrevOrder(root->_right);
}

🍌后序遍历

void BinaryTreePrevOrder(BTNode* root) {
	if (!root) {  
		cout << "NULL" << " ";  //为了方便观察,所以把NULL也打印出来
		return;
	}
	BinaryTreePrevOrder(root->_left);
	BinaryTreePrevOrder(root->_right);
	cout << root->_data << " ";
}

🍉计数

🍌求结点数

求结点数,可以转化为左子树结点数+右子树结点数+1(根结点本身),如果遇到空结点,那就返回0

int BinaryTreeSize(BTNode* root) {
	if (!root)
		return 0;
	return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}

🍌求叶子结点数

和求结点数差不多,不过多了一个判断是否为叶子结点的语句,如果是,就返回1

//左右结点都为空返回1   
int BinaryTreeLeafSize(BTNode* root) {
	if (!root)
		return 0;
	if (root->_left == NULL && root->_right == NULL)
		return 1;
	//不为空or只有一个子树为空
	return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}

🍌求第k层结点数

这个的递归不太好找。
方法如下:
假设k为5,那么第k层相对于第一层就是第五层,而它相对第二层则是第四层,相对第三层是第三层……相对第五层就是第一层。也就是说要求第k层,实际上是求“第一层”的结点个数
(有点像求一个数的阶乘时用到的递归思想)

int BinaryTreeLevelKSize(BTNode* root, int k) {
	assert(k > 0);  //确保k为正数
	if (!root)
		return 0;
	if (k == 1)  //此时已经递归到了第k层
		return 1;
	return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1);
}

🍉树的深度

深度是指从根结点到叶子结点的最长距离。这个问题可以拆解为求第二层的根结点到叶子结点的最长距离+1,再拆为第三层到叶子结点的最长距离+2……最后遇到空结点就可以停下来了
最后返回左子树和右子树二者深度的较大值

代码如下:

int BinaryTreeDepth(BTNode* root) {
	if (!root)
		return 0;
	int LeftSize = BinaryTreeDepth(root->_left);
	int RightSize = BinaryTreeDepth(root->_right);
	return LeftSize > RightSize ? LeftSize + 1 : RightSize + 1;
}

🍉查找结点

要在二叉树里面查找值为x的结点。每次递归先找左子树,找到就返回,找不到就去找右子树。
下面两个条件满足其一,递归终止:①到空结点时返回NULL;②找到值为x的结点就返回该结点

代码如下:

BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {
	if (!root)
		return NULL;
	if (root->_data == x)
		return root;
	BTNode* ret = BinaryTreeFind(root->_left, x);
	if (ret)  //如果左子树找到指定结点,就不用找右子树了
		return ret;
	return BinaryTreeFind(root->_right, x);
}

🍉构建二叉树

现在已知一个数组,它是某二叉树前序遍历的结果,该数组会用#表示空结点,现在要我们通过这个数组来构建原二叉树
●通过递归来构建。每次递归时数组当前位置的元素如果不为#,就创建一个结点,然后将它和双亲结点连起来。
●既然要让结点间能够连接起来,那函数就要返回结点或者NULL。这样在递归的过程中可以将新创建的结点或者NULL和双亲结点连接起来
●递归的停止条件就是遇到#,此时返回NULL,

BTNode* BinaryTreeCreate(BTDataType* a, int n, int pi) {  //n:数组大小  pi:指向数组下标
	if (a[pi] == '#') {
		++pi;
		return NULL;
	}
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	node->_data = a[pi++];  //数组赋值给node之后记得++
	node->_left = BinaryTreeCreate(a, n, pi); //连接左子树
	node->_right = BinaryTreeCreate(a, n, pi); //连接右子树
	return node;  //返回根结点
}

这里要说一下这个pi因为我们使用递归而非迭代,需要知道数组下标(即递归到哪个元素了),所以要传下标


🍉销毁二叉树

要采用后序遍历销毁结点,如果采用前序或中序,根结点都不是最后销毁的,这样会导致无法找到某一边的子树。比如采用中序,销毁左子树后把根结点给销毁了,那就没法找到右子树了

void BinaryTreeDestory(BTNode** root) {
	assert(root);
	if (*root == NULL)
		return;
	BinaryTreeDestory(&(*root)->_left);
	BinaryTreeDestory(&(*root)->_right);
	free(*root);
	*root = NULL;
}

🍉层序遍历

前面讲的前、中、后序遍历都属于深度优先遍历,以前序遍历为例,会先递推到最左下的结点,然后才回归,这是一个纵向的过程。
而层序遍历又称为广度优先遍历,顾名思义,就是一层一层遍历。这种遍历需要使用队列
具体的过程为:先让根结点入队,标记为队首front,然后将它的左右子树根结点入队(前提是结点不为空,如果为空那就不用入),再让队头的根结点出队,子树的根结点成为新的队头。
重复上面这个过程,直到队列为空

举个栗子:
在这里插入图片描述
代码如下:

void BinaryTreeLevelOrder(BTNode* root) {
	if (!root)
		return;
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);

	while (!QueueEmpty(&q)) {
		QDataType front = QueueFront(&q); //队首元素
		cout << QueueFront(&q)->_data << endl;
		QueuePop(&q);  //队首元素出队  然后要让它的左右子树入队
		if (root->_left)
			QueuePush(&q, front->_left);
		if (root->_left)
			QueuePush(&q, front->_right);
	}
	QueueDestroy(&q);
}


🍉判断是否为完全二叉树

完全二叉树的特点就是结点连续,根据这个性质,我们采用层序遍历,不过这次层序遍历需要把空结点也入队。如果遇到空结点时后面的结点也全为空,那就是完全二叉树;反之,如果后面还有非空结点,那就不是
简而言之,我们要判断front为空结点时队列剩下的元素是否全为空

代码如下:

bool BinaryTreeComplete(BTNode* root) {
	if (!root)
		return true;
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	BTNode* front = root;
	while (!QueueEmpty(&q)){
		front = QueueFront(&q); 
		if (front == NULL)  //队首是空结点,就是遇到的第一个空结点,跳出循环
			break;
		//空结点也要入队
		QueuePush(&q, front->_left);
		QueuePush(&q, front->_right);
		QueuePop(&q);  //队首元素出队
	}
	//到这里的时候说明已经遇到空结点
	//接下来要排查剩下的结点,看看是否有非空结点
	while (!QueueEmpty(&q)) {
		front = QueueFront(&q);
		QueuePop(&q);
		if (front) {  //若遇到不为空的结点,说明不是完全二叉树
			QueueDestroy(&q);
			return false;
		}
	}
	//到这里说明全部都是空结点,那就是完全二叉树了
	QueueDestroy(&q);
	return true;
}

🍌补充

	while (!QueueEmpty(&q)) {
		front = QueueFront(&q);
		QueuePop(&q);
		if (front) {  
			QueueDestroy(&q);
			return false;
		}
	}

我们已经将队首的结点pop掉,那后面的if语句还能否访问front?
答案是可以。因为front保存队首结点的值,而这个值是二叉树结点的地址,即指向二叉树的结点。(刚才上面那个图是为了方便理解,所以才把front的箭头指向队首)pop掉队首结点并不会影响树的结点,所以还是可以访问的


🍉写在最后

以上就是本篇文章的全部内容,如果你觉得本文对你有所帮助的话,那不妨点个小小的赞哦!(比心)

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

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

相关文章

年末汇总⭐️ 我是如何从学生切换到职场人身份的

目录 2023.12.23 天气阴 温度较低 一、Learning 二、Working 三、Living 章末 2023.12.23 天气阴 温度较低 小伙伴们大家好&#xff0c;冬已至 年将末 身为逮虾户的我看到大家的年末总结心中也不由得涌起一股创作热情&#xff0c;奈何没文化&#x…

qt简单连接摄像头

要使用摄像头&#xff0c;就需要链接多媒体模块以及多媒体工具模块 需要在.pro文件中添加QT multimedia multimediawidgets 是用的库文件 QCamera 类用于打开系统的摄像头设备&#xff0c; QCameraViewfinder 用于显示捕获的视频&#xff0c; QCameraImageCapt…

tcp vegas 为什么好

我吹捧 bbr 时曾论证过它在和 buffer 拧巴的时候表现如何优秀&#xff0c;但这一次说 vegas 时&#xff0c;我说的是从拥塞控制这个问题本身看来&#xff0c;vegas 为什么好&#xff0c;并且正确。 接着昨天 tcp vegas 鉴赏 继续扯。 假设一群共享带宽的流量中有流量退出或有…

每日一题:给定一个字符串s,请你找出其中不含有重复字符得最长子串的长度

每日一题&#xff1a;给定一个字符串s&#xff0c;请你找出其中不含有重复字符得最长子串的长度 function getLongSubstring(s){let map new Map();let max 0;let left 0;for(let i0;i<s.length;i){if(map.has(s[i]) && map.get(s[i])>left){left map.get(s…

D3839|完全背包

完全背包&#xff1a; 首先01背包的滚动数组中的解法是内嵌的循环是从大到小遍历&#xff0c;为了保证每个物品仅被添加一次。 for(int i 0; i < weight.size(); i) { // 遍历物品for(int j bagWeight; j > weight[i]; j--) { // 遍历背包容量dp[j] max(dp[j], dp[j…

在线渗透盒子,集成了近百个常见的渗透渗透工具,类似软件商城的工具可以进行工具下载

现在简单统计了一下大概有80个渗透工具左右&#xff0c;3个运行环境&#xff0c;1个破解工具 工具包介绍 该工具是一个类似软件商城的工具可以进行工具下载&#xff0c;工具的卸载&#xff0c;工具的更新&#xff0c;工具编写了自动化的安装脚本&#xff0c;不用担心工具跑不起…

oracle即时客户端(Instant Client)安装与配置

之前的文章记录了oracle客户端和服务端的下载与安装&#xff0c;内容参见&#xff1a; 在Windows中安装Oracle_windows安装oracle 如果不想安装oracle客户端&#xff08;或者是电脑因为某些原因无法安装oracle客户端&#xff09;&#xff0c;还想能够连接oracle远程服务&#…

【数据结构】递归与分治

一.递归 1.递归的概念&#xff1a; 子程序&#xff08;或函数&#xff09;. 接调用自己或通过一系列调用语句间接调用自己&#xff0c;成为递归。 递归是一种描述问题和解决问题的基本方法。 重复地把问题转化为与原问题相似的新问题&#xff0c;直到问题解决为止。 2.递归…

要参加微软官方 Copilot 智能编程训练营了

GitHub Copilot 是由 GitHub、OpenAI 和 Microsoft 联合开发的生成式 AI 模型驱动的。 GitHub Copilot 分析用户正在编辑的文件及相关文件的上下文&#xff0c;并在编写代码时提供自动补全式的建议。 刚好下周要参加微软官方组织的 GitHub Copilot 工作坊-智能编程训练营&…

Blender插件-The Grove 10 树木生长动画植物插件

注意&#xff1a;Blender和The Grove的版本匹配。 亲测Blender 2.9与The Grove 10可以配合使用&#xff0c;Blender 3.6会报错&#xff0c;具体看报错记录。 一、下载 CG咖官网地址&#xff1a; Blender插件-树木生长插件植物生成插件 The Grove 10插件资产库 CSDN下载地址…

AI技术图像编辑 Luminar Neo最新中文 for Mac

Luminar Neo是一款功能强大的AI智能图像处理工具&#xff0c;借助Luminar Neo领先的AI技术和灵活的工作流程&#xff0c;用户可以完成创意任务并获得专业品质的编辑结果。以下是该软件的主要特点和功能&#xff1a; 支持多种文件格式&#xff1a;Luminar Neo支持多种文件格式&…

Prometheus介绍和安装

Prometheus介绍和安装 1. Prometheus介绍 Prometheus&#xff08;普罗米修斯&#xff09;是一个最初在SoundCloud上构建的监控系统。自2012年成为社区开源项目&#xff0c;拥有非常活跃的开发人员和用户社区。为强调开源及独立维护&#xff0c;Prometheus于2016年加入云原生云…

N字形变换(麻烦的方法)

class Solution:def convert(self, s: str, numRows: int) -> str:#先判断z有多少隔开s_new""index_now0if len(s)<numRows or numRows1:return sfor i in range(numRows-1,-1,-1):exchange0index_exchangeindex_nows_news[index_now]#计算每一层的差距gap_but…

Git安装和使用教程,并以gitee为例实现远程连接远程仓库

文章目录 1、Git简介及安装2、使用方法2.1、Git的启动与配置2.2、基本操作2.2.1、搭建自己的workspace2.2.2、git add2.2.3、git commit2.2.4、忽略某些文件不予提交2.2.5、以gitee为例实现git连接gitee远程仓库来托管代码 1、Git简介及安装 版本控制&#xff08;Revision cont…

超好用的工具类:并发集合简介--线程安全的HashMap

超好用的工具类&#xff1a;并发集合简介 ConcurrentHashMap,这是一个搞笑的并非HashMap。你可以把它理解为一个线程安全的HashMap. CopyOnWriteArrayList,这是一个List,从名字看就知道它和ArrayList是一族的。 在读多写少的场合&#xff0c;这个List的性能非常号&#xff0c;远…

[足式机器人]Part4 南科大高等机器人控制课 CH10 Bascis of Stability Analysis

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;CLEAR_LAB 笔者带更新-运动学 课程主讲教师&#xff1a; Prof. Wei Zhang 南科大高等机器人控制课 Ch10 Bascis of Stability Analysis 1. Background1.1 What is Stability Analysis1.2 General ODE Models for Dynamic…

小程序怎么实现会员运营?

充分利用小程序快捷方便的优点&#xff0c;小程序可以融合会员机制的获客、激活、留存、转化几个大的方面。小程序会员机制其实是会员系统的获客、激活、留存和转化的机制&#xff0c;而小程序则是为会员提供注册、服务、购买的场所&#xff0c;是商家连接会员、消费者的重要途…

每日一题——轮转数组

1. 题目描述 给定一个整数数组nums&#xff0c;将数组中的元素向右轮转k个位置&#xff0c;其中k是非负数。 示例1: 输入&#xff1a;nums [1,2,3,4,5,6,7]&#xff0c;k 3 输出&#xff1a;[5,6,7,1,2,3,4] 解释&#xff1a; 向右轮转 1步&#xff1a;[7,1,2,3,4,5,6] 向右…

文字编辑软件,批量给多个文本添加文档内容

在当今信息爆炸的时代&#xff0c;文字编辑工作是很多人需要面对的&#xff0c;而怎么快速的完成编辑工作&#xff0c;则是很多人所思考解决的。现在有一款很好用的软件——首助编辑高手&#xff0c;可以批量对多个文本文档内容进行处理&#xff0c;能帮你在文字编辑的工作上节…

安装vcpkg管理opencv的安装+MFC缺失的解决

第一步&#xff0c;出现#include没有办法找到opencv头文件的问题&#xff0c;无法解决 在VC的提示下&#xff0c;安装了vcpkg&#xff0c;然后用vcpkg命令来帮助安装opencv&#xff0c;过程十分顺利。 1. cmd 到命令行窗口&#xff1b; 2. 建立src文件夹&#xff0c;并进入…