前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【初阶数据结构】节点层级的逻辑乐章:二叉树

【初阶数据结构】节点层级的逻辑乐章:二叉树

作者头像
DARLING Zero two
发布2025-02-25 14:37:23
发布2025-02-25 14:37:23
3500
代码可运行
举报
文章被收录于专栏:C语言C语言
运行总次数:0
代码可运行
本章节是树结构的最后一篇——二叉树,这里我们只实现最简单的二叉树结构,在C++语法部分将学习更高阶的AVL树、红黑树巩固

1.二叉树的结构

代码语言:javascript
代码运行次数:0
复制
typedef int BTDataType;

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

再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:

  1. 空树
  2. 非空:根结点,根结点的左子树、根结点的右子树组成的

2.二叉树接口实现

2.1 二叉树节点创建

代码语言:javascript
代码运行次数:0
复制
BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

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

	return node;
}
  1. 使用 malloc 函数为新节点分配内存,其大小为 BTNode 结构体的大小
  2. 检查内存分配是否成功。如果 malloc 返回 NULL,表示内存分配失败,使用 perror 函数输出错误信息,并返回 NULL
  3. 若内存分配成功,将传入的数据 x 赋值给新节点的 data 成员
  4. 将新节点的左右子指针 leftright 都初始化为 NULL,表示该节点暂时没有左右子节点
  5. 返回新创建的节点指针

2.2 二叉树树的创建

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。

代码语言:javascript
代码运行次数:0
复制
BTNode* CreatTree()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);

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

	return node1;
}

由于现在对二叉树结构掌握还不够深入,为了降低学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式

2.3 二叉树的前序遍历

代码语言:javascript
代码运行次数:0
复制
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

前序遍历依据左子树右子树的顺序,前序遍历又叫做深度优先遍历(DFS)

其本质上是一个有限递归的过程,当左节点递归到最后一个叶节点时,其子节点NULL向下递归就结束,然后开始回退遍历右节点

🔥值得注意的是: root == NULLif 条件句既是为了表示空树的情况,也是为了结束向下递归的过程

💻测试结果:

2.4 二叉树的中序遍历

代码语言:javascript
代码运行次数:0
复制
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

中序遍历依据左子树右子树的顺序

其余操作和前序遍历一致

💻测试结果:

2.5 二叉树的后序遍历

代码语言:javascript
代码运行次数:0
复制
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

后序遍历依据左子树右子树的顺序

其余操作和前序遍历一致

💻测试结果:

2.6 二叉树结点个数

代码语言:javascript
代码运行次数:0
复制
void TreeSize(BTNode* root, int* psize)
{
	if (root == NULL)
	{
		return;
	}
	++(*psize);
	TreeSize(root->left, psize);
	TreeSize(root->right, psize);
}

获取节点个数首先想到的肯定是遍历二叉树,如上代码所示的方法,用一个移动指针遍历,每到一个节点就 ++ ,这固然可行,但是有更直观简洁的方法

⌨️优化代码:

代码语言:javascript
代码运行次数:0
复制
int TreeSize(BTNode* root)
{
	return root == NULL ? 0 :
		TreeSize(root->left)
		+ TreeSize(root->right)
		+ 1;
}

这种模式就像学校里的人数统计一样,假设想要统计全校寝室人数

  1. 寝室长统计寝室人数,汇报给宿管阿姨
  2. 宿管阿姨统计每栋宿舍人数,汇报给专业年级主任
  3. 专业年级主任统计专业人数,汇报给校长
  4. 最后由校长汇总专业年级主任上交的数据,就能得出全校寝室人数

这是一种清晰的统计流程,二叉树结点个数就是从最底下逐渐往上加和进行统计

2.7 二叉树高度获取

代码语言:javascript
代码运行次数:0
复制
int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	return TreeHeight(root->left) > TreeHeight(root->right)
		? TreeHeight(root->left) + 1 : TreeHeight(root->right) + 1;
}

二叉树高度获取的思想和二叉树结点个数是一致的,高度取决于左子树右子树路径较长的那条,直接比较然后递归取值即可

但是这段代码有个问题,比较时需要递归一次,比较完并没有保存每个节点高度的结果,获取结果的时候又要再递归一次,导致代码效率减慢

⌨️优化代码:

代码语言:javascript
代码运行次数:0
复制
int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	
	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);

	return leftHeight > rightHeight 
		? leftHeight + 1 
		: rightHeight + 1; 
}

所以每次向上回退时,保存每个节点的高度结果即可

2.8 二叉树第k层节点个数

代码语言:javascript
代码运行次数:0
复制
int TreeKLevel(BTNode* root, int k)
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;

	int leftK = TreeKLevel(root->left, k - 1);
	int rightK = TreeKLevel(root->right, k - 1);

	return leftK + rightK;
}

还是利用递归的思想,假设要获取第三层的节点个数,那么需要向上递归两次到达根节点,所以是 k - 1

从下往上数层数

  • 根节点处于第 1 层,对于根节点的左子节点和右子节点,它们处于第 2 层,距离第 3 层还有 1 层,所以递归调用 TreeKLevel(root->left, 2)TreeKLevel(root->right, 2)
  • 当递归到第 2 层的节点时,对于它们的子节点(即第 3 层的节点),再次递归调用时传入 k - 11,此时满足 k == 1 的条件,返回 1 表示找到了一个第 3 层的节点
  • 通过不断地递归调用并使用 k - 1 作为新的层数参数,最终可以准确计算出二叉树中第 k 层的节点总数

2.9 二叉树查找值为x的节点

代码语言:javascript
代码运行次数:0
复制
BTNode* TreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;

	BTNode* lret = TreeFind(root->left, x);
	if (lret)
		return lret;

	BTNode* rret = TreeFind(root->right, x);
	if (rret)
		return rret;

	return NULL;
}
  • 递归查找左子树: 如果上述两个 if 条件都不满足,说明当前节点不是目标节点,需要继续在其左子树中查找。递归调用 TreeFind(root->left, x) 来查找左子树中是否存在值为 x 的节点,并将返回结果存储在 lret 中。如果 lret 不为 NULL,说明在左子树中找到了目标节点,直接返回 lret
  • 递归查找右子树 如果在左子树中未找到目标节点(即 lretNULL),则继续在右子树中查找。递归调用 TreeFind(root->right, x) 来查找右子树中是否存在值为 x 的节点,并将返回结果存储在 rret 中。如果 rret 不为 NULL,说明在右子树中找到了目标节点,直接返回 rret
  • 未找到目标节点 如果在左子树和右子树中都未找到值为 x 的节点,说明整个二叉树中不存在该节点,函数最终返回 NULL

2.10 二叉树的层序遍历

代码语言:javascript
代码运行次数:0
复制
void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	while (QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%d ", front->data);

		if (front->left)
			QueuePush(&q, front->left);

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

	QueueDestroy(&q);
}

层序遍历就是从上到下从左到右进行遍历,层序遍历也叫作广度优先遍历(BFS)

这里需要利用队列的特性来进行,出上一层,带入下一层(每出一个节点,就插入该节点的子节点)引入队列的头文件和源文件

传送门:【初阶数据结构】先来后到的秩序:栈和队列

🔥值得注意的是: BTNode* front = QueueFront(&q) 保存了二叉树的节点作为队列的头节点,释放时并不会影响到二叉树本身,而是释放队列头节点

2.11 判断是否为完全二叉树

代码语言:javascript
代码运行次数:0
复制
bool TreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	while (QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front == NULL)
			break;
		else
		{
			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;
}

我们要知道一个特性:完全二叉树的非空节点一定是连续的

第一个循环是层序遍历二叉树,直到遇到第一个空就停下来,第二个循环是检查队列中剩余的节点是否都为空,继续遍历队列中剩余的节点,如果遇到非空节点,说明该二叉树不是完全二叉树,返回 false如果队列中剩余的节点都为空,说明该二叉树是完全二叉树,返回 true

2.12 二叉树的销毁

代码语言:javascript
代码运行次数:0
复制
void TreeDestroy(BTNode* root)
{
	if (root == NULL)
		return;

	TreeDestroy(root->left);
	TreeDestroy(root->right);
	free(root);
	root == NULL;
}

希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本章节是树结构的最后一篇——二叉树,这里我们只实现最简单的二叉树结构,在C++语法部分将学习更高阶的AVL树、红黑树巩固
  • 1.二叉树的结构
  • 2.二叉树接口实现
    • 2.1 二叉树节点创建
    • 2.2 二叉树树的创建
    • 2.3 二叉树的前序遍历
    • 2.4 二叉树的中序遍历
    • 2.5 二叉树的后序遍历
    • 2.6 二叉树结点个数
    • 2.7 二叉树高度获取
    • 2.8 二叉树第k层节点个数
    • 2.9 二叉树查找值为x的节点
    • 2.10 二叉树的层序遍历
    • 2.11 判断是否为完全二叉树
    • 2.12 二叉树的销毁
  • 希望读者们多多三连支持
  • 小编会继续更新
  • 你们的鼓励就是我前进的动力!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档