二叉树链式结构的实现——C语言

news2025/2/23 11:56:47

目录

一、提前说明

二、二叉树的遍历 

2.1前序遍历

2.2中序遍历

2.3后序遍历

2.4代码 

三、二叉树结点个数 

3.1整体思路

3.2代码 

四、二叉树叶子结点个数 

4.1整体思路

4.2代码 

五、二叉树的高度(深度)

5.1整体思路

5.2代码

六、二叉树第k层节点个数

6.1整体思路: 

6.2代码 

七、二叉树查找值为x的节点

7.1整体思路 

7.2代码 

八、二叉树的创建

8.1整体思路​编辑

8.2代码 

九、二叉树的销毁

十、二叉树的层序遍历 

10.1层序遍历的概念 

​编辑10.2整体思路 

 10.3代码

10.4番外篇

十一、检查二叉树是否为完全二叉树

11.1整体思路

11.2代码


一、提前说明

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。我们在这里先手动构建一棵“死树”,学完基本操作以后我们再来重新构建。

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}TreeNode;

TreeNode* CreateTreeNode(int x)
{
	TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
	assert(node);

	node->data = x;
	node->left = NULL;
	node->right = NULL;

	return node;
}

TreeNode* CreateTree()
{
	TreeNode* node1 = CreateTreeNode(1);
	TreeNode* node2 = CreateTreeNode(2);
	TreeNode* node3 = CreateTreeNode(3);
	TreeNode* node4 = CreateTreeNode(4);
	TreeNode* node5 = CreateTreeNode(5);
	TreeNode* node6 = CreateTreeNode(6);
	TreeNode* node7 = CreateTreeNode(7);


	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;
	node5->right = node7;

	return node1;
}

二、二叉树的遍历 

遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础

2.1前序遍历

 前序遍历(Preorder Traversal)——访问结点的操作发生在遍历其子树之前。

2.2中序遍历

中序遍历(Inorder Traversal)——访问结点的操作发生在遍历其子树之中(间)。

2.3后序遍历

后序遍历(Postorder Traversal)——访问结点的操作发生在遍历其子树之后。

2.4代码 

void PreOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

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

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

紧接着我们用我们的“死树”来验证一下我们的代码:

我们看我们的二叉树其实是非常丑的,但是我们通过验证也证明了我们代码的正确性。

而且在验证的时候还有一个小插曲:当我发现实际和输出不对等时,我没有怀疑代码的正确性,而是发现我的二叉树图画错了。

三、二叉树结点个数 

3.1整体思路

求结点个数那么遍历整个二叉树肯定是必要的,这就是为什么说遍历是最重要的操作的原因 

我们可以看出,如果root为NULL时,返回0,否则都是向上返回左右结点之和,那么我们就可以直接相加,还要加上它本身。

3.2代码 

int BinaryTreeSize(TreeNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

四、二叉树叶子结点个数 

叶结点或终端结点:度为0的节点称为叶节点

4.1整体思路

我们的想法是上一个函数的基础上修改一下返回条件:

4.2代码 

int BinaryTreeLeafSize(TreeNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

五、二叉树的高度(深度)

5.1整体思路

首先要判断根是否存在,如果根存在,继续向下遍历,再次遍历的时候,先判断其左右子树是否存在,若存在,再遍历其左右子树,此时就不用再进行根判断了,根判断的代码只在进入函数时有效执行。 以下是我们的大致思路,但是递归的图着实太难画了,实在不理解可以仔细参照文字或者自己画: 

在画图的过程中我们也发现了,我们每次都要返回左右子树遍历后的较大值,如何做?


这时我们可以借用C语言库中的较大值函数,而且可以进行代码的简化: 

5.2代码

int BinaryTreeHeight(TreeNode* root)
{
	if (root == 0)
	{
		return 0;
	}
	return fmax(BinaryTreeHeight(root->left), BinaryTreeHeight(root->right)) + 1;
}

六、二叉树第k层节点个数

6.1整体思路: 

6.2代码 

int BinaryTreeLevelKSize(TreeNode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
		return 0;
	if (k == 0)
		return 0;
	else if (k == 1)
		return 1;
	else
		return BinaryTreeLevelKSize(root->left, k - 1) + 
		BinaryTreeLevelKSize(root->right, k - 1);
}

七、二叉树查找值为x的节点

7.1整体思路 

我认为思路的重点是怎么递归左右子树并当返回其中不为空的值。 

我们要如何验证代码的正确性呢?在return 0处打断点:

另外我们的printf要用p%打印出地址,观察监视的值与输出是否相同。

7.2代码 

TreeNode* BinaryTreeFind(TreeNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	else
	{
		TreeNode* leftans = BinaryTreeFind(root->left, x);
		TreeNode* rightans = BinaryTreeFind(root->right, x);
		return leftans == NULL ? rightans : leftans;
	}
}

八、二叉树的创建

我们以前序遍历为例子,假设我们提供一个数组“123NN8NN45N7NN6NN”,需要以前序遍历来恢复这棵二叉树,我们要怎么办呢?

8.1整体思路

其中需要注意的点是,因为我们要辨别空指针,所以我们传的是一个 char 数组,因为我们 root 接收的永远是数组中的元素,所以我们要有一个能每次递归都改变值的下标 i ,所以我们使用了传址调用的方式来调用 i 

但是我们代码的正确性怎么检查呢?各位可以专门写一个打印函数,把它打印出来,我就偷个懒,打了断点来监视,根据监视画了图:

大家看这个图是不是有点眼熟(虽然我画的比较丑),但前序遍历的数组我就是抄之前的代码,现在我把之前的“死树”粘贴过来,大家对比一下,根据监视窗口画出来的树和实际的树是不是完全一样? 

8.2代码 

TreeNode* CreateTree(char* a, int* i)
{
	if (a[*i] == 'N')
	{
		(*i)++;
		return NULL;
	}
	TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
	if (root == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	root->data = a[*i];
	(*i)++;
	root->left = CreateTree(a, i);
	root->right = CreateTree(a, i);
	return root;
}

九、二叉树的销毁

二叉树的销毁就容易多了,我们直接看代码吧!

void DestroyTree(TreeNode* root)
{
	if (root == NULL)
		return;
	DestroyTree(root->left);
	DestroyTree(root->right);
	free(root);
	root = NULL;
}

十、二叉树的层序遍历 

10.1层序遍历的概念 

其实二叉树并非只有前中后序的遍历,还有层序遍历,即使我们的二叉树一层一层的输出:

10.2整体思路 

我们可以用队列来实现,当我们的根被Pop时,就把他的两个子结点Push入队,以此类推,大家看图吧!

我们先把之前写的队列的代码Copy一份放在我们现在的代码中:

接着我们就可以在右侧列表中看到我们的Queue了! 

因为我们使用的是队列,所以千万别忘了使用队列前的操作: 
因为我们使用队列存放的是二叉树的结点,所以我们的QueueDataType也要改为Tree Node* ! 
接着我们就可以写出下面的代码,经过测试,不论死树还是活树,我们的层序遍历都是对的

 10.3代码

void LevelOrder(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root != NULL)
		QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%c ", front->data);
		if (front->left)
			QueuePush(&q, front->left);
		if (front->right)
			QueuePush(&q, front->right);
	}
	QueueDestroy(&q);
}

10.4番外篇

我们来看一下我们的层序遍历的输出,这个输出好像有点丑,明明是层序输出,但是为什么那么没有层次感呢?下面请我们思考一下如何一层一层的输出呢?
我们来观察一个规律,当每一层的全部结点都被Pop出来时,它的下一次结点已经全部入队列了:

根据这个特点我们就可以每Pop一层后得到下一层的个数,然后继续Pop个数次: 
紧接着我们就可以看到我们的打印:

代码:

void LevelOrder(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root != NULL)
		QueuePush(&q, root);
	int LevelSize = 1;
		while (!QueueEmpty(&q))
		{
			while (LevelSize--)
			{
				TreeNode* front = QueueFront(&q);
				QueuePop(&q);
				printf("%c ", front->data);
				if (front->left)
					QueuePush(&q, front->left);
				if (front->right)
					QueuePush(&q, front->right);
			}
			printf("\n");
			LevelSize = QueueSize(&q);
		}
		
	QueueDestroy(&q);
}

十一、检查二叉树是否为完全二叉树

11.1整体思路

如果我们学完层序遍历后,不难发现,如果我们不对二叉树的左右子树做检查,让它们不管空或非空都入队列,当前面队列出空时,若队列中还有元素,则为不完全二叉树:

11.2代码

bool TreeComplete(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	int levelSize = 1;
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front == NULL)
			break;

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

	// 前面遇到空以后,后面还有非空就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;
}

完结散花! 

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

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

相关文章

天津python培训课程,普通人学python有用吗?

选择一个正确的行业对于个人的发展至关重要,在当今时代,IT行业高薪的特点吸引了越来越多的人转行“入坑”,而作为目前的热门编程语言,python也是很多人转行的选择。 Python培训学费选择 python培训班学费可能会因为培训机构所在…

利用document.write阻塞js文件加载

脚本加载失败如何重试【渡一教育】_哔哩哔哩_bilibili 注意需要添加3个转义符 利用document.write加载js的好处是会阻塞后续的js记载,必须等待当前js加载并执行完毕后,才会加载后续的js文件,保证了顺序性

计算机网络:可靠数据传输(rdt)、流水协议、窗口滑动协议

文章目录 前言一、Rdt1.Rdt1.02.Rdt2.03.Rdt2.14.Rdt2.25.Rdt3.0 二、流水线协议1.滑动窗口(slide window)协议发送窗口接收窗口正常情况下的2个窗口互动异常情况下GBN的2个窗口互动异常情况下SR的2窗口互动GBN协议和SR协议的异同 2.小结 总结 前言 Rdt…

Mac电脑如何安装git

一、简介 在Mac上安装Git之前,可以先使用git --version来查看一下是否安装了Git,因为Mac系统可能自带了Git,或者在你安装XCode(或者XCode的命令行工具)时,可能已经安装了 Git。 如果Mac还没有安装Git的话&…

做一个类似东郊到家的上门服务类系统有哪些功能?

上门服务系统是一款便捷的技师接单、上门提供理疗服务的软件。我们拥有优秀的开发团队,为您量身定制解决方案,价格合理,用心服务。 预约上门:该功能是预约上门推拿理疗按摩系统软件小程序APP的核心功能。消费者通过系统预约下单&a…

【带头学C++】----- 九、类和对象 ---- 9.5 初始化列表

目录 9.5 初始化列表 9.5.1 对象成员 代码: 9.5.2 初始化列表 9.5 初始化列表 9.5.1 对象成员 在类中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以是对象,叫做对象成员。 先调用对象成员的构造函数,再调用本身的构造函数…

C# .NET平台提取PDF表格数据,并转换为txt、CSV和Excel表格文件

处理PDF文件中的内容是比较麻烦的事情,特别是以表格形式呈现的各种数据。为了充分利用这些宝贵的数据资源,我们可以通过程序提取PDF文件中的表格,并将其保存为更易于处理和分析的格式,如txt、csv、xlsx,从而更方便地对…

2024年口碑比较好的猫罐头有哪些?2024年口碑比较好的猫罐头盘点

想必铲屎官都知道给猫咪长期吃主食罐头的好处了吧!主食罐头不仅营养丰富,还能让猫咪顺便补充水分。有时候猫咪食欲不佳,一罐主食罐头就能让它们胃口大开呢。 猫罐头侠登场!养猫这么久了我就把我吃的不错的猫罐头分享一下&#xf…

银行测试:什么是银行数据治理?如何进行有效的银行领域的实际应用?

在数字化时代,数据已经成为银行的重要资产,而数据治理则是确保数据质量、安全性和可用性的关键。那么,什么是银行数据治理?为什么我们需要银行数据治理?又如何进行有效的银行数据治理呢?又有哪些数据治理技…

万界星空科技智能工厂主要建设模式

由于各个行业生产流程不同,加上各个行业智能化情况不同,智能工厂有以下几个不同的建设模式。 第一种模式:是从生产过程数字化到智能工厂 在石化、钢铁、冶金、建材、纺织、造纸、医药、食品等流程制造领域,企业发展智能制造的内在…

SIT75176B:3.0~5.5V 供电,32 节点,10Mbps 半双工 RS485/RS422 收发器

SIT75176B 是一款 3.0V~5.5V 电源供电、总线端口 ESD 保护能力 HBM 达到 15kV 以上、总 线耐压范围达到 15V 、半双工、低功耗,功能完全满足 TIA/EIA-485 标准要求的 RS-485 收发器, 最多允许 32 个节点同时连接到总线。 SIT75176B 包…

2023年个人工作总结怎么写?工作任务完成自动记录的待办软件

2023年已经接近尾声,不少人已经开始期待新的一年到来了。不过对于大多数职场人士来说,最近还有一项让人头疼的任务需要完成,这就是撰写2023年个人工作总结。 那么年度个人工作总结怎么写呢?其实很简单,年度工作总结一…

Linux 基础知识整理(三)

Linux文件和目录 Linux系统是一种典型的多用户系统,不同的用户有不一样的地位和权限。为了保护系统的安全性,Linux系统对不同的用户访问同一文件(包括目录文件)的权限做了不同的规定。 root权限最高,可以通过ls -l 或…

Temu数据软件:如何使用Temu数据软件优化您的Temu店铺运营

在如今竞争激烈的电商市场中,了解市场趋势、优化产品和店铺运营、了解竞争对手等方面的数据分析变得至关重要。为了帮助Temu平台上的商家更好地了解市场和消费者需求,提高运营效果,Temu数据软件成为了一项强大的工具。本文将介绍一些建议的Te…

连接池打满,导致页面夯住

连接池打满,导致页面夯住 1、背景生产环境中访问系统,页面延迟卡顿 2、排产思路 1)、查看日志是排查问题的第一要素(个人认为);查看日志发现使用com.alibaba.druid.pool设置最大连接数为100,已…

2024年天津财经大学珠江学院专升本专业课考试《经济学》考试大纲

天津财经大学珠江学院2024年高职升本科专业课考试《经济学》考试大纲 一、本大纲系天津财经大学珠江学院2024年高职升本科《经济学》课程考试大纲。所列考试范围出自郑健壮、王培才主编的教材《经济学基础(第二版)》,清华大学出版社&#xf…

独立开发者都使用了哪些技术栈?

目录 一、前言 架构展示: 技术栈展示: 二、JNPF-JAVA-Cloud微服务 1.后端技术栈 2. 前端技术栈 Vue3技术栈 3. 数据库支持 一、前言 像独立开发者这类人群,也可以把他们理解为个人开发者/自由职业者。有一组数据显示,在美国&#…

FL Studio21.3破解版水果编曲软件下载

电子编曲需要什么软件?市面上的宿主软件都可以完成电子编曲的工作,主要适用电子音乐风格编曲的宿主软件有FL Studio、Ableton Live等。电子编曲需要什么基础?需要对于电子音乐足够熟悉、掌握基础乐理知识以及宿主软件的使用方法。 就我个人的…

Canal笔记:安装与整合Springboot模式Mysql同步Redis

官方文档 https://github.com/alibaba/canal 使用场景 学习一件东西前,要知道为什么使用它。 1、同步mysql数据到redis 常规情况下,产生数据的方法可能有很多地方,那么就需要在多个地方中,都去做mysql数据同步到redis的处理&…

使用gunicorn部署django项目时,发现静态文件加载失败问题

本文主要介绍如何配置Niginx加载Django的静态资源文件,也就是Static 1、首先需要将Django项目中的Settings.py 文件中的两个参数做以下设置: STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, static) 2、将 STATICFILES_DIRS [ os.p…