【数据结构】二叉树的基本操作与遍历(C语言)

news2024/11/15 11:02:29

目录

定义

满二叉树

 完全二叉树

性质

应用

计算二叉树结点个数

 计算叶子结点的个数

第 k 层结点的个数

查找值为x的节点

遍历

前序遍历

中序遍历

后序遍历

 层序遍历

 判断是否为完全二叉树


定义

🦄二叉树是由树发展过来的,即度最大为2的树,且子树有左右之分,可以这么理解,二叉树是空结点跟左右子树的结合体。

 🦄下面这张图可能更好理解一点,任何二叉树都是下列几种情况复合而成的。因此只要这个树的度超过 ,那么它就不是二叉树。

满二叉树

🦄满二叉树是一种特殊的二叉树,即每一层结点都到达最大值。举个简单的例子,假设这个二叉树根结点在 层且一共有 层,若结点总数为(2^i) -1 个那么这个二叉树就是满二叉树。

 完全二叉树

🦄可以说满二叉树也是一种特殊的完全二叉树,完全二叉树的底层的结点的排序从左到右都是连续的。若假设下面这张图里的F结点还有一个左结点,那么这个二叉树就不是完全二叉树了。

                             

性质

1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 2 ^ (i - 1) 个结点。
2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是(2 ^ h) - 1
3. 对任何一棵二叉树, 如果度为 其叶结点个数为 n0 , 度为 的分支结点个数为 n2 ,则有  n0= n2+1 
4. 若规定根节点的层数为 ,具有 个结点的满二叉树的深度,h = log (n + 1) 。(ps: 是 log 以2为底,n+1 为对数)
5. 对于具有 个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从 开始编号,则对于序号为 的结点有:
1. 若 i>0 位置节点的双亲为:( i - 1 ) / 2 
2. 若 2i + 1 < n ,左孩子为:2i+1 2i + 1 >= n否则无左孩子。
3. 若2i + 2 < n,右孩子为:2i + 22i + 2 >= n否则无右孩子。

应用

🦄与堆不同,二叉树是多个结点链接起来的链式结构,因此对应其特性,每一个根节点都指向左右两个结点。

typedef int BTdatatype;
typedef struct BinaryTreeNode 
{
	BTdatatype data;                 //数据
	struct BinaryTreeNode* left;     //左结点
	struct BinaryTreeNode* right;    //右结点

}BTNode;

🦄而这里二叉树的实现主要依靠的是递归,为的便是能够在访问完一个子树后还能返回到它原来的根结点。

计算二叉树结点个数

🦄这里开始递归就初见雏形,需要一次次递归遍历整个二叉树来计算结点个数,通过返回值的方式将数值统计起来。(若使用全局变量,在多次调用该函数的时候就会出现全局变量未初始化的情况而导致结果出错)

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;         //遇到空结点就返回0(代表0个结点)
	}

	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;  //左右子树结点加上自己的总个数
}

 计算叶子结点的个数

🦄这题跟上一题类似,但还需要加上一个条件筛选叶子结点。

// 二叉树叶子节点个数
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); //两边叶子结点的个数
}

第 k 层结点的个数

🦄这题的关键在于对 层的检索,既要找到 层的结点又要在 层前遇到空返回,则需要两个判断。举个例子而言,根节点距离 层的长度为 k-1 ,即第 层的结点距离 层的长度为 k-2 ,因此每次进一层的时候就把 k-- 当 k==1 的时候该结点就是 层的结点,因此只需要统计这些结点的个数就可以了。

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)                  //空结点不计数
	{
		return 0;
	}
	if (k == 1 && root != NULL)        //到k层且结点不为空则计数
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);  //统合数量
}

查找值为x的节点

🦄这题要注意的是对结点的值的返回,我们都知道一次的结果并不一定会影响到最终返回的结果,因此处理好一找到点就立刻返回的方法非常重要。这里就通过对返回值的判断,只有找到目标结点时才会直接层层返回。

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTdatatype x)
{
	if (root == NULL)
	{
		return 0;                      
	}
	if (root->data == x)               //找到值为x的结点返回
	{
		return root;
	}
	BTNode* ret1 = BinaryTreeFind(root->left, x);    //往左找x
	if (ret1)                                        //有返回值则继续返回
	{ 
		return ret1;
	}

	BTNode* ret2 = BinaryTreeFind(root->right, x);    //往右找x
	if(ret2)                                          //有返回值继续返回
	{
		return ret2;
	}
}

遍历

🦄遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。根据规则的不同,二叉树的遍历可以分作四种,分别是前序、中序、后序以及层序遍历。

前序遍历

传送门:144.二叉树的前序遍历

🦄口诀:根 左 右,即先访问根结点后再依次访问左右结点,倒也像一个人从根结点绕着外围跑遍了整个二叉树。

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)                //为空返回
	{
		printf("NULL ");
		return 0;
	}

	printf("%d ", root->data);         //先打印根节点的数据
	BinaryTreePrevOrder(root->left);   //访问左子树
	BinaryTreePrevOrder(root->right);  //访问右子树

}

中序遍历

传送门:94.二叉树的中序遍历

🦄口诀:左 根 右,中序遍历则需要先访问左子树之后再访问根结点,最后才是右子树。简单地看就是从左到右依次把结点拿下来并访问。

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)                   //为空返回
	{
		printf("NULL ");
		return 0;
	}

	BinaryTreeInOrder(root->left);   //先访问左子树
	printf("%d ", root->data);       //打印根结点
	BinaryTreeInOrder(root->right);  //再访问右子树
}

后序遍历

传送门:145.二叉树的后序遍历

🦄口诀:左 右 根 ,后序遍历则是先遍历两个子树之后才是访问根结点。即要访问这个结点它的左右子树都必须先遍历一遍。可以看成一个结点必须是叶结点才能对其访问,若非叶结点就先访问并“删除”左右结点后让原结点变成叶结点之后再进行访问。

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)                  //为空返回
	{
		printf("NULL ");
		return 0;
	}

	BinaryTreePostOrder(root->left);   //先遍历左子树
	BinaryTreePostOrder(root->right);  //再遍历右子树
	printf("%d ", root->data);         //最后打印根结点
}

 层序遍历

传送门:102. 二叉树的层序遍历

🦄正如其名,层序遍历就是以层为单位进行遍历。若要实现层序遍历的话,所需的思想与上面的遍历就完全不同。因为之前的遍历是以深度优先进行的,而层序遍历需要的是广度优先的遍历。

 🦄为了实现依层遍历的功能,我们需要使用队列来辅助实现。第一次传入的是根结点,每次访问完根结点之后把队头删除,再把队头对应的左右子树对应的指针传入队列之中,根据队列先进先出的性质,所以后传入的结点就会较后地被访问。

typedef BTNode* Qdatatype;
typedef struct Qnode
{
	Qdatatype data;          //数据
	struct Queue* next;      //指向下个结点
}Qnode;

typedef struct Queue
{
	Qnode* head;             //队列的头
	Qnode* tail;             //队列的尾
	int size;                //大小
}Queue;

void Queueinit(Queue* p)
{
	p->head = NULL;           //头尾结点制空
	p->tail = NULL;
	p->size = 0;              //数据量为0
}

bool QueueEmpty(Queue* p)
{
	assert(p);
	return p->head == NULL || p->tail == NULL;    //二者一个为空则队列为空
}

void Queuepush(Queue* p, Qdatatype x)
{
	assert(p);                //断言

	Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));   //开辟新结点
	if (newnode == NULL)              //开辟失败返回
	{
		perror(malloc);
		exit(-1);
	}
	newnode->data = x;                //赋值
	newnode->next = NULL;
	if (p->head == NULL)              //队列为空的情况
	{
		p->head = p->tail = newnode;
	}
	else
	{
		p->tail->next = newnode;      //链接
		p->tail = newnode;
	}
	p->size++;                        //对size进行处理
}

void Queuepop(Queue* p)
{
	assert(p);                      //断言p不为空
	assert(!QueueEmpty(p));         //断言队列不为空

	Qnode* next = p->head->next;    //存储下一结点
	free(p->head);                  //释放当先头结点
	p->head = next;                 //下一结点变成头结点
	p->size--;                      //对size进行处理
}

Qdatatype Queuefront(Queue* p)
{
	assert(p);
	assert(!QueueEmpty(p));         //断言不为空

	return p->head->data;           //头结点存储的就是队头的数据
}

void QueueDestroy(Queue* p)
{
	assert(p);

	Qnode* cur = p->head;
	while (cur)
	{
		Qnode* next = cur->next;
		free(cur);
		cur = next;
	}
	p->head = p->tail = NULL;       //头尾结点制空
	p->size = 0;                    //大小归0
}

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	assert(root);

	Queue q;
	Queueinit(&q);                    //构建队列
	Queuepush(&q, root);              //根结点入队

	while (!QueueEmpty(&q))           //队列为空遍历完成
	{
		BTNode* front = Queuefront(&q);  //取队头
		printf("%d ",front->data);
		Queuepop(&q);                    //删队头
		if (front->left)
		{
			Queuepush(&q, front->left);  //插入队头的左结点
		}
		if (front->right)
		{
			Queuepush(&q, front->right); //插入队头的右结点
		}
	}
	printf("\n");
	QueueDestroy(&q);                    //销毁队列
}

 判断是否为完全二叉树

🦄这个问题是在层序遍历的基础之上实现的,我们知道判断是否为完全二叉树的关键在于最后一层前都为满,最后一层的结点必须连续。因此我们可以这样想,只要层序遍历直到遇到空指针就停止,之后判断队列里面还有没有非空结点。若队列里都是空指针则说明这棵树为完全二叉树。

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
	assert(root);

	Queue q;
	Queueinit(&q);
	Queuepush(&q, root);

	while (!QueueEmpty(&q))          
	{
		BTNode* front = Queuefront(&q);     //取队头
		if (front == NULL)                  //队头不为空则继续循环
		{
			break;
		}
		Queuepop(&q);
		Queuepush(&q, front->left);         //插入左右结点
		Queuepush(&q, front->right);
	}
	
	while (!QueueEmpty(&q))                 
	{
		BTNode* front = Queuefront(&q);      
		if (front != NULL)                  //只要判断队列之后还有没有非空结点
		{
			QueueDestroy(&q);
			return -1;
		}
		Queuepop(&q);
	}
	QueueDestroy(&q);
	return 1;
}

🦙那么,今天二叉树的基本操作与遍历就告一段落,如果这篇文章对你有帮助的话,不妨留下三连支持以下博主,谢谢大家!!!

                                                                             

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

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

相关文章

stm32 笔记 PWM及HAL库应用

stm32 PWM原理 STM32 使用一个定时器作为 PWM 输出&#xff0c;在上图中&#xff0c;ARR 即为重装载值。在计数器的值大于CRRx的值并且小于 ARR 之间&#xff0c;即区分高低电平。输出在图中分别有 ① 和 ② 两种情况. 分别为&#xff1a; ①CRR 和 ARR 区间为低电平。 ②CR…

【pen200-lab】10.11.1.222

pen200-lab 学习笔记 【pen200-lab】10.11.1.222 &#x1f525;系列专栏&#xff1a;pen200-lab &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月27日&#x1f334; &#x1f36d;作…

Vue框架学习(第十三课)Vuex状态管理中的store和state属性

学习官网文档:开始 | Vuex (vuejs.org) 第一部分:查图观色思考为什么&#xff1f;下面的一张图中的数据如何实现组件与组件之间的数据共享呢&#xff1f; 如何去实现下面的方案呢能让数据得到共享 这一张图告诉你们答案 这样如何实现组件与组件之间的通信呀 Vuex五个核心的基本…

FANUC机器人程序设计

一&#xff0e;注意事项 1.FANUC机器人所有者、操作者必须对自己的安全负责。FANUC不对机器使用的安全问题负责。FANUC提醒用户在使用FANUC机器人时必须使用安全设备&#xff0c;必须遵守安全条款。 2.FANUC机器人程序的设计者、机器人系统的设计和调试者、安装者必须熟悉FAN…

408 考研《操作系统》第一章第一节:操作系统的概念和特征

文章目录教程&#xff1a;1. 操作系统的概念、功能和目标1.1 大家熟悉的操作系统1.2 操作系统的概念1.3 操作系统的功能和目标1.3.1 操作系统的功能和目标——作为系统资源的管理者1.3.2 操作系统的功能和目标——作为用户和计算机硬件之间的接口1.3.3 操作系统的功能和目标——…

【三维目标检测】CenterPoint(二)

CenterPoint数据和源码配置调试过程请参考上一篇博文&#xff1a;https://blog.csdn.net/suiyingy/article/details/128002709。本文主要详细介绍CenterPoint网络结构及其运行中间状态。 1 CenterPoint模型总体过程 CenterPoint模型的整体结构如下图所示&#xff0c;由最初的一…

50 jhat 中 java.lang.String 的实例占用空间为什么是 28 bytes ?

前言 此问题是 多个 classloader 加载的同类限定名的Class 在 jhat 中显示不全d 同一时期发现的问题 大致的情况是 看到了 jhat 中统计的各个 oop 的占用空间 似乎是不太能够对的上 比如 java.lang.String, 在 64bit vm 上面 开启了 UseCompressedOops 之后, 应该是占用 …

Gram矩阵+Gram矩阵和协方差矩阵的关系

目录Gram矩阵简介协方差矩阵Gram矩阵 和 协方差矩阵的关系Gram Matrix代码Gram矩阵简介 gram矩阵是计算每个通道 i 的feature map与每个通道 j 的feature map 的内积 gram matrix的每个值可以说是代表 i 通道的feature map和 j 通道的 feature map的互相关程度。 参考博客 GAT…

小程序开发---02认识宿主环境

小程序依赖于微信提供宿主环境 小程序可以借助宿主环境提供的能力&#xff0c;可以完成许多普通网页无法完成的功能&#xff0c;如&#xff1a;微信扫码&#xff0c;微信支付&#xff0c;微信登录&#xff0c;定理定位&#xff0c;etc…等 小程序宿主环境包含以下内容&#xf…

关闭不同型号的 ESP 芯片的 ROM Code 上电启动日志的流程

【说明】 芯片 ROM Code 上电启动日志&#xff0c;不会对应用固件产生任何影响。通过 ROM Code 上电日志能够判断芯片启动模式是处于什么状态。若关闭此日志打印&#xff0c;当芯片进入下载模式或进入 Flash 启动模式等都不会有任何日志提示&#xff0c;不利于检查芯片状态&am…

操作系统学习笔记(V):设备管理

目录 1 设备 1.1 设备的概念 1.2 设备的分类 2 I/O控制器 2.1 I/O控制器 1.定义 2.功能 3.组成 2.2 I/O控制方式 1.程序直接控制方式 2.中断驱动方式 3.DMA方式 4.通道控制方式 5.对比 2.3 I/O软件层次结构 1.用户层软件 2.设备独立性软件 3.设备驱动程序 4…

Windows ssh免密访问Linux服务器

文章目录1.在Windows上生成公钥和私钥2.将公钥中的内容复制到linux服务器3.确认linux服务器开启了允许SSH免密登录4.确认免密登录配置成功ssh提供了安全的身份认证的策略&#xff0c;在免密登录之前&#xff0c;首先需要一对公钥和私钥。客户端拿着私钥&#xff0c;服务端拿着公…

【计算机网络】超详细——华为eNSP的安装教程

网络工程师小白或初次接触计算机网络的学生&#xff0c;网络相关的书本学习起来枯燥乏味&#xff0c;这时需要仿真模拟器来加深对网络知识的理解。目前提供网络仿真平台有cisco、华为等&#xff0c;若您英语基础薄弱建议选华为&#xff0c;英语阅读能力较强的直接上cisco的模拟…

redis我记不住的那些命令(五)

背景&#xff1a;我记不住那么多命令&#xff0c;又是Linux命令&#xff0c;又是Git命令&#xff0c;又是kubernetes的命令&#xff0c;又是maven命令&#xff0c;又是redis命令。所谓好记性不如烂笔头&#xff0c;记下来吧。 一、set集合 集合的特点是 无序且各不相同的元素…

SpringSecurity(二十二)--OAuth2:实现资源服务器(下)通过redis和缓存实现TokenStore

一、前言 本章将在前面几章基础上进行讲解&#xff0c;所以大家最好尽量先去看一下前几章的内容再来跟进会好很多。那么本章我们将通过redis和本地缓存Caffeine对JdbcTokenStore进行重写&#xff0c;并且讲解资源服务器配置的新方案&#xff0c;使得我们可以不用在资源服务器又…

[附源码]计算机毕业设计springboot校园疫情防范管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

LeetCode 337. 打家劫舍 III(C++)*

该题也是使用动态规划的思路&#xff0c;主要考虑根节点的最大金额和左右子节点的关系&#xff0c;其中分为两种情况&#xff1a;有该结点有没有偷钱&#xff0c;其次要遵守不报警原则。可得到状态转移方程&#xff1a; f为根节点被选中的最大&#xff0c;g为根节点没被选中的最…

Day17-购物车页面-结算-动态计算已勾选商品的数据和选中状态

1.动态渲染已勾选商品的总数量 我的操作&#xff1a; 1》在 store/cart.js 模块中&#xff0c;定义一个名称为 checkedCount 的 getters&#xff0c;用来统计已勾选商品的总数量&#xff1a; 2》在 my-settle 组件中&#xff0c;通过 mapGetters 辅助函数&#xff0c;将需要的…

[附源码]Python计算机毕业设计Django健身房信息管理

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

GIS工具maptalks开发手册(五)01-用JSON载入地图——json格式绘制多个面之基础版

GIS工具maptalks开发手册(五)01-用JSON载入地图——json格式绘制多个面之基础版 效果-json渲染图层基础版 代码 index.html <!DOCTYPE html> <html> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width,…