数据结构——二叉树(续集)

news2024/11/25 11:07:09


♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥

♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥

♥♥♥我们一起努力成为更好的自己~♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥



在上一篇博客我们说到了树的基本概念,以及顺序结构二叉树的实现及运用,我们知道二叉树还可以通过链式结构来实现,这一篇博客带着大家一起继续在二叉树的世界里面遨游~(提前透露一下~这一篇博客更多的是体会递归的暴力美学~)

实现链式结构二叉树

既然是链式结构的二叉树,结合我们前面的经验那肯定离不开链表了~首先来看看链式结构二叉树的结构~

结构

用链表来表示⼀棵二叉树,即用链来指示元素之间逻辑关系。 通常的方法是链表中每个结点由三个域组 数据域和左、右指针域 ,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
结构代码:
//定义二叉树结点结构
typedef char BTDataType;//保存的数据类型
typedef struct BinaryTreeNode
{
	BTDataType data;//保存的数据
	struct BinaryTreeNode* left;//左孩子结点的地址
	struct BinaryTreeNode* right;//右孩子结点的地址
}BTNode;
有了一个结点的结点代码,但是因为二叉树的创建方式比较复杂,所以我们这里手动创建一个二叉树进行实现~

手动创建二叉树

这里呢,我们创建一个比较复杂的二叉树,既然二叉树也是由一个个结点组成的,那么我们创建二叉树就需要创建一个个结点再进行连接起来,我们可以看到,上面的二叉树一共有6个结点,所以我们需要创建6个结点再进行连接~注意:这里我们保存的是字符,所以保存数据类型改为char 

代码:

#include"Btree.h"

//创建一个结点代码
BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(1);
		//创建失败,退出程序
	}
	//创建成功
	node->data = x;
	node->left = node->right = NULL;
	//返回创建的结点地址
	return node;
}
//创建二叉树
BTNode* BTreeCreate()
{
	BTNode* n1 = BuyNode('A');
	BTNode* n2 = BuyNode('B');
	BTNode* n3 = BuyNode('C');
	BTNode* n4 = BuyNode('D');
	BTNode* n5 = BuyNode('E');
	BTNode* n6 = BuyNode('F');

	//通过逻辑关系连接结点
	n1->left = n2;
	n1->right = n3;

	n2->left = n4;

	n3->left = n5;
	n3->right = n6;

	//返回头结点
	return n1;
}
void test1()
{
	//手动创建一个二叉树
	BTNode* head = BTreeCreate();
}

手动创建的二叉树也就完成了,接下来我们想一想怎么对这个二叉树进行操作呢?每一颗子树的末尾都会走到空,显然,我们这里不能像单链表那样进行遍历,那我们应该怎么样遍历二叉树呢?

二叉树的遍历

遍历方式

二叉树的遍历方式有三种:

前序/中序/后序的递归结构遍历:

1.前序遍历(Preorder Traversal 亦称先序遍历):
访问根结点的操作发生在遍历其左右子树之前
访问顺序为:根结点、左子树、右子树
2.中序遍历(Inorder Traversal):
访问根结点的操作发生在遍历其左右子树之中(间)
访问顺序为:左子树、根结点、右子树
3.后序遍历(Postorder Traversal):
访问根结点的操作发生在遍历其左右子树之后
访问顺序为:左子树、右子树、根结点

我们可以发现,二叉树不同的遍历方式最大的区别就是访问根结点的顺序不同最开始访问根结点就是前序遍历,中间访问根结点就是中序遍历,最后访问根结点就是后序遍历

知道了这三种遍历方式,那么我们来看看这个二叉树不同遍历方式下有什么结果呢?

前序遍历:

A B D NULL NULL NULL C E NULL NULL F NULL NULL

中序遍历:

NULL D NULL B NULL A NULL E NULL C NULL F NULL

后序遍历:

NULL NULL D NULL B NULL NULL E NULL NULL F C A

不知道你的答案有没有正确呢?这种遍历有一种方式就是先全局再局部进行遍历,往下走就是子树也按照规定顺序进行遍历就可以了。

比如举中序遍历的例子:

首先我们知道根结点在中间,那么我们首先就可以确定整体的样子,再在子数中操作


(B子树)A(C子树)


(……B……)A (……C……)


(…D…B NULL)A (…E…C…F…)


NULL D NULL B NULL A NULL E NULL C NULL F NULL

最后我们就可以得到中序遍历的结果:NULL D NULL B NULL A NULL E NULL C NULL F NULL

其他的遍历方式操作方法类似,当然也可以画图来理解遍历的过程~

知道了这三种遍历方式,我们怎么用代码实现呢?

这里就要开始我们递归的暴力美学了~我们可以发现二叉树是一层层往下面递进的~相当于有一个递归的过程~

前序遍历

//前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		//如果为空,直接打印返回
		printf("NULL ");
		return;
	}
	//递归遍历,最开始打印根结点数据
	printf("%c ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

看看打印结果~

我们可以发现和我们前面分析的结果是一模一样的,是不是很神奇~这里我们来看看这里的递归是如何达到我们想要的效果的~

解释:如果根结点为空就打印NULL,如果根结点不为空就打印根结点,然后再同样的方式遍历左孩子结点和右孩子结点(左孩子结点和右孩子结点也就是子树的根结点),这里的递归也就是先往下面一层层递归然后再回退~画图分析~

怎么样~有没有体会到递归的暴力美学呢?有了这一个基础,接下来我们的中序遍历和后序遍历相信就简单了~

中序遍历

//中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		//如果为空,直接打印返回
		printf("NULL ");
		return;
	}
	//根结点在中间打印
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}

后序遍历

//后序遍历
void LasOrder(BTNode* root)
{
	if (root == NULL)
	{
		//如果为空,直接打印返回
		printf("NULL ");
		return;
	}
	//根结点最后打印
	LasOrder(root->left);
	LasOrder(root->right);
	printf("%c ", root->data);
}

这里三种遍历相信问题不大,递归的魅力有没有体会到呢?接下来我们继续使用递归对二叉树进行操作~

二叉树操作

还是以这一个二叉树为例

二叉树结点个数

这里我们来巧妙的使用递归方法求结点个数~

原理:

总结点个数 = 1 + 左子树结点个数 +右子树结点个数(根结点为空返回0)

代码:

// 二叉树结点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

二叉树叶子结点的个数

1.什么是叶子结点?

度为0,左孩子结点和右孩子结点都为NULL

2.原理

叶子结点总个数=左子树叶子结点个数 +右子树叶子结点个数

代码:

// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{
	//root不为空,避免对空指针解引用
	//左孩子结点和右孩子结点都为NULL是叶子结点
	if (root != NULL && root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	//如果root为NULL,返回0
	if (root == NULL)
	{
		return 0;
	}
	//叶子结点总个数=左子树叶子结点个数 +右子树叶子结点个数
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

二叉树第k层结点个数

原理:

二叉树第k层结点个数 =  左子树第k层结点个数 +右子树第k层结点个数

代码:

// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	//如果为空,没有结点,返回0
	if (root == NULL)
	{
		return 0;
	}
	//root不为空,并且走到第k层
	//后面传参k-1,到第k层也就是k=1
	//例:求第三层,从第一层向下走2层就可以到第三层
	if (k == 1)
	{
		return 1;
	}
	//二叉树第k层结点个数 =  左子树第k层结点个数 +右子树第k层结点个数
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

二叉树的深度/高度

思路:

二叉树的深度/高度 = 1 + Max(左子树深度/高度、右子树深度/高度最大值)

(如果为空返回0)

代码:

// 二叉树的深度/高度
int BinaryTreeDepth(BTNode* root)
{
	//如果root为空返回0
	if (root == NULL)
	{
		return 0;
	}
	//求左子树深度/高度
	int DepLeft = BinaryTreeDepth(root->left);
	// 求右子树深度/高度
	int DepRight = BinaryTreeDepth(root->right);
	//返回1+左子树深度/高度、右子树深度/高度最大值
	return 1 + (DepLeft > DepRight ? DepLeft : DepRight);
}

二叉树查找值为x的结点

既然需要查找结点,那么这里肯定离不开遍历二叉树了~

思路:

判断根结点root是否为要找的结点,如果是直接返回root,如果root为空就返回NULL,然后在左子树里面找,最后在右子树里面找,都没有找到就返回NULL。

代码:

// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	//判断根结点
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	//在左子树里面找
	BTNode* leftFind = BinaryTreeFind(root->left, x);
	//左子树找到结果不为空,返回
	if (leftFind)
	{
		return leftFind;
	}
	//在右子树里面找
	BTNode* rightFind = BinaryTreeFind(root->left, x);
	//右子树找到结果不为空,返回
	if (rightFind)
	{
		return rightFind;
	}
	//最后没有找到
	return NULL;
}

测试:

二叉树销毁

接下来就是二叉树的销毁了~

这里有一个需要注意的点是我们前面对二叉树操作并没有更改头结点,但是二叉树销毁,是每一个结点都需要销毁的,所以我们要传二级指针~

思路:遍历二叉树销毁,root为NULL直接返回,这里我们需要使用后序遍历(如果前面就把root置为空之后,我们是不能对空指针进行解引用的~就找不到左右孩子结点了~)

代码:(后序遍历销毁)

// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
	//为空直接返回,不需要销毁了
	if (*root == NULL)
	{
		return;
	}
	//后序遍历销毁——左右根
	//注意:二级指针传地址
	BinaryTreeDestory(&((*root)->left));
	BinaryTreeDestory(&((*root)->right));
	free(*root);
	*root = NULL;
}

我们可以看到二叉树销毁成功~

常见问题

这里二叉树的大部分操作已经完成了~接下来看一些常见问题~

1.不同函数特殊情况处理操作中为什么有的return NULL;有的return 0;有的直接return;

答:这就与函数的返回值有关了,我们可以看到有的函数返回值是int类型,有的是BTNode*类型,有的是void类型,这里返回的与返回值类型保持一致~

例:

int类型

BTNode*类型

void类型:

层序遍历

前面讲解了二叉树的三种遍历方式——前中后序遍历~除此之外,二叉树还有一种遍历方式也就是层序遍历~听这个名字是不是就是一层层的进行遍历呢?答案是的~

比如下面这个二叉树遍历结果为:A B C D E F

思路

那么我们怎么通过代码来实现这个过程呢?

这里就需要我们前面学到的数据结构知识——队列(不清楚的可以看看前面的文章:数据结构——栈和队列)

这里我们给出思路:

首先让根结点入队列,循环判断队列是否为空,如果队列不为空就取队头并且出队头,如果结点的左右孩子不为空就让结点的左右孩子入队列,如此循环

画图理解:

1.让根结点入队列(队尾入,队头出)

2.判断队列是否为空,队列不为空就取队头并且出队头,然后让结点的左右孩子入队列

当队列为空,这个循环就结束,我们可以看到这就是层序遍历的结果~

注意:

1.这里是结点入队列,所以队列保存的数据类型是struct BinaryTreeNode*!

有人可能会说不是将struct BinaryTreeNode重定义为BTNode了吗?是不是也可以写成

typedef BTNode* QueueDataType,答案是不可以这样写的,我们必须告诉操作系统这一个类型是struct这样一个结构,如果这样使用同样要在前面加上struct。

正确使用:

方法一:

typedef struct BinaryTreeNode* QueueDataType;

方法二:

typedef struct BTNode* QueueDataType;

这样看起来,我们还是推荐使用方法一~

2.在前面,我们已经实现过队列,有相应的头文件和源文件这里我们只需要包含头文件使用就可以了~

代码

知道了思路,接下来就是我们的代码实现了~

// 层序遍历
void LevelOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	//定义一个队列结构
	Queue Qu;
	//初始化
	QueueInit(&Qu);
	//让根结点入队列
	QueuePush(&Qu, root);
	//队列不为空,取队头打印数据,并且出队头
	while (!QueueEmpty(&Qu))
	{
		BTNode* top = QueueFront(&Qu);
		printf("%c ", top->data);
		//队头出队列
		QueuePop(&Qu);
		//如果左右孩子结点不为空入队列
		if (top->left)
		{
			QueuePush(&Qu, top->left);
		}
		if (top->right)
		{
			QueuePush(&Qu, top->right);
		}
	}
	//销毁
	QueueDestroy(&Qu);
}

我们可以看到达到了我们想要的效果~

如果我们也想要像前面打印NULL,代码就会有一些小变化~打印NULL,那么NULL也需要入队列~如果取队头为NULL,就直接打印,然后使用continue语句继续执行循环~

// 层序遍历2
//为空也入队列
void LevelOrder2(BTNode* root)
{
	//定义一个队列结构
	Queue Qu;
	//初始化
	QueueInit(&Qu);
	//让根结点入队列
	//为空也入队列,不需要判断
	QueuePush(&Qu, root);
	//队列不为空,取队头打印数据,并且出队头
	while (!QueueEmpty(&Qu))
	{
		BTNode* top = QueueFront(&Qu);
		//队头为空打印NULL并且NULL出队列
		if (top == NULL)
		{
			printf("NULL ");
			QueuePop(&Qu);
			continue;//继续执行循环
		}
		else
		{
			printf("%c ", top->data);
		}
		//队头出队列
		QueuePop(&Qu);
		//左右孩子结点入队列
		QueuePush(&Qu, top->left);
		QueuePush(&Qu, top->right);
	}
	//销毁
	QueueDestroy(&Qu);
}

这里的层序遍历就巧妙地将二叉树和队列结合在一起~

判断完全二叉树

画图分析思路

前面我们说到完全二叉树的特点是最后一层结点个数不一定达到最大~但是结点从左向右依次排列~

这里我们需要判断一个二叉树是不是完全二叉树应该怎么办呢?这里同样需要使用队列~这里我们来画图找完全二叉树和非完全二叉树的区别~

完全二叉树

1.根结点入队列

2.队列不为空,取队头出队头,将左右孩子结点入队列(这里结点为空也入,方便后面判断),如此循环,到取到top为NULL第一层循环结束,第二次循环条件依然是队列不为空,取剩下的队列元素

3.第二层循环取队头出队头

我们可以发现完全二叉树剩下的队列元素都是空~

接下来我们看看非完全二叉树进行同样的操作~

1.根结点入队列

2.队列不为空,取队头出队头,将左右孩子结点入队列(这里结点为空也入,方便后面判断),如此循环,到取到top为NULL第一层循环结束,第二次循环条件依然是队列不为空,取剩下的队列元素

3.第二层循环取队头出队头

我们可以看到非完全二叉树第二次循环取队头元素有不为空的结点~这就是它们之间的区别~

思路:首先让根结点入队列,第一次循环队列不为空,取队头出队头,将左右孩子结点入队列(这里结点为空也入,方便后面判断),如此循环,到取到top为NULL第一层循环结束,第二次循环条件依然是队列不为空,取剩下的队列元素,剩下的队列元素有不为空的说明是非完全二叉树~

代码

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	//借助队列
	Queue Qu;
	//初始化
	QueueInit(&Qu);
	//根结点入队列
	QueuePush(&Qu, root);
	while (!QueueEmpty(&Qu))
	{
		//取队头元素
		BTNode* top = QueueFront(&Qu);
		//出队列
		QueuePop(&Qu);
		//为空第一层循环结束
		if (top == NULL)
		{
			break;
		}
		//让左右孩子结点入队列
		QueuePush(&Qu, top->left);
		QueuePush(&Qu, top->right);
	}
	//第二层循环,队列不为空
	//继续取队头出队头,如果有不为NULL的说明是非完全二叉树
	while (!QueueEmpty(&Qu))
	{
		//取队头元素
		BTNode* top = QueueFront(&Qu);
		//出队列
		QueuePop(&Qu);
		if (top != NULL)
		{
			//返回前销毁队列!!!
			QueueDestroy(&Qu);
			return false;
		}
	}
	//剩下的全部为NULL,是完全二叉树
	return true;
	//销毁
	QueueDestroy(&Qu);
}

我们的代码达到了我们想要的效果~

总代码

BTree.h


//需要的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include"Queue.h"

//定义二叉树结点结构
typedef char BTDataType;//保存的数据类型
typedef struct BinaryTreeNode
{
	BTDataType data;//保存的数据
	struct BinaryTreeNode* left;//左孩子结点的地址
	struct BinaryTreeNode* right;//右孩子结点的地址
}BTNode;


//前序遍历
void PreOrder(BTNode* root);

//中序遍历
void InOrder(BTNode* root);

//后序遍历
void LasOrder(BTNode* root);

// 二叉树结点个数
int BinaryTreeSize(BTNode* root);

// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root);

// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);

// 二叉树的深度/高度
int BinaryTreeDepth(BTNode* root);

// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

// 二叉树销毁
void BinaryTreeDestory(BTNode** root);

// 层序遍历1
void LevelOrder1(BTNode* root);
// 层序遍历2
void LevelOrder2(BTNode* root);

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

BTree.c


#include"Btree.h"

//前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		//如果为空,直接打印返回
		printf("NULL ");
		return;
	}
	//递归遍历,最开始打印根结点数据
	printf("%c ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

//中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		//如果为空,直接打印返回
		printf("NULL ");
		return;
	}
	//根结点在中间打印
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}

//后序遍历
void LasOrder(BTNode* root)
{
	if (root == NULL)
	{
		//如果为空,直接打印返回
		printf("NULL ");
		return;
	}
	//根结点最后打印
	LasOrder(root->left);
	LasOrder(root->right);
	printf("%c ", root->data);
}


// 二叉树结点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{
	//root不为空,避免对空指针解引用
	//左孩子结点和右孩子结点都为NULL是叶子结点
	if (root != NULL && root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	//如果root为NULL,返回0
	if (root == NULL)
	{
		return 0;
	}
	//叶子结点总个数=左子树叶子结点个数 +右子树叶子结点个数
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	//如果为空,没有结点,返回0
	if (root == NULL)
	{
		return 0;
	}
	//root不为空,并且走到第k层
	//后面传参k-1,到第k层也就是k=1
	//例:求第三层,从第一层向下走2层就可以到第三层
	if (k == 1)
	{
		return 1;
	}
	//二叉树第k层结点个数 =  左子树第k层结点个数 +右子树第k层结点个数
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

// 二叉树的深度/高度
int BinaryTreeDepth(BTNode* root)
{
	//如果root为空返回0
	if (root == NULL)
	{
		return 0;
	}
	//求左子树深度/高度
	int DepLeft = BinaryTreeDepth(root->left);
	// 求右子树深度/高度
	int DepRight = BinaryTreeDepth(root->right);
	//返回1+左子树深度/高度、右子树深度/高度最大值
	return 1 + (DepLeft > DepRight ? DepLeft : DepRight);
}

// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	//判断根结点
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	//在左子树里面找
	BTNode* leftFind = BinaryTreeFind(root->left, x);
	//左子树找到结果不为空,返回
	if (leftFind)
	{
		return leftFind;
	}
	//在右子树里面找
	BTNode* rightFind = BinaryTreeFind(root->left, x);
	//右子树找到结果不为空,返回
	if (rightFind)
	{
		return rightFind;
	}
	//最后没有找到
	return NULL;
}

// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
	//为空直接返回,不需要销毁了
	if (*root == NULL)
	{
		return;
	}
	//后序遍历销毁——左右根
	//注意:二级指针传地址
	BinaryTreeDestory(&((*root)->left));
	BinaryTreeDestory(&((*root)->right));
	free(*root);
	*root = NULL;

	//中序遍历销毁
	//err
	/*BinaryTreeDestory(&((*root)->left));
	free(*root);
	*root = NULL;
	BinaryTreeDestory(&((*root)->right));*/
}

// 层序遍历
void LevelOrder1(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	//定义一个队列结构
	Queue Qu;
	//初始化
	QueueInit(&Qu);
	//让根结点入队列
	QueuePush(&Qu, root);
	//队列不为空,取队头打印数据,并且出队头
	while (!QueueEmpty(&Qu))
	{
		BTNode* top = QueueFront(&Qu);
		printf("%c ", top->data);
		//队头出队列
		QueuePop(&Qu);
		//如果左右孩子结点不为空入队列
		if (top->left)
		{
			QueuePush(&Qu, top->left);
		}
		if (top->right)
		{
			QueuePush(&Qu, top->right);
		}
	}
	//销毁
	QueueDestroy(&Qu);
}

// 层序遍历2
//为空也入队列
void LevelOrder2(BTNode* root)
{
	//定义一个队列结构
	Queue Qu;
	//初始化
	QueueInit(&Qu);
	//让根结点入队列
	//为空也入队列,不需要判断
	QueuePush(&Qu, root);
	//队列不为空,取队头打印数据,并且出队头
	while (!QueueEmpty(&Qu))
	{
		BTNode* top = QueueFront(&Qu);
		//队头为空打印NULL并且NULL出队列
		if (top == NULL)
		{
			printf("NULL ");
			QueuePop(&Qu);
			continue;//继续执行循环
		}
		else
		{
			printf("%c ", top->data);
		}
		//队头出队列
		QueuePop(&Qu);
		//左右孩子结点入队列
		QueuePush(&Qu, top->left);
		QueuePush(&Qu, top->right);
	}
	//销毁
	QueueDestroy(&Qu);
}

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	//借助队列
	Queue Qu;
	//初始化
	QueueInit(&Qu);
	//根结点入队列
	QueuePush(&Qu, root);
	while (!QueueEmpty(&Qu))
	{
		//取队头元素
		BTNode* top = QueueFront(&Qu);
		//出队列
		QueuePop(&Qu);
		//为空第一层循环结束
		if (top == NULL)
		{
			break;
		}
		//让左右孩子结点入队列
		QueuePush(&Qu, top->left);
		QueuePush(&Qu, top->right);
	}
	//第二层循环,队列不为空
	//继续取队头出队头,如果有不为NULL的说明是非完全二叉树
	while (!QueueEmpty(&Qu))
	{
		//取队头元素
		BTNode* top = QueueFront(&Qu);
		//出队列
		QueuePop(&Qu);
		if (top != NULL)
		{
			//返回前销毁队列!!!
			QueueDestroy(&Qu);
			return false;
		}
	}
	//剩下的全部为NULL,是完全二叉树
	return true;
	//销毁
	QueueDestroy(&Qu);
}

test.c


#include"Btree.h"

//创建一个结点代码
BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(1);
		//创建失败,退出程序
	}
	//创建成功
	node->data = x;
	node->left = node->right = NULL;
	//返回创建的结点地址
	return node;
}
//创建二叉树
BTNode* BTreeCreate()
{
	BTNode* n1 = BuyNode('A');
	BTNode* n2 = BuyNode('B');
	BTNode* n3 = BuyNode('C');
	BTNode* n4 = BuyNode('D');
	BTNode* n5 = BuyNode('E');
	BTNode* n6 = BuyNode('F');

	//通过逻辑关系连接结点
	n1->left = n2;
	n1->right = n3;

	n2->left = n4;

	n3->left = n5;
	n3->right = n6;

	//返回头结点
	return n1;
}
void test1()
{
	//手动创建一个二叉树
	BTNode* head = BTreeCreate();
	//前序遍历
	printf("前序遍历:");
	PreOrder(head);
	printf("\n");
	//中序遍历
	printf("中序遍历:");
	InOrder(head);
	printf("\n");
	//后序遍历
	printf("后序遍历:");
	LasOrder(head);
	printf("\n");
}

void test2()
{
	//手动创建一个二叉树
	BTNode* head = BTreeCreate();
	//二叉树结点个数
	printf("二叉树结点个数:%d\n", BinaryTreeSize(head));
	printf("二叉树叶子结点个数:%d\n", BinaryTreeLeafSize(head));
	/*printf("二叉树第%d层结点个数:%d\n", 1, BinaryTreeLevelKSize(head, 1));
	printf("二叉树第%d层结点个数:%d\n", 3, BinaryTreeLevelKSize(head, 3));
	printf("二叉树第%d层结点个数:%d\n", 4, BinaryTreeLevelKSize(head, 4));*/

	printf("二叉树的深度/高度:%d\n", BinaryTreeDepth(head));

	/*BTNode* find = BinaryTreeFind(head, 'B');
	if (find)
	{
		printf("找到了!\n");
	}
	else
	{
		printf("没有找到!\n");
	}*/

	//二级指针传地址
	//BinaryTreeDestory(&head);
}

void test3()
{
	//手动创建一个二叉树
	BTNode* head = BTreeCreate();
	//层序遍历
	printf("LevelOrder1:\n");
	LevelOrder1(head);
	printf("\n");
	printf("LevelOrder2:\n");
	LevelOrder2(head);
	//判断完全二叉树
	/*if (BinaryTreeComplete(head))
	{
		printf("是完全二叉树!\n");
	}
	else
	{
		printf("不是完全二叉树!\n");
	}*/
}

int main()
{
	//test1();
	//test2();
	test3();
	return 0;
}

♥♥♥本篇博客内容结束,期待与各位未来的优秀程序员交流,有什么问题请私信♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥


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

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

相关文章

动态规划 —— dp问题-按摩师

1. 按摩师 题目链接&#xff1a; 面试题 17.16. 按摩师 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/the-masseuse-lcci/description/ 2. 算法原理 状态表示&#xff1a;以某一个位置为结尾或者以某一个位置为起点 dp[i]表示&#xff1a;选择到i位置…

python爬取旅游攻略(1)

参考网址&#xff1a; https://blog.csdn.net/m0_61981943/article/details/131262987 导入相关库&#xff0c;用get请求方式请求网页方式&#xff1a; import requests import parsel import csv import time import random url fhttps://travel.qunar.com/travelbook/list.…

C++设计模式创建型模式———原型模式

文章目录 一、引言二、原型模式三、总结 一、引言 与工厂模式相同&#xff0c;原型模式&#xff08;Prototype&#xff09;也是创建型模式。原型模式通过一个对象&#xff08;原型对象&#xff09;克隆出多个一模一样的对象。实际上&#xff0c;该模式与其说是一种设计模式&am…

基于STM32的智能温室环境监测与控制系统设计(代码示例)

一、项目概述 在现代农业中&#xff0c;智能大棚能够通过环境监测、数据分析和自动控制等技术手段&#xff0c;实现对作物生长环境的精细化管理。本项目旨在设计一个基于STM32单片机的智能大棚系统&#xff0c;能够实时监测光照强度、空气温湿度及土壤湿度&#xff0c;并根据设…

(五)Web前端开发进阶2——AJAX

目录 1.Ajax概述 2.Axios库 3.认识URL 4.Axios常用请求方法 5.HTTP协议——请求报文/响应报文 6.HMLHttpRequest对象 7.前后端分离开发&#xff08;接口文档&#xff09; 8.Element组件库 1.Ajax概述 AJAX 是异步的 JavaScript和XML(Asynchronous JavaScript And XML)。…

进程信号——信号的保存

信号的概念 实际执行信号的处理动作称为信号递达(Delivery) 信号从产生到递达之间的状态,称为信号未决(Pending)。 进程可以选择阻塞 (Block )某个信号。 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作. 注意,阻塞和忽略是不同的,只要信号…

基于SSM的“房屋租赁系统”的设计与实现(源码+数据库+文档+PPT)

基于SSM的“房屋租赁系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM&#xff0c;JSP 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 房屋租赁系统首页 管理员后台管理页面 报告故障管…

无需懂代码!用AI工具Bolt一键生成网站的入门指南!

​ ​ 随着AI技术的不断发展&#xff0c;许多原本需要技术门槛的操作正在被大大简化&#xff0c;甚至零基础的用户也可以轻松实现。 例如&#xff0c;AI生成网站工具Bolt就是这样一个可以帮助我们快速创建、实时预览并自动部署网站的平台。接下来&#xff0c;本文将带你深入了…

Elasticsearch中时间字段格式用法详解

Elasticsearch中时间字段格式用法详解 攻城狮Jozz关注IP属地: 北京 2024.03.18 16:27:51字数 758阅读 2,571 Elasticsearch&#xff08;简称ES&#xff09;是一个基于Lucene构建的开源、分布式、RESTful搜索引擎。它提供了全文搜索、结构化搜索以及分析等功能&#xff0c;广泛…

vue中el-table显示文本过长提示

1.el-table设置轻提示:show-overflow-tooltip“true“&#xff0c;改变轻提示宽度

关于我的编程语言——C/C++——第四篇(深入1)

&#xff08;叠甲&#xff1a;如有侵权请联系&#xff0c;内容都是自己学习的总结&#xff0c;一定不全面&#xff0c;仅当互相交流&#xff08;轻点骂&#xff09;我也只是站在巨人肩膀上的一个小卡拉米&#xff0c;已老实&#xff0c;求放过&#xff09; 字符类型介绍 char…

【春秋云镜】CVE-2023-23752

目录 CVE-2023-23752漏洞细节漏洞利用示例修复建议 春秋云镜&#xff1a;解法一&#xff1a;解法二&#xff1a; CVE-2023-23752 是一个影响 Joomla CMS 的未授权路径遍历漏洞。该漏洞出现在 Joomla 4.0.0 至 4.2.7 版本中&#xff0c;允许未经认证的远程攻击者通过特定 API 端…

AI 写作(一):开启创作新纪元(1/10)

一、AI 写作&#xff1a;重塑创作格局 在当今数字化高速发展的时代&#xff0c;AI 写作正以惊人的速度重塑着创作格局。AI 写作在现代社会中占据着举足轻重的地位&#xff0c;发挥着不可替代的作用。 随着信息的爆炸式增长&#xff0c;人们对于内容的需求日益旺盛。AI 写作能够…

快速构建数据产品原型 —— 我用 VChart Figma 插件

快速构建数据产品原型 —— 我用 VChart Figma 插件 10 种图表类型、24 种内置模板类型、丰富的图表样式配置、自动生成图表实现代码。VChart Figma 插件的目标是提供 便捷好用 & 功能丰富 & 开发友好 的 figma 图表创建能力。目前 VChart 插件功能仍在持续更新中&…

源鲁杯 2024 web(部分)

[Round 1] Disal F12查看: f1ag_is_here.php 又F12可以发现图片提到了robots 访问robots.txt 得到flag.php<?php show_source(__FILE__); include("flag_is_so_beautiful.php"); $a$_POST[a]; $keypreg_match(/[a-zA-Z]{6}/,$a); $b$_REQUEST[b];if($a>99999…

【ArcGIS】绘制各省碳排放分布的中国地图

首先&#xff0c;准备好各省、自治区、直辖市及特别行政区&#xff08;包括九段线&#xff09;的shp文件&#xff1a; 通过百度网盘分享的文件&#xff1a;GS&#xff08;2022&#xff09;1873 链接&#xff1a;https://pan.baidu.com/s/1wq8-XM99LXG_P8q-jNgPJA 提取码&#…

C++《list的模拟实现》

在上一篇C《list》专题当中我们了解了STL当中list类当中的各个成员函数该如何使用&#xff0c;接下来在本篇当中我们将试着模拟实现list&#xff0c;在本篇当中我们将通过模拟实现list过程中深入理解list迭代器和之前学习的vector和string迭代器的不同&#xff0c;接下来就开始…

讲讲⾼可用的原则?

大家好&#xff0c;我是锋哥。今天分享关于【讲讲⾼可用的原则&#xff1f;】面试题。希望对大家有帮助&#xff1b; 讲讲⾼可用的原则&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在当今信息化时代&#xff0c;随着互联网技术的快速发展&#xff0…

003-Kotlin界面开发之声明式编程范式

概念本源 在界面程序开发中&#xff0c;有两个非常典型的编程范式&#xff1a;命令式编程和声明式编程。命令式编程是指通过编写一系列命令来描述程序的运行逻辑&#xff0c;而声明式编程则是通过编写一系列声明来描述程序的状态。在命令式编程中&#xff0c;程序员需要关心程…

Ubuntu 20.04 部署向量数据库 Milvus + Attu

前言 最开始在自己的办公电脑&#xff08;无显卡的 windows 10 系统&#xff09; 上使用 Docker Desktop 部署了 Milvus 容器&#xff0c;方便的很&#xff0c; 下载 Attu 也很方便&#xff0c;直接就把这个向量数据库通过 Attu 这个图形化界面跑了起来&#xff0c;使用起来感…