「数据结构」二叉树2

🎇个人主页: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掉队首结点并不会影响树的结点,所以还是可以访问的


🍉写在最后

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

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

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

相关文章

PromptNER: Prompt Locating and Typing for Named Entity Recognition

原文链接&#xff1a; https://aclanthology.org/2023.acl-long.698.pdf ACL 2023 介绍 问题 目前将prompt方法应用在ner中主要有两种方法&#xff1a;对枚举的span类型进行预测&#xff0c;或者通过构建特殊的prompt来对实体进行定位。但作者认为这些方法存在以下问题&#xf…

Python入门学习篇(五)——列表字典

1 列表 1.1 定义 ①有序可重复的元素集合 ②可以存放不同类型的数据 ③个人理解:类似于java中的数组1.2 相关方法 1.2.1 获取列表长度 a 语法 len(列表名)b 示例代码 list2 [1, 2, "hello", 4] print(len(list2))c 运行结果 1.2.2 获取列表值 a 语法 列表名…

渗透实验 XSS和SQL注入(Lab3.0)

windows server2003IIS搭建 配置2003的虚拟机 1、利用AWVS扫描留言簿网站&#xff08;安装见参考文档0.AWVS安装与使用.docx&#xff09;&#xff0c;发现其存在XSS漏洞&#xff0c;截图。 2、 Kali使用beef生成恶意代码 cd /usr/share/beef-xss./beef执行上面两条命令 …

echarts显示N条折线图DEMO

1、代码 <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>Echarts折线图</title> </head> <body> <div id"main" style"width: 600px;height:400px;"></div> <sc…

Qt/C++视频监控Onvif工具/组播搜索/显示监控画面/图片参数调节/OSD管理/祖传原创

一、前言 能够写出简单易用而又不失功能强大的组件&#xff0c;一直是我的追求&#xff0c;简单主要体现在易用性&#xff0c;不能搞一些繁琐的流程和一些极难使用的API接口&#xff0c;或者一些看不懂的很难以理解的函数名称&#xff0c;一定是要越简单越好。功能强大主要体现…

在做题中学习(39):盛最多水的容器

11. 盛最多水的容器 - 力扣&#xff08;LeetCode&#xff09; 解释&#xff1a;因为木桶原理&#xff0c;能否盛最多的水是由最短的一块板决定的&#xff0c;所以容纳水的公式为&#xff1a;v 两个数下标之差 * 短板高度。 思路&#xff1a;最优解法&#xff08;双指针法&…

房顶漏水啦【算法赛】

问题描述 小蓝家的房顶是一个边长为 n 的正方形&#xff0c;可以看成是由 nn 个边长为 1 的小正方形格子组成。 从上到下第 i 行、从左到右第 j 列的格子用 (i,j) 表示。 小蓝的家由于年久失修&#xff0c;导致房顶有一些地方漏水。总共有 m 处漏水的地方&#xff0c;我们用…

java之Druid连接池介绍和使用方法 简单易懂

文章目录 一、什么是数据库连接池&#xff1f;二、 为什么选择Druid连接池&#xff1f;三、连接池的jar包四、连接池的使用1、配置及使用配置文件连接mysql数据库2、使用Map集合使用Druid 五、总结 一、什么是数据库连接池&#xff1f; 数据库连接池是一个存储数据库连接的缓冲…

银河麒麟v10 rpm安装包 安装mysql 8.35

银河麒麟v10 rpm安装包 安装mysql 8.35 1、卸载mariadb2、下载Mysql安装包3、安装Mysql 8.353.1、安装Mysql 8.353.3、安装后配置 1、卸载mariadb 由于银河麒麟v10系统默认安装了mariadb 会与Mysql相冲突&#xff0c;因此首先需要卸载系统自带的mariadb 查看系统上默认安装的M…

红队打靶练习:DIGITALWORLD.LOCAL: MERCY V2

目录 信息收集 1、arp 2、netdiscover 3、nmap 4、nikto 5、whatweb 6、总结 目录探测 1、gobuster 2、dirsearch WEB enum4linux枚举工具 smbclient工具 knock工具 CMS 文件包含漏洞 Tomcat 提权 系统信息收集 本地提权 get root 信息收集 1、arp ┌──…

天文与计算机:技术的星辰大海

天文与计算机&#xff1a;技术的星辰大海 一、引言 在人类的历史长河中&#xff0c;天文学与计算机技术这两个领域似乎相隔甚远&#xff0c;然而在科技的推动下&#xff0c;它们却逐渐走到了一起&#xff0c;为人类对宇宙的探索开辟了新的道路。天文观测的复杂度与数据量随着…

C++面向对象(OOP)编程-STL详解(vector)

本文主要介绍STL六大组件&#xff0c;并主要介绍一些容器的使用。 目录 1 泛型编程 2 CSTL 3 STL 六大组件 4 容器 4.1 顺序性容器 4.1.1 顺序性容器的使用场景 4.2 关联式容器 4.2.1 关联式容器的使用场景 4.3 容器适配器 4.3.1 容器适配器的使用场景 5 具体容器的…

Java经典框架之Spring

Java经典框架之Spring Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机&#xff0c;Java 仍是企业和开发人员的首选开发平台。 课程内容的介绍 1. Spring简介 2.…

20231220将NanoPC-T4(RK3399)开发板的Android10的SDK按照Rockchip官方挖掘机开发板编译打包刷机之后启动跑飞

20231220将NanoPC-T4(RK3399)开发板的Android10的SDK按照Rockchip官方挖掘机开发板编译打包刷机之后启动跑飞 2023/12/20 17:19 简略步骤&#xff1a;rootrootrootroot-X99-Turbo:~/3TB$ tar --use-compress-programpigz -xvpf rk3399-android-10.git-20210201.tgz rootrootro…

HarmonyOS4.0系统性深入开发02 UIAbility组件详解(上)

UIAbility组件概述 概述 UIAbility组件是一种包含UI界面的应用组件&#xff0c;主要用于和用户交互。 UIAbility组件是系统调度的基本单元&#xff0c;为应用提供绘制界面的窗口&#xff1b;一个UIAbility组件中可以通过多个页面来实现一个功能模块。每一个UIAbility组件实例…

智能优化算法应用:基于爬行动物算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于爬行动物算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于爬行动物算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.爬行动物算法4.实验参数设定5.算法结果6.…

Android Canvas画布saveLayer与对应restoreToCount,Kotlin

Android Canvas画布saveLayer与对应restoreToCount&#xff0c;Kotlin private fun mydraw() {val originBmp BitmapFactory.decodeResource(resources, R.mipmap.pic).copy(Bitmap.Config.ARGB_8888, true)val newBmp Bitmap.createBitmap(originBmp.width, originBmp.heigh…

【数据结构一】初始Java集合框架(前置知识)

Java中的数据结构 Java语言在设计之初有一个非常重要的理念便是&#xff1a;write once&#xff0c;run anywhere&#xff01;所以Java中的数据结构是已经被设计者封装好的了&#xff0c;我们只需要实例化出想使用的对象&#xff0c;便可以操作相应的数据结构了&#xff0c;本篇…

数值分析期末复习

第一章 科学计算 误差 解题步骤 先求绝对误差: ∣ x − x ∗ ∣ |x - x^*| ∣x−x∗∣求相对误差限: ∣ x − x ∗ ∣ x ∗ \frac{|x\,\,-\,\,x^*|}{x^*} x∗∣x−x∗∣​求有效数字 ∣ x − x ∗ ∣ 需要小于它自身的半个单位 |x-x^*|\text{需要小于它自身的半个单位} ∣…

qt简单连接摄像头

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