【数据结构】二叉树的节点总个数、叶子节点个数、第K层节点个数、二叉树的深度

news2024/9/23 11:13:38

目录

1.结点总个数

1.1 局部静态变量法

思维

代码

不足之处

2.传指针法

程序代码

3.递归法

思想

程序代码

详细过程

2.叶子节点个数

思想

程序代码

3.第K层节点个数

思想

程序代码

4.二叉树深度

思想

程序代码


        求二叉树节点总个数、叶子节点个数、第k层节点个数、二叉树深度等等都是二叉树较为经典和常见的,下面详细介绍这些内容的实现方法及其思想、原理。

1.结点总个数

1.1 局部静态变量法

思维

        首先,最容易想到的计算总结点个数的方法,就是在遍历二叉树的时候,设置一个 变量,每一次访问到 非空节点,该变量的值就+1,遍历二叉树完后,该变量的值就是二叉树的节点总个数。

       但是,这个变量如何创建,创建什么样的变量 就成了问题,假如是在函数内部创建一个普通变量,如下,则会产生一个问题:每次遍历节点的时候,++ 的 size 是同一个 size吗? 答案是否定的。

        如下图,当进入以节点 a 为参数的 LeafSize1 时,这里面创建的 size加一了;然后进入以 节点 b为参数的 LeafSize1,但是,以节点 a 为参数 和 以节点 b 为参数,是两个不同的函数栈帧,意为着其开辟的size 不是同一块内存中的,自然也就不是同一个 size(如右下角 栈区内存示意图,两个函数栈帧是两块不同的空间)。所以,这样子写的话,无论如何都无法计算节点总个数。

        那既然这是局部变量,每次创建新的函数栈帧都会重新创建该变量,此时将这个变量设置成全局变量不就可以了吗?这样创建的变量size,在静态区,每次创建的函数栈帧虽然都在栈区,但是一直使用的都是静态区的那一个size。如下,这样可以保证最后得到的结果是节点总个数。

代码

        如下,在 size 前面加上 static ,将其设置成静态区的全局变量。每一次递归调用该函数,++的都是处于静态区的size。

int LeafSize1(BTNode* root)
{
	static int size = 0;//全局变量
	if (!root)
		return;
	if (root)
		size++;
	LeafSize1(root->left);
	LeafSize1(root->right);
    return size;
}

不足之处

        但是,这种方法也存在很大的不足。比如,只能计算一颗二叉树的节点总个数。如下运行结果:发现三次计算该二叉树节点总个数值都不同。

        这是因为,由于我们创建的是局部静态变量,所以,只能在最开始调用该函数的时候进行初始化。并且,由于是递归调用该函数,所以也无法判断递归到哪一层结束,也就没办法在本轮递归结束的时候,设置size 为0

        具体过程如下两张图所示。size的值由于无法在每次计算节点总个数的时候初始化为0,同时也没有办法判断在每一次递归结束的时候置为0,也就只能无限制地增加下去。

第一次计算二叉树节点总个数:

第二次计算二叉树节点总个数:

        所以,这显然不是一个好办法。

2.传指针法

        既然在函数内部设置变量无法达到想要的效果。那么我们可以尝试在函数外部设置对应的变量,然后每次调用该函数的时候,传入指针即可。示意图如下:

程序代码

         如下,不需要设置返回值,因为一直改变的就是传入的变量,直接在函数外部取即可。

void LeafSize2(BTNode* root, int* size)
{
	if (!root)
		return;
	if (root)
		(*size)++;
	LeafSize2(root->left, size);
	LeafSize2(root->right, size);
}

         运行结果也是如预期。无论是新建变量来计算,还是在下一次计算时将变量值设为0,都可以满足需求。

        因此,这种方法是比较推荐使用的。

3.递归法

 递归的两个基本要素:
    1、递归关系式:确定关系式,即原问题是如何分解为子问题
    2、递归出口:确定递归到什么时候终止

同时、一个递归函数我们是默认它能够完成所需功能的。

思想

        在这里,首先准备递归出口:当递归到传入指针为NULL时,自然就结束了,返回0,因为传入参数为 NULL的情况下,自然不是节点。

        递归关系式:先求左子树的节点个数,再求右子树的节点个数,最后加上根节点。

        所以,只需要判断当前参数是否为NULL,是NULL,表示当前没有节点,返回0,否则返回左子树的节点个数+右子树的节点个数+1。

程序代码

        代码如下。

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

详细过程

        这里和递归遍历二叉树是非常类似的,如下,一开始以节点 a 为参数,然后进入函数直接判断,参数root 不为NULL,那么返回 LeafSize3(root->left) + LeafSize3(root->right) +1 ;  毫无疑问,这里要先调用 LeafSize(root->left) 以节点a 的左孩子为参数调用函数,直到以节点d 为参数,其左右节点都是NULL,所以返回值都是0,如蓝色箭头和数字所示。

        同时,以节点d 为参数的这一块调用也结束,返回一个值,其值是孩子节点数之和再+1,由于其左右孩子都返回0,所以最后返回的结果是1。符合要求,因为d节点及其所有孩子节点一共也就它自己这一个。

        上图过程完毕,返回到以 b节点为参数的函数栈帧中,如下过程,应该调用 LeafSize3(root->right); 由于root 是节点b,所以其右孩子理所当然是节点e。如下图,节点e左右孩子均为NULL,所以它调用的函数栈帧均返回0,再加上1,它返回的结果也是1

        然后到了以 节点b 为参数的函数栈帧返回的时候了,其返回值是 1+1 +1,所以返回3,如下图蓝色标识。

         然后又回到以节点a 为参数的函数栈帧中,应该调用 LeafSize3(root->right); 节点a 的右孩子无疑是节点c,由于其左右孩子都是NULL,故以节点c 为参数的函数栈帧返回值也是1。所以整个递归最后的返回值就是 3+1+1  等于5

2.叶子节点个数

思想

        求叶子节点个数,当然也可以像上面一样有多种方法,只是把判断普通节点时的操作,换成判断为叶子节点的操作即可。这里简单地讲一讲用上面第三种方法——递归法来完成。

        首先,递归关系式就是:返回左右子树的叶子节点个数。

        而对于递归终止条件,不是简单地遇到叶子节点就返回1。如下图,如果只判断传入参数是否为叶子节点,遇到某个节点,只有一个孩子的时候,就与发生如下情况,会报错。所以,既要判断传入参数是否为叶子节点,也要判断是否为NULL。

程序代码

//叶子节点个数
int TreeLeafSize(BTNode* root)
{
	if (!root)
		return 0;
	if (root->left == NULL && root->right == NULL) //叶子返回1
		return 1;
	return    TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

3.第K层节点个数

思想

        既然要求第k层,那么传入参数必然要多一个k,表示求该层节点个数。递归关系式很容易:返回左右子树第k-1层的节点个数(因为当前树的根节点算一层,所以递归下去每次减去一层)。

        和上一个一样,一方面,要判断是否到了第k层,到了第k层就直接返回1,这样子第k层之下的就不用再遍历了,免做无用功;另一方面,也是要判断传入参数是否为空,如果为空直接返回0。

        首先来看如何判断递归是否到达第K层,如下图(先不要看递归终止条件,看这个递归过程即可),每下一层,传入参数的 k 就减一。要求第3层的节点个数,实际上只要递归深入两层即可,所以,要求第k层节点个数,实际上只递归下去了k-1层,即,只要传入的参数 k 等于1,就到达了第K层

        但是,在这里是先判断是否到达第k层,还是先判断当前节点是否为空?我们假设,第k层如果为空,第k-1层不为空,我们可以到达第k-1层,然后进入第k层,此时如果先判断传入参数k,这时k=1,返回1,但是实际上第k层是没有节点的,就会造成错误。如下图蓝色数字,原本节点a的右子树应该返回的结果是0,但是由于到了第k层,先判断k==1,所以最后返回了2,是错误的:

        所以如下面代码所示,要先判断当前节点是否为空。因为在这个算法的设计下,递归是无法递归到第k层之下的,所以大可放心。

程序代码

//第k层节点个数
int TreeKLevelSize(BTNode* root, int k)
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	k--;
	return TreeKLevelSize(root->left, k) + TreeKLevelSize(root->right, k);
}

4.二叉树深度

思想

        依然使用递归法,这里的递归关系式就有小小的不同了:如果左子树的深度大于右子树,返回左子树深度+1,否则返回右子树深度+1(+1代表加上当前树的根节点所在层)。递归终止条件依然是遇到空节点返回0。

        但是,如下图的代码,虽然可行,效率却极其低下。可以自己尝试画一下递归展开图,就拿上面一直做例子的二叉树作为参考。

        所以,我们用两个变量 将左右子树的深度存起来,比较这两个变量大小即可。如下面的代码。

程序代码

//求二叉树深度
int TreeDepth(BTNode* root)
{
	if (root == NULL)
		return 0;
	int left = TreeDepth(root->left);
	int right = TreeDepth(root->right);

	return left > right ? left + 1 : right + 1;
}

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

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

相关文章

汀丶的创作纪念日

机缘 csdn的博龄5年了,但实际创作时间只有两年;第一次接触csdn主要是用来查找代码bug并收藏一些有价值博客,但渐渐地自己也就习惯把自己学到的知识和技术分享出来,一起共建。 主要是关于机器学习、强化学习、数据挖掘、强化学习以…

ADI Blackfin DSP处理器-BF533的开发详解62:DSP控制ADXL345三轴加速度传感器-贪食蛇游戏(含源码)

硬件准备 ADSP-EDU-BF533:BF533开发板 AD-HP530ICE:ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 MEMS三轴加速度传感器 我做了一个三轴加速度传感器的子卡,插在这个板子上,然后写了一些有意思的应用程序。 代码实现功能…

Bootstrap5 侧边栏导航(Offcanvas)

Bootstrap5 侧边栏侧边栏类似于模态框,在移动端设备中比较常用。 创建滑动导航 我们可以通过 JavaScript 来设置是否在 .offcanvas 类后面添加 .show 类,从而控制侧边栏的显示与隐藏: .offcanvas 隐藏内容 (默认).offcanvas.show 显示内容…

JVM之native关键字与PC寄存器

native关键字: native关键字主要用于修饰方法: 被native关键字修饰的方法叫做本地方法,一个native方法就是一个Java调用非Java代码的接口,该方法的实现由非Java语言实现,而是使用C或C等其他编程语言实现 native方法…

Compose 和 Android 传统View 互相调用

1. 前言 Compose 具有超强的兼容性,兼容现有的所有代码,Compose 能够与现有 View 体系并存,可实现渐进式替换。这就很有意义了,我们可以在现有项目中一小块一小块逐步地替换Compose,或者在旧项目中实现新的需求的时候…

设计模式之外观模式

Facade design pattern 外观模式的概念、外观模式的结构、外观模式的优缺点、外观模式的使用场景、外观模式的实现示例、外观模式的源码分析 1、外观模式的概念 外观模式,为多个复杂的子系统提供一个统一的接口,使得这些子系统更加容易被访问。在现有的…

【AI with ML】第 11 章 :对序列模型使用卷积和递归方法

🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎 📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃 🎁欢迎各位→点赞…

SAP Gateway Foundation 里的 batch 操作

SAP Gateway Foundation (SAP_GWFND) 是一个在 SAP NetWeaver 中可用的软件组件。 SAP Gateway Foundation 提供开发和生成工具来为各种客户端开发工具创建 OData 服务。 简而言之,它在应用程序或 SAP Business Suite 数据与目标客户、平台和编程框架之间建立连接。…

核心面试题:MVCC、间隙锁、Undo Log链、表级锁、行级锁、页级锁、共享锁、排它锁、记录锁等等

文章很长,而且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 :《尼恩Java面试宝典》 持续更新 史上最全 面试必备 2000页 面试必备 大厂必备 涨薪必备 免费赠送 经典…

网页初学者,如何使用VS2005搭建编程环境(JavaScript及ASP调试)

一直想学一下网页编程,但是感觉要学的东西太多了。也没有人指导。只好一个人摸索。 尝试了一些常用的网页编程技术。得出自己的总结,写在这里做一个备份。 本文写个自己,也作为和我一样的初学者一个参考。 【工具准备】 一、服务器端学什…

大数据的基础知识上(大数据的概念和生态、linux系统与命令、虚拟机导入、虚拟机额配置和联网)

目录一、数据分析的方向二、数据分析步骤1.明确分析目的和思路2.数据传输收集过程3.数据处理4.数据分析5.数据展现6.报告撰写三、数据是什么 大数据时代大数据的应用有哪些四、分布式和集群1.概念🎡(by the way)大数据生态系统🎡&…

【Three.js入门】一文带你入坑前端3Dの妙妙屋

个人简介 👀个人主页: 前端杂货铺 🙋‍♂️学习方向: 主攻前端方向,也会涉及到服务端 📃个人状态: 在校大学生一枚,已拿多个前端 offer(秋招) 🚀未…

消息队列 - RabbitMQ - 拓展

1. Message 状态 Message 在投递时,如果当前 Queue 没有 Message,且有 Consumer 已经订阅了这个 Queue,那么该 Message 会直接发送给 Consumer,不会经过 Queue 存储 Message 的这一步 当 Message 无法直接投递给 Consumer 时&am…

【大数据技术Hadoop+Spark】Spark RDD创建、操作及词频统计、倒排索引实战(超详细 附源码)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 一、RDD的创建 Spark可以从Hadoop支持的任何存储源中加载数据去创建RDD,包括本地文件系统和HDFS等文件系统。我们通过Spark中的SparkContext对象调用textFile()方法加载数据创建RDD。 1、从文件系统加载数据创…

基于 Tensorflow 2.x 实现多层卷积神经网络,实践 MNIST 手写数字识别

一、MNIST 数据集 上篇文章中使用了Tensorflow 2.x 搭建了对层的 BP 神经网络,经过训练后发现准确率只有 96.8% 对于单环境的图片识别场景来说,还是有点偏低,本文使用多层的卷积代替BP网络中的隐藏层对模型进行优化。 下面是上篇文章地址&am…

C语言重点解剖第12课笔记

1.int* a,b; a和b的类型不一样, a是指针,b是整型。 typedef int* int_p; int_p a,b; 或者int* a,*b; 这样写的话,a和b都是指针类型。 #define int_p int*;这是纯粹的文本替换。 typedef定义之后是一种独立类型。 2.大部分注释都换成了…

Linux网络协议之HTTP协议(应用层)

Linux网络协议之HTTP协议(应用层) 文章目录Linux网络协议之HTTP协议(应用层)1.HTTP协议的概念2.HTTP协议中URL的理解3.HTTP协议的数据流4.HTTP协议的格式4.1 HTTP请求格式4.2 HTTP响应格式5.HTTP协议格式图解6.HTTP协议版本7.HTTP协议请求方法7.1 GET方法:获取资源7…

OWASP API安全Top 10

文章目录API1-失效的对象级授权API2-失效的用户认证API3-过度的数据暴露API4-缺乏资源和速率控制API5-失效的功能级授权API6-批量分配API7-安全性配置错误API8-注入API9-资产管理不当API10-日志记录和监控不足在API安全发展的过程中,除了各大安全厂商和头部互联网企…

计算机基础学习笔记:操作系统篇之硬件结构,CPU的基本工作原理

一、CPU的是如何运行程序的? 本文知识来源小林Coding阅读整理思考,原文链接请见以下: https://xiaolincoding.com/os/1_hardware/how_cpu_run.html#图灵机的工作方式 问题引入 程序的执行过程?例如 12 的具体过程是怎么样的&…

Windows VS2015 cmake编译Gtest并进行测试

1.下载Gtest 下载网址:https://github.com/google/googletest/releases 也可以直接使用下载好的附件 解压,放到一个目录中,演示所用,直接存放D盘了。 2.使用CMake生成vs编译工程 选好下图中两个路径,点击Configure…