二叉树(接口函数的实现)

今天继续来分享的是二叉树,我们废话不多说,直接来看下面的几个接口函数,然后我们把他们实现,我们就掌握二叉树的二分之一(今天粉丝破千了,属实有点高兴了)。

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode* root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);

我们来看看第一个接口函数是创建,上来就是有点难度的接口,如果要实现这个数的样子的字符是"ABD##E#H##CF##G##"  那我们要创建出这样的一棵树,先要知道用前序遍历还是中序遍历这些,其实三种遍历方式都是可以实现的,这里用前序遍历比较好理解,我们写这个接口函数的时候,得自己创造节点,所以有一个CreateNode的函数得写好,其次就是我们来观察这个函数是有返回值的,按照前序遍历的方法就是根节点  左孩子  右孩子这样的写法,因此我们的代码就是下面的这个。

BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	BTNode* root = CreateNode(a[(*pi)++]);
	
	root->left = BinaryTreeCreate(a, n, pi);
	root->right = BinaryTreeCreate(a, n, pi);
	return root;
}

这里需要注意的一点就是pi是传递的是地址,pi需要我们进行解引用才可以的,我们的pi代表的意思就是数组的下标,然后我们来考虑为什么传的是指针,首先就是我们函数栈帧的开辟,局部变量都是随着函数栈帧的销毁而销毁的,其次还是那据最重要的话,形参是实参的一份临时拷贝。每次如果改变i的话只是在这个函数栈帧中起到作用。

void BinaryTreeDestory(BTNode* root)

我们回创建节点,那肯定要会销毁节点,我们销毁节点就可以用后序遍历的思路,其实很简单,遇到空就可以返回,我们就直接给代码,但是我们这个代码会有点问题,但是可以加点代码来解决。

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

这里的问题就是还是形参是实参的一份临时拷贝,但是我们的root还能释放的原因是root虽然是形参,但是还是指向这片空间的,所以我们需要注意的就是在这个函数调用结束的时候加上这句就行了。

当然我们也可以传二级解决,但是我觉得没啥必要。

int BinaryTreeSize(BTNode* root)

下一个接口函数就是我们来统计这颗树有多少个节点,到NULL就是开始返回,这里的子问题就是我们如何来统计我们左子树和右子树节点的个数,如果不为空就是有节点,我们只要返回节点个数,因为子问题就是左子树和右子树的个数,所以我们这里就可以写出下面的代码.

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 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层的节点个数。

int BinaryTreeLevelKSize(BTNode* root, int k)

这个首先我们得知道我们的树是怎么个样子的,下面这个图就是简易版这个题目的树,我们要来统计k层的节点个数。

首先就是我们把他分成子问题就是我们只要到K层才开始统计,遇到空就返回,其实这样我们的代码也就是可以直接写出来,我们递归左子树和右子树然后进行相加就可以完成我们的代码了。

int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}

	if (k == 1)
	{
		return 1;
	}
	k--;
	return BinaryTreeLevelKSize(root->left, k) +
		BinaryTreeLevelKSize(root->right, k);

}

 这个需要我们注意就是只有当 k == 1的时候才是我们要来统计的时候,那我们就得让k--,并且能够递归到下一层,这样我们的代码也就能完成了。

BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

这个就是找我们内容是x的节点,但是我们返回的时候是返回这个节点,返回的时候可能会出现问题,我们可以这样想这个题目,我们如果没找到就是返回空指针,如果找到了返回这个节点,但是我们要去左右子树递归找,我们不知道什么时候是找到以及他返回的是什么,我们可以保存下这个节点,这样我们再次判断就可以知道哪个是我们需要的。

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* ret1 = BinaryTreeFind(root->left, x);
	BTNode* ret2 = BinaryTreeFind(root->right, x);
	if (ret1)
	{
		return ret1;
	}
	if (ret2)
	{
		return ret2;
	}
	return NULL;
}

后面的三个接口函数就相比起来要简单很多,我们可以来看看,这里呢我只写一个,另外两个真的太简单了,我就不写了。

oid BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);

我们要来实现前序 中序和后序遍历,我们直接上手,遇到空就返回并打印其内容。

void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("#");
		return;
	}
	printf("%c", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}

下面的两个接口函数才是最重要的,他要用到我们的队列先进先出的特点,因为C语言没有队列,所以我们需要手搓一个,还好我早有准备,出来吧队列!!!!

#include"Queue.h"
 

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

void QueuePush(Queue* pq, QueueDateType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	newnode->next = NULL;
	newnode->val = x;
 

	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;

}


void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	QNode* tmp = pq->head;
	pq->head = pq->head->next;
	free(tmp);
	tmp = NULL;

	if(pq->head == NULL)
		pq->tail = NULL;
	pq->size--;
}

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;

}

int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

QueueDateType QueueFront(Queue* pq)
{
	assert(pq);

	
	
	return pq->head->val;
	
	
}

QueueDateType QueueBack(Queue* pq)
{
	assert(pq);
	return pq->tail->val;
}


void QueueDestory(Queue* pq)
{
	assert(pq);
	while (pq->head != pq->tail)
	{
		QNode* del = pq->head;
		pq->head = pq->head->next;
		free(del);
	}
	free(pq->tail);
}

这个队列可以放心的使用,那我们就来看看层序遍历上级怎么个事。

void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if(root)
		QueuePush(&q, root);
	int leafsize = 1;
	while (!QueueEmpty(&q))
	{
		while (leafsize--)
		{
			BTNode* 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");
		leafsize = QueueSize(&q);
	}

}

层序遍历的关键就是这个队列是不是为空,以及队列里有几个数据,所以我们这里必须得做的就是有一个leafsize这个来统计,如果没有这个的话我们无法实现递归的思路,然后最重要的一个就是队列的判空,这个也是特别重要。

那我们层序遍历的思路就是我们把根节点先入进去,出根节点的时候把孩子节点也是插入队列,我们需要一个数字来统计在该层有几个数据,所以这个时候就有了我们leafsize。时刻更新leafsize和入孩子节点就行了。

那大家再来看看我们如何来判断满二叉树的思想。

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

	int levelSize = 1;
	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/240954.html

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

相关文章

Epicypher—CUTANA™ ChIC/CUTRUN Kit

核酸酶靶向切割和释放 (CUT&RUN)技术是由Steven henikoff博士团队开发的一种染色质图谱分析方法,基于Ulrich Laemmli博士的染色质免疫切割技术 (ChIC),融合蛋白A与微球菌核酸酶 (pA-MNase),选择性原位切割与抗体结合的染色质。在CUT&…

漏洞补丁存在性检测技术洞察

1、 漏洞补丁存在性检测技术是什么? 漏洞补丁存在性检测技术通俗的理解就是检测目标对象中是否包含修复特定已知漏洞的补丁代码,目标检测对象可能是源码,也能是二进制文件。 2、 漏洞补丁存在性检测技术业务背景 补丁检测这个问题背景是产品…

uview1 的u-tabs组件在微信小程序中会出现横向滚动条

uview1 的u-tabs组件在微信小程序中会出现横向滚动条,真机才会生效,微信开发者工具没问题包括官方示例也会 原因:未屏蔽微信小程序的滚动条 解决办法:uview-ui中uview-ui/components/u-tabs/u-tabs.vue文件把h5屏蔽滚动条的条件编…

基于ssm的线上课程管理系统论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本线上课程管理系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息…

mysql,树形结构表中,查询所有末节点数据(叶子结点)

需求:在一个可以存放多级目录的表中,查询出某个课程目录下所有末节点(因为只有末节点可以挂载资源) 例如下图: 其中 1.11.2.12.1 都是末节点,因为他们已经没有下一级了 catalog表中重要字段有:c…

对Spring源码的学习:基于XML文件配置的开发流程

目录 BeanFactory开发流程 ApplicationContext BeanFactory与ApplicationContext对比 基于XML方式的Bean的配置 自动装配 BeanFactory开发流程 这里的第三方指的是Spring提供的BeanFactory,Spring启动时会初始化BeanFactory,然后读取配置清单&#…

[山东大学操作系统课程设计]实验六

0.写在前面: 事先说明一点,在实验六开始,绝大多数的问题我应该都无法解释了,因为我自己做这个实验都是有点困难的,所以在接下来我不会过多阐述原理上的东西,只交待这个东西是怎么做的。 另外实验六七又是…

关于“Python”的核心知识点整理大全17

目录 ​编辑 8.3.4 结合使用函数和 while 循环 greeter.py 8.4 传递列表 greet_users.py 8.4.1 在函数中修改列表 printing_models.py 8.4.2 禁止函数修改列表 要将列表的副本传递给函数,可以像下面这样做: 往期快速传送门👆&#x…

java SSM教师工作量管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM 教师工作量管理系统是一套完善的web设计系统(系统采用SSM框架进行设计开发,springspringMVCmybatis),对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要…

【数学建模】《实战数学建模:例题与讲解》第十二讲-因子分析、判别分析(含Matlab代码)

【数学建模】《实战数学建模:例题与讲解》第十二讲-因子分析、判别分析(含Matlab代码) 基本概念时间判别费歇判别贝叶斯判别 习题10.31. 题目要求2.解题过程3.程序4.结果 习题10.6(1)1. 题目要求2.解题过程——对应分析…

OLED屏幕,如何成为商显主流

OLED屏幕在商显领域的应用逐渐增加,成为商显主流的原因主要有以下几点: 显示效果优异:OLED屏幕具有自发光原理,色彩鲜艳、对比度高、视角广,能够提供更好的视觉体验。在商业展示、广告宣传等场景中,OLED屏幕…

Feign-自定义配置

目录 一、自定义Feign配置 二、修改日志级别 方式一:application配置文件方式 方式二:java代码方式 三、总结 一、自定义Feign配置 二、修改日志级别 配置Feign日志有两种方式 方式一:application配置文件方式 (1&#xff09…

Java服务占用过高CPU排查思路

一、背景说明 如果线上启动的Java服务占用过高的CPU,我们通过top命令是可以查看到的。 那么问题来了,如果通过top命令查看到是因为java服务引起的占用过高的CPU时间,该如何进行详细的排查呢?换句话说就是如何定位问题发生在代码的…

Vue中使用echarts@4.x中国地图及AMap相关API的使用

一、此 demo 实现的基本功能 1.中国地图的显示 2.地图点击下钻的功能 3.地图相关组件的使用,例 tooltip… 二、实现思路 初始使用下载本地的中国 geo 格式的 json 数据来绘制地图,点击某一区划(例:山东省)时&#xff0…

PySpark大数据处理详细教程

欢迎各位数据爱好者!今天,我很高兴与您分享我的最新博客,专注于探索 PySpark DataFrame 的强大功能。无论您是刚入门的数据分析师,还是寻求深入了解大数据技术的专业人士,这里都有丰富的知识和实用的技巧等着您。让我们…

Centos7 配置Git

随笔记录 目录 1, 新建用户 2. 给用户设置密码相关操作 3. 为新用户添加sudo 权限 4. 配置Git 4.1 配置Git 4.2 查看id_ras.pub 5, 登录Git 配置SSH 秘钥 6. Centos7 登录Git 7. clone 指定branch到本地 8. 将新代码复制到指定路径 9. 上传指定代码 …

2023年11月国产数据库大事记-墨天轮

本文为墨天轮社区整理的2023年11月国产数据库大事件和重要产品发布消息。 11月国产数据库大事记 TOP10 11月国产数据库大事记(时间线) 11月1日消息,近日,由金仓数据库支撑的某大型运营商B域一级BOSS枢纽系统顺利升级上线。金仓数…

四川技能大赛——2023年四川网信人才技能大赛(网络安全管理员赛项)决赛

四川技能大赛——2023年四川网信人才技能大赛(网络安全管理员赛项)决赛 文章目录 四川技能大赛——2023年四川网信人才技能大赛(网络安全管理员赛项)决赛C1-比64少的bas - DONEC2-affine - DONEC3-简单的RSA - DONEM1-不要动我的f…

关于String.Format混合$符号格式化引发的问题

之前一个老项目是用string.Format()进行格式化的,.net 4.5之后的版本 引入 $"字符串" 格式化标识符, 如下代码: string barcode "1234567{#0.000}ABCDE";barcode "12345START{0:#000}ABCDE";try{string sFo…

【网络安全技术】电子邮件安全PGP,SMIME

一、PGP(Pretty Good Privacy) PGP是一种邮件加密手段,他在发邮件一方加密,然后发给发送方邮件服务器,发送方邮件服务器再发送给接收方邮件服务器,然后接收方再从接收方邮件服务器pop出来,这整…