二叉树--C语言实现数据结构

news2024/12/23 13:41:46

在这里插入图片描述

本期带大家一起用C语言实现二叉树🌈🌈🌈

1、二叉树的定义

二叉树是一种特殊的树状数据结构,它由节点组成,每个节点最多有两个子节点,分别称为左子节点和右子节点

二叉树的链式存储结构是指用 链表 来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三

个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址

2、二叉树 的结构

在这里插入图片描述

3、二叉树的实现

3.1 结构设计

typedef int BTreeDataType;

typedef struct BTreeNode
{
	BTreeDataType val;

	struct BTreeNode* right;
	struct BTreeNode* left;
}BTNode;

3.2 手动构建二叉树

在这里插入图片描述

BTNode* CreatNode(BTreeDataType val)
{
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	if (root == NULL)
	{
		perror("root malloc fail");
		return;
	}

	root->val = val;
	root->left = NULL;
	root->right = NULL;

	return root;

}
BTNode* CreatBinaryTree()
{
	
	BTNode* root1 = CreatNode(1);
	BTNode* root2 = CreatNode(2);
	BTNode* root3 = CreatNode(3);
	BTNode* root4 = CreatNode(4);
	BTNode* root5 = CreatNode(5);
	BTNode* root6 = CreatNode(6);
	BTNode* root7 = CreatNode(7);

	root1->left = root2;
	root1->right = root3;
	root2->left = root4;
	root2->right = root5;
	root3->left = root6;
	root3->right = root7;
	return root1;

}

3.3 前序遍历

  1. 首先进行条件判断,如果当前节点root为空,即遍历到了空节点,输出"N "(表示Null)之后返回。这是因为在先序遍历中,空节点也需要被遍历到。

  2. 若当前节点root不为空,则输出当前节点的值root->val

  3. 继续递归遍历当前节点的左子树,即调用PrevOrder(root->left)

  4. 最后,递归遍历当前节点的右子树,即调用PrevOrder(root->right)

通过递归的方式,先序遍历会按照先根节点、左子树、右子树的顺序遍历整个二叉树。在代码中,先打印当前节点的值,然后分别遍历左子树和右子树。

void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	else
		printf("%d ", root->val);
	PrevOrder(root->left);
	PrevOrder(root->right);

}

在这里插入图片描述

3.4 中序遍历

  1. 首先进行条件判断,如果当前节点root为空,即遍历到了空节点,输出"N "(表示Null)之后返回。这是因为在中序遍历中,空节点也需要被遍历到。

  2. 若当前节点root不为空,则递归遍历当前节点的左子树,即调用InOrder(root->left)

  3. 输出当前节点的值root->val

  4. 最后,递归遍历当前节点的右子树,即调用InOrder(root->right)

通过递归的方式,中序遍历会按照左子树、根节点、右子树的顺序遍历整个二叉树。在代码中,先遍历左子树,然后打印当前节点的值,最后遍历右子树。这样可以保证中序遍历的顺序性。

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right);

}

3.5 后序遍历

后序遍历的访问次序是 先访问左子树,再访问右子树,再访问根节点

逻辑同前序遍历和中序遍历差不多

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);

}

3.4 层序遍历

层序遍历就是从第一层开始从左向右逐个遍历

那么当前结果就是1 2 3 4 5 6 7

层序遍历的话,需要一个队列来进行辅助

首先我们将root根节点放进去,然后判断队列当中是否为空,为空就跳出循环

不为空的话就将节点不为NULL的放进去,直到队列为空

void LevalOrder(BTNode* root)
{
	if (root == NULL)
		return;
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%d ", front->val);
		if(front->left)
			QueuePush(&q, front->left);
		if(front->right)
			QueuePush(&q, front->right);

	}

	QueueDestroy(&q);

}

核心思想就是出上一层带下一层

3.5 计算二叉树的节点数

这里我们可以有两种思路:
1、给定一个 size ,遍历二叉树,分别遍历左右子树,遍历过程中遍历到非空节点 size++,遍历到空,则返回 0。
2、二叉树的大小 = 根节点 + 左子树节点 + 右子树节点,将其分成多个子问题,递归求解。

思路1 size计数

使用局部变量size来记录节点数量是不可行的。因为在递归调用的过程中,每一次递归都会创建一个新的函数栈帧,这意味着每个递归调用都有自己独立的size变量,并且初始值都是0,无法准确计算出二叉树的大小。

为了解决这个问题,可以使用全局变量size来记录节点数量。全局变量位于全局数据区,在整个工程内都有效。但需要注意的是,由于全局变量不会被销毁,如果多次调用计算节点数量的函数,size会累加,可能导致错误的结果。因此,在每次调用之前,需要将size重置为0,以确保准确计算二叉树的大小。

总结来说,局部变量不能满足统计二叉树节点数量的在这里插入代码片需求,需要使用全局变量,并在每次调用之前将其重置为0。

int size = 0;

int BTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	size++;
	BTreeSize(root->left);
	BTreeSize(root->right);
	return size;
}

思路2 递归实现
计算二叉树的节点数

如果root==NULL的话,那我们就返回0

不然的话我们就返回1+左子树的节点树+右子树的节点数

int BTreeSize(BTNode* root)
{

	//return root == NULL ? 0 : 1 + BTreeSize(root->left) + BTreeSize(root->right);

	if (root == NULL)
		return 0;

	return 1 + BTreeSize(root->left) + BTreeSize(root->right);
}

3.6 计算二叉树的高度

计算二叉树的高度

如果我们的root==NULL的话,那我们返回0

不然的话计算左子树的高度和右子树的高度

让最高的子树高度+1就是我们的二叉树高度

不过需要注意的是需要拿left_height和right_height来计数,防止重复递归调用

不然到时候还得重复计算好多次,尤其是会重复计算下面的高度

可不是简单的二倍关系

nt BTreeHeight(BTNode* root)
{
	if (root == NULL)

		return 0;
	int left_hight = BTreeHeight(root->left);
	int right_hight = BTreeHeight(root->right);

	return left_hight > right_hight ?  1 + left_hight : 1 + right_hight;
}

3.7 计算叶子节点的个数

计算叶子节点的个数

如果root==NULL,返回0

如果root->left == NULL && root->right == NULL 那就返回1

如果还不满足以上的条件话,那就返回左子树的叶子节点个数+右子树叶子节点个数

int BTreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	if (root->left == NULL && root->right == NULL)
		return 1;
	return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);


}

3.8 计算第K层节点数

计算第K层节点数

对于第一层,需要计算的是 第 k 层的节点个数;
对于第二层,需要计算的是 第 k - 1 层的节点个数;
对于第 k 层,计算的就是 第 1 层( 当前层数 ) 的节点个数。

如果我的root==NULL的话,那就返回0

如果我的root!=NULL而且k==1的话那就返回1

不然的话就返回左子树第k-1层节点的数目+右子树第k-1层节点的数目

int BTreeLevelSize(BTNode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	return BTreeLevelSize(root->left, k - 1) + BTreeLevelSize(root->right, k - 1);

}

3.9 查找某个值对应的节点

查找某个值对应的节点

如果root==NULL,返回NULL

如果root->val==val,返回root

既然root->val!=val,那么就去左右子树分别查找

如果在左子树查走到了,那么就返回左子树当中找到的那个节点

如果在右子树查走到了,那么就返回右子树当中找到的那个节点

至于为什么要判断左右子树查找到结果不为NULL呢

因为左子树如果结果是NULL,不然直接返回NULL

还有右子树没有查找

右子树的结果的话可以不用判NULL,因为这个时候根节点和左子树当中都没有找到val,那么就剩下右子树了

如果右子树还没有的话,那就没有,返回NULL,有的话就返回节点

BTNode* BTreeFind(BTNode* root, BTreeDataType val)
{
	if (root == NULL)
		return NULL;
	if (root->val == val)
		return root;
	BTNode* leftNode = BTreeFind(root->left,val);
	if (leftNode != NULL)
		return leftNode;
	BTNode* rightNode = BTreeFind(root->right, val);
	if (rightNode != NULL)
		return rightNode;
	return NULL;

}

3.10 判断是否为满二叉树

和层序遍历的思想一样,需要一个队列来进行辅助

首先将根节点root放到队列当中
如果在中途遇到了NULL的话,那就跳出循环
如果没遇到NULL的话,那就带下一层子树进来

遇到NULL结束入队列,并且检查队列当中是否还有有效元素(NULL除外)

如果全是NULL,那就代表这个二叉树完全二叉树

不然的话就不是

bool IsComplete(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 != NULL)
		{
			QueueDestroy(&q);
			return false;
		}

	}
	QueueDestroy(&q);
	return true;
}

3.11 二叉树的销毁

二叉树的销毁逻辑是和后序遍历一样的

void BTreeDestroy(BTNode* root)
{

	if (root == NULL)
		return;
	BTreeDestroy(root->left);
	
	BTreeDestroy(root->right);

	free(root);

}

4、感谢与交流✅

🌹🌹🌹如果大家通过本篇博客收获了,对二叉树有了新的了解的话
那么希望支持一下哦如果还有不明白的,疑惑的话,或者什么比较好的建议的话,可以发到评论区,
我们一起解决,共同进步 ❗️❗️❗️
最后谢谢大家❗️❗️❗️💯💯💯

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

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

相关文章

公司私服Maven踩坑,项目配置都OK但是包就是下载不下来【已解决】

我的问题是公司的私服Maven下载不下来,因为公司保密协议,这里用阿里云为例! 具体的至少参考:(32条消息) 这篇博文只讲MirrorOf_Java软件工程师的博客-CSDN博客 1:Java的Maven爆红了就找到资源库,然后把对于…

2.10messagebox弹窗

2.10messagebox弹窗 messagebox部件 其实这里的messagebox就是我们平时看到的弹窗。 我们首先需要定义一个触发功能,来触发这个弹窗 这里我们就放上以前学过的button按钮 tk.Button(window, texthit me, commandhit_me).pack()通过触发功能,调用messa…

超高性能协议框架fury完爆protostuff(附性能测试对比)

简单介绍: 序列化框架是系统通信的基础组件,在大数据、AI 框架和云原生等分布式系统中广泛使用。当对象需要跨进程、跨语言、跨节点传输、持久化、状态读写、复制时,都需要进行序列化,其性能和易用性影响运行效率和开发效率。 Fury 是一个基于…

3.2.18 DIR函数的补充说明

【分享成果,随喜正能量】人与人之间都是相互的,你给人搭桥,别人为你铺路;你让人难堪,别人给你添堵。。 我给VBA的定义:VBA是个人小型自动化处理的有效工具。利用好了,可以大大提高自己的劳动效…

【小梦C嘎嘎——启航篇】C++ 基础中的精华(二)

【小梦C嘎嘎——启航篇】C 基础中的精华(二)😎 前言🙌1、引用的使用场景1.1 做参数1.2 做返回值 2、const修饰 的引用2.1 权限上的探讨2.1.1权限放大2.1.2 权限平移2.1.3 全新缩小 4、函数重载的延伸条件编译: 条件编译…

自定义类型详解(C语言)

自定义类型 一. 结构体1.1 什么是结构体1.2 结构体的声明1.3 特殊的声明1.4 结构体的自引用1.5 结构体变量的定义和初始化1.5.1 结构体变量的定义1.5.2 结构体变量的初始化 1.6 结构体内存对齐1.6.1 为什么存在内存对齐 1.7 修改默认对齐数1.8 结构体传参 二. 位段2.1 什么是位…

PVE安装好后拔显卡后连接不了网络

目录 前因 原因 解决办法 前因 前几天装了个​Proxmox​ ve当做一个服务器7*24开机 但是由于转好系统后,显卡就不需要了 加上它耗电的原因(我的gtx650平时空载有10w左右的功耗) 我在想拔显卡拔了,我用xshell进行ssh连接不就…

MVCC:多版本并发控制

MVCC 1. MVCC是什么2. 快照读和当前读2.1 快照读2.2 当前读 3. Read View3.1 Read View中含有什么内容3.2 ReadView的规则 4. MVCC整体操作流程 1. MVCC是什么 MVCC(Multi Version Concurrency Control),多版本并发控制;MVCC用于…

【数学建模】——相关系数

第一部分:皮尔逊相关系数的计算以及数据的描述性统计 本讲我们将介绍两种最为常见的相关系数:皮尔逊person相关系数和斯皮尔曼spearman等级相关系数。它们可以用来衡量两个变量之间的相关性的大小,根据数组满足的不同条件,我们要选…

linux图形界面总结——X、Xorg、WM、QT、GTK、KDE、GNOME的区别与联系

文章目录 一、 linux图形界面二、X协议三、Xfree86 Xorg四、WM(window manager:窗口管理器)五、X协议的Client端实现六、KDE、GNOME、QT和GTK直接关系七、参考: 一、 linux图形界面 linux本身没有图形界面,linux现在的图形界面的实现只是linux下的应用程…

网络类型及数据链路层协议

目录 网络类型的分类 数据链路层协议 MA网络以太网协议 P2P网络 HDLC ---高级数据链路控制协议 更改链路协议的方法 HDLC数据帧封装结构 PPP---点到点协议 PPP协议的优点 PPP数据帧封装结构 PPP会话的搭建 链路建立阶段---LCP建立 认证阶段 网络层协议协商阶段--- NCP协商 网络…

大型风电叶片研发项目管理体系建设实践︱中车时代新材PMO负责人姚运帅

中车株洲时代新材料科技股份有限公司风电运维事业部总经理、PMO负责人姚运帅先生受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾,演讲议题:大型风电叶片研发项目管理体系建设实践。大会将于8月12-13日在北京举办,敬请关注&#xff01…

sqlserver 存储过程当中如何实现增删改查

--声明存储过程 新增编辑 ALTER procedure [dbo].[Eng_MyAddOrEdtADPro] My_Cocode int, Type int, -- --1 新增 2 编辑 My_KeyId uniqueidentifier, My_PCode int, My_SCode int, My_PName nvarchar(36), My_SName nvarchar(36), My_Orde…

IPUU的小工具拍了拍你(下)

IPUU是埃文科技旗下的综合性IP查询网站,提供多维度的IP数据信息。通过在线查询,用户可以获取目标IP地址的详尽信息,包括位置属性、网络属性、风险属性以及业务属性等,同时还可以查询域名信息。无论您是需要查看某个IP地址归属地&a…

1.13 通过aop日志监控service执行时间

步骤1&#xff1a;添加aop依赖包 <!-- aop切面 依赖--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency>步骤2&#xff1a;创建AOP日记监控记录切面类 …

微服务架构——配置中心

「配置中心」&#xff0c;顾名思义&#xff0c;就是用来统一管理项目中所有配置的系统。虽然听起来很简单&#xff0c;但也不要小瞧了这个模块。如果一个中型互联网项目&#xff0c;不采用配置中心的模式&#xff0c;一大堆的各类配置项&#xff0c;各种不定时的修改需求&#…

TMS Aurelius v5.15 Source Crack

TMS Aurelius v5.15 Source Crack 面向Delphi的ORM框架&#xff0c;完全支持数据操作、复杂和高级查询、继承、多态等。。。 功能详细信息 支持多个数据库服务器(MS SQL Server、Firebird、MySQL、DB2、Interbase、Oracle等) 支持多个数据库访问组件(dbExpress、AnyDac、SQLDir…

Linux驱动开发:设备树dts详解

前言&#xff1a;掌握设备树是 Linux 驱动开发人员必备的技能&#xff01;因为在新版本的 Linux 中&#xff0c;ARM 相关的驱动全部采用了设备树(也有支持老式驱动的&#xff0c;比较少)&#xff0c;最新出的 CPU 其驱动开发也基本都是基于设备树的&#xff0c;比如 ST 新出的 …

Stable Diffusion + EbSynth + ControlNet 解决生成视频闪烁

一、安装 1.1、安装ffmpeg 下载地址&#xff1a; 解压&#xff0c;配置环境变量 E:\AI\ffmpeg\bin 检查是否安装成功 1.2、安装SD的 EbSynth 插件 插件地址 https://github.com/s9roll7/ebsynth_utility 报错&#xff1a;ModuleNotFoundError: No module named extension…

后端查询出的数据库数字自动补零和不补零

select CAST(YTD_CHANGE*100 as decimal(18,1)), round(YTD_CHANGE*100,1) from RP where data_date 20211231补零 round(PYTD_CHANGE_PER*100,1)不补零 CAST(PYTD_CHANGE_PER*100 as decimal(18,1))