【数据结构】——二叉树的递归实现,看完不再害怕递归

 创作不易,感谢三连加支持?!

一  递归理解

递归无非就是相信它,只有你相信它,你才能写好递归!为什么?请往下看

在进入二叉树的实现之前,我们得先理解一遍递归,可能很多人对于递归感到恐惧或者害怕,有时候还不敢面对,其实大可不必,  递归其实分为两个东西:

1.递归结束的条件

2.递归的子问题

1.递归的结束条件:一遍以题目的题意为主,只要理解题意就会知道结束条件是什么,比如求二叉树的结点个数,那他的结束条件就是没有结点的时候,没有结点,那就返回0就行了

2.递归子问题:在使用递归的过程中可能一个问题可以被分成很多个相同的问题,这些相同的问题也就是子问题,拿上面求二叉树的结点个数这个题目来说,我们要求的就是以根为结点这个树的所有结点个数,那么我们可以分成求根节点左子树和右子树的结点个数,然后左子树和右子树又可以和上面一样继续分开

弄清楚上面这个以后,我们在写递归的时候,可以先想想递归的结束条件是什么,然后先结束条件写上,然后再根据子问题一个一个分解,这个递归过程就完成了。

其实我们大部分对于上面两个点都理解,只不过我们写的时候就是写不出来,这种情况是不是很多人有呢? 往下看,相信看完,你的疑惑会少很多!

那我们还是拿上面的例子来说明吧

求二叉树的结点个数(递归实现)

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)//判断条件
		return 0;
	return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;//递归
}

上面代码的结束条件很好理解,那么就是后面的递归过程不好理解

    这里的+1是我们递归思想为左子树的结点个数+右子树的结点个数+自己 ,可能还是有点抽象

那么我们不妨 先假设如果只有一个根结点,左右子树都为空,那么返回的就是他自己,也就是1

如果还是比较混乱,那就看看下面的图

如果理解了里面的细节,那么我们就看看如果攻破递归 !!

首先判断条件我们肯定第一步直接写,那么我们的下一步就是把每一个递归看作一个黑盒,这个黑盒怎么运作的我们不管(这个是在你理解了递归的细节之后),相信它,它一定可以帮你完成任务,你相信它,它也相信你,比如上面的代码我们可以改成这样

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	int left = BinaryTreeSize(root->_left);//相信它,把它当作一个黑盒,它肯定能帮助你完成你想做的事情
	int right = BinaryTreeSize(root->_right);//和上面一样
	return left + right + 1;//最后把结果返回
}

 这里的黑盒就是我们的递归函数,写完判断条件,那么后面我们希望求出左子树的结点个数和右子树的结点个数,那么我们直接把认为交给这两个黑盒(函数),他们可以帮我们算出来。剩下的我们只需要去把他们的返回值接收就行了,然后把所有的结果一次性返回就ok了,相信看到这里,你应该有点感觉了 

那我们趁热打铁,直接来看二叉树的实现

二  二叉树的实现

1.二叉树的创建和销毁
typedef char BTDataType;//一样的先定义一个树的结构体
typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;


//然后对它进行初始化
BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	node->_data = x;
	node->_left = NULL;
	node->_right = NULL;
}

创建二叉树可以用递归创建,也可以自己一个结点一个结点的创建
递归创建得先给你一个序列才能创建
这里我们用一个前序递归来创建二叉树

 比如通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

这里的’#’号代表空

1.既然是递归那么我们的想法就是先写出结束条件,那是不是当我们遇到空的时候就不用递归下去了,也就是遇到’#‘号我们直接返回就行了

2.第二就是我们是前序遍历,所以我们需要先把根结点弄出来

3.第三步递归去把左右子树全部创建,这里我们就需要用两个黑盒去实现

BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)//pi是数组下表
{
	if (*pi >= n || a[*pi] == '#')
	{
		(*pi)++;//遇到空了,下表也要往后面走
		return NULL;
	}
	BTNode* cur = BuyNode(a[*pi]);//不是空,那么我们创建根结点结点
	(*pi)++;//继续往后面走
	cur->_left = BinaryTreeCreate(a, n, pi);//黑盒去创建左子树
	cur->_right = BinaryTreeCreate(a, n, pi);//黑盒去创建右子树
    //完成任务直接返回根结点
	return cur;
}

 那这样我们的二叉树就被创建出来了,其实也不是很难,如果用这个思想去做递归的题目可以轻松不少,因为想的少,前提是前期是理解了递归的过程,不然这样直接去想到后面也会寸步难行

还有一个销毁,销毁我们可不是简单把指针置空就完成任务了,这里也需要递归去实现,和上面的思想一样

先判断结束条件,后面用黑盒去帮你把左右子树销毁,有这个思路,那么写起来就很轻松

void BinaryTreeDestory(BTNode* root)
{
	if (root==NULL)
	{
		return;//直接返回
	}
	BinaryTreeDestory(root->_left);//两个黑盒帮你完成任务
	BinaryTreeDestory(root->_right);
	free(root);//最后别忘记释放
}

那么下面我们就对以上的思想进行一个强化

2.计算二叉树叶子结点的个数

思想:计算叶子结点,那么结束条件肯定也是结点为空就停止,既然是叶子结点,那么肯定是左右子树都为空才是叶子结点,那么后面递归我们就需要用到黑盒去完成

int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	if (root->_left == NULL && root->_right == NULL)//两个为空才为叶子结点
		return 1;
    //这里如果不好理解,可以和上面一样把他们拆开然后再一起返回,都是黑盒思想
	return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}

 这里没有+1是没有算自己,因为我们是算的左右结点,如果左右结点是叶子结点那么就返回它就行了


3.二叉树第k层节点个数 

 思想:第一还是判断条件,如果结点为空,那么就返回,因为结点为空说明结点个数为0,

第二我们要算第k层的结点个数,我们知道如果k=1那么就一个结点,那如果k不是1,那我们就需要去左右子树去找第k层的结点数,剩下的就不需要我说了吧

int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
    //交给黑盒,然后返回就行
	return BinaryTreeLevelKSize(root->_left, k - 1)+
	BinaryTreeLevelKSize(root->_right, k - 1);
}

 注意这里黑盒的参数,k是要一直减小的,因为是越来越靠近第k层的

4. 二叉树查找值为x的结点

一样的我们的黑盒大法

剩下的你们自己说

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->_data == x)//找到就返回
		return root;
    //黑盒大法
	BTNode*left=BinaryTreeFind(root->_left,x);
	BTNode* right = BinaryTreeFind(root->_right,x);
    //看那个不为空,不为空就返回
	if (left)
		return left;
	if (right)
		return right;
    //有的人会这样写
    //因为要返回的是指针,这里的或判断是bool型只会返回一个0或者1;
    //return  BinaryTreeFind(root->_left,x)||BinaryTreeFind(root->_right,x);
}

 以上就是递归思想的总结和二叉树基本操作和实现,可以试试用上面的思想去做一下二叉树的前序遍历中序遍历和后序遍历,这样思路会更加清晰

 这里着重说一下层序遍历

5.二叉树的层序遍历

这里的就不是用黑盒思想那么简单了,这里的遍历需要用到队列去实现,思想就是先把根结点入队列,然后出队列,然后把左右结点入队列,左结点出队列的时候,把左结点的左左节点和右结点入队列,后面的操作和这里一样,这样也就实现了层序遍历 

 图画的不好还请谅解

void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root)
		QueuePush(&q, root);//放头节点

	while (!QueueEmpty(&q))如果队列为空就退出来
	{
		BTNode* front = QueueFront(&q);//出第一个结点
		QueuePop(&q);

		printf("%d ", front->data);

		if(front->left)入左右结点
			QueuePush(&q, front->left);

		if (front->right)
			QueuePush(&q, front->right);
	}

	printf("\n");

	QueueDestroy(&q);
}
6.判断是否是完全二叉树 

 和上面一样都需要用队列实现,如果要用其他方法那就比较麻烦了,不推荐

这里的思想就是层序遍历,然后出数据,因为完全二叉树是连续的不可能出现左子树为空,右子树不为空的情况,所以如果出的数据为空,那么退出,然后再去遍历,如果有不为空的那么就不是完全二叉树

bool BTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root)
		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)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;如果走到这里就说明是完全二叉树
}

如果看到这里,就请三连支持一下吧,非常感谢!

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

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

相关文章

通用开发技能系列:Authentication、OAuth、JWT 认证策略

云原生学习路线导航页(持续更新中) 本文是 通用开发技能系列 文章,主要对编程通用技能 Authentication、OAuth、JWT 认证策略 进行学习 1.Basic Authentication认证 每个请求都需要将 用户名密码 进行base64编码后,放在请求头的A…

网络基础二——TCP可靠性实现机制补充

11.3.4确认应答机制 ​ 1.双方通信时要返回确认应答报文,保证对方发送的报文是有效的;尽管整个通信过程中无法保证数据全部可靠,但是可以保证单个方向发送的数据是可靠的; ​ 发送的报文要设置序号,如果是应答报文要…

腾讯云容器与Serverless的融合:探索《2023技术实践精选集》中的创新实践

腾讯云容器与Serverless的融合:探索《2023技术实践精选集》中的创新实践 文章目录 腾讯云容器与Serverless的融合:探索《2023技术实践精选集》中的创新实践引言《2023腾讯云容器和函数计算技术实践精选集》整体评价特色亮点分析Serverless与Kubernetes的…

基于springboot实现校园资料分享平台系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现校园资料分享平台演示 摘要 随着信息互联网购物的飞速发展,国内放开了自媒体的政策,一般企业都开始开发属于自己内容分发平台的网站。本文介绍了校园资料分享平台的开发全过程。通过分析企业对于校园资料分享平台的需求,创…

设计模式总结-面向对象设计原则

面向对象设计原则 面向对象设计原则简介单一职责原则单一职责原则定义单一职责原则分析单一职责原则实例 开闭原则开闭原则定义开闭原则分析开闭原则实例 里氏代换原则里氏代换原则定义里氏代换原则分析 依赖倒转原则依赖倒转原则定义依赖倒转原则分析依赖倒转原则实例 接口隔离…

React 入门

一、官网地址 英文官网: https://reactjs.org/中文官网: https://react.docschina.org/ 二、React 特点 声明式编码组件化编码React Native 编写原生应用高效(优秀的 Diffing 算法)高效的原因:1.使用虚拟DOM,不总是直接操作页面…

Navicat Premium 16 Mac/win---数据库设计、管理与维护轻松掌握数据库管理精髓

Navicat Premium是一款功能强大的数据库开发工具,支持多种数据库系统,如MySQL、Redis、MariaDB、Oracle等,并可与云数据库兼容,如Amazon RDS、Microsoft Azure等。它提供了直观易用的用户界面,使得开发者能够轻松上手并…

递归算法讲解2

前情提要 上一篇递归算法讲解在这里 递归算法讲解(结合内存图) 没看过的小伙伴可以进去瞅一眼,谢谢! 递归算法的重要性 递归算法是非常重要的,如果想要进大厂,以递归算法为基础的动态规划是必考的&…

基于单片机的无线红外报警系统

**单片机设计介绍,基于单片机的无线红外报警系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的无线红外报警系统是一种结合了单片机控制技术和无线红外传感技术的安防系统。该系统通过无线红外传感器实…

思通数科:利用开源AI能力引擎平台打造企业智能搜索系统

在信息爆炸的时代,如何高效地管理和检索海量数据已成为企业和个人面临的一大挑战。思通数科 StoneDT 多模态AI能力引擎平台,以其强大的自然语言处理(NLP)、OCR识别、图像识别和文本抽取技术,为用户带来了前所未有的智能…

UE4 C++获取Niagara变量值

UE4 获取Niagara变量值 Niagara有一堆Get方法,但是是基于数据的,单独的Set方法是有的,因此,我们这参考Set源码去Get 源代码如下: 我们的实现(当然要返回其他类型值,修改一下对应传参就行了…

一个简单的Demo展示fastapi+tortoise-orm+celery如何搭配

1. 创建并激活虚拟环境 python3 -m venv venv source venv/*/activate 2. 安装依赖包 pip install fastapi uvicorn[standard] tortoise-orm celery[redis] fastapi-cdn-host 3. 配置数据库连接参数 - config.py from typing import TypedDictclass TortoiseInitParam(Ty…

【C语言】翻译环境与运行环境

一、前言 在我们学习C语言的时候,第一个接触的程序就是:在屏幕上打印” hello word! “,可当时的我们却未去深入的理解与感悟,一个程序代码是如何运行的;而这一期的博客,则是带着我们,通过C代码…

【旅行商问题TSP】基于大邻域搜索算法LNS

课题名称:大规模邻域搜索算法LNS求解TSP问题 版本时间:2024-04-01 程序运行:直接运行LNS_TSP.m 文件即可 代码获取方式: QQ:491052175 VX:Matlab_Lover 模型介绍: 第一步:设定…

LeetCode-统计完全连通分量的数量

题目要求: 给你一个整数 n 。现有一个包含 n 个顶点的 无向 图,顶点按从 0 到 n - 1 编号。给你一个二维整数数组 edges 其中 edges[i] [ai, bi] 表示顶点 ai 和 bi 之间存在一条 无向 边。 返回图中 完全连通分量 的数量。 如果在子图中任意两个顶点…

OPPO云VPC网络实践

1 OPPO 云网络现状 随着OPPO业务的快速发展,OPPO云规模增长迅速。大规模虚拟实例的弹性伸缩、低延时需求对网络提出了诸多挑战。原有基于VLAN搭建的私有网络无法解决这些问题,给网络运维和业务的快速上线带来了挑战。 梳理存在的主要问题如下&#xf…

Tik Tok与抖音:一母同胞的不同风采

随着智能手机的普及和网络技术的飞速发展,短视频平台已经成为了大众娱乐生活中不可或缺的一部分。在众多的短视频平台中,Tik Tok和抖音无疑是最受欢迎的两个。尽管它们有着相似的基因——都源自中国,但两者在定位、内容、用户群体以及运营策略…

解决VScode中matplotlib图像中文显示问题

一、更改配置文件 参考这个文件路径找到自己Python环境下的matplotlibrc文件并用记事本打开。 用ctrl F寻找下面的这两行并将前面的#删除,保存并退出。 font.family: sans-serif font.serif: DejaVu Serif, Bitstream Vera Serif, Computer Modern Roman, N…

红黑树介绍与模拟实现(insert+颜色调整精美图示超详解哦)

红黑树 引言红黑树的介绍实现结点类insert搜索插入位置插入调整当parent为gparent的左子结点当parent为gparent的右子结点 参考源码测试红黑树是否合格总结 引言 在上一篇文章中我们认识了高度平衡的平衡二叉树AVL树:戳我看AVL树详解哦 (关于旋转调整的…

springboot 项目整合easy-captcha验证码功能

效果 1、验证码使用easy-captcha,在pom文件增加依赖 <!-- google 验证码 --><dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId></dependency> 2、增加获取kaptcha的ctrl package com.*.*.s…