链式结构二叉树(递归暴力美学)

news2025/2/7 12:06:01

在这里插入图片描述


在这里插入图片描述


文章目录

  • 1. 链式结构二叉树
    • 1.1 二叉树创建
  • 2. 前中后序遍历
    • 2.1 遍历规则
    • 2.2 代码实现
      • 图文理解
  • 3. 结点个数以及高度等
    • 二叉树结点个数
      • 正确做法:
  • 4. 层序遍历
  • 5. 判断是否完全二叉树

1. 链式结构二叉树

完成了顺序结构二叉树的代码实现,可以知道其底层结构是类似顺序表的结构;
因此,链式结构的二叉树类似于链表结构。

二叉树的结构一般由指数据域和左右指针域这三个域组成。

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data; //当前结点数据域
	struct BinaryTreeNode* left; //指向当前结点左孩⼦
	struct BinaryTreeNode* right; //指向当前结点右孩⼦
}BTNode;

1.1 二叉树创建

由于二叉树的代码实现过于复杂,因此这里采用手动创建一棵链式二叉树方便后续思路的代码实现。

单独封装一个函数可用来申请结点(和链表一样);
手动创建多个结点,并让其形成一棵二叉树。

BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	//malloc成功
	node->data = x;
	node->left = node->right = NULL;
	return node;
}
//手动构造一颗二叉树
BTNode* CreaterTree()
{
	BTNode* newnodeA = BuyNode('A');
	BTNode* newnodeB = BuyNode('B');
	BTNode* newnodeC = BuyNode('C');
	BTNode* newnodeD = BuyNode('D');
	BTNode* newnodeE = BuyNode('E');
	BTNode* newnodeF = BuyNode('F');

	newnodeA->left = newnodeB;
	newnodeA->right = newnodeC;
	newnodeB->left = newnodeD;
	newnodeC->left = newnodeE;
	newnodeC->right = newnodeF;
	return newnodeA;
}

在这里插入图片描述

2. 前中后序遍历

既然是二叉树,那必然也离不开遍历。因此,我们有多种遍历方式。

2.1 遍历规则

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

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

以前序遍历为例如下图所示:
遵循前序遍历的规则:
1 先是访问A结点,然后进入左子树;
2. 在左子树中访问B根结点,进入B结点的左子树;
3. 在左子树中访问D根结点,而D中没有左子树和右子树,返回访问B的右子树;
4. B的右子树不存在,返回访问A的右子树;
5. 在右子树中访问C根结点,进入C结点的左子树;
6. 在左子树中访问E根结点,而E中没有左子树和右子树,返回访问C的右子树;
7. 在右子树中访问F根结点,F中没有左子树和右子树,遍历结束。

因此,前序遍历出来的数据是: A B D NULL NULL NULL C E NULL NULL F NULL NULL (NULL表示空)
在这里插入图片描述

图文理解

在这里插入图片描述

2.2 代码实现

为了方便理解,建议看完此二篇。
函数栈帧的创建和销毁.

函数递归的理解 <-- 详见此文


前序遍历
//前序遍历 --- 根左右
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 PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->data);
}

3. 结点个数以及高度等

二叉树结点个数

错误示例1:

int BinaryTreeSize(BTNode* root)
{
	static int size = 0;
	if (root == NULL)
	{
		return 0;
	}
	++size;
	BinaryTreeSize(root->left);
	BinaryTreeSize(root->right);
	return size;
}

我们开始的想法是通过调用该函数,在里面通过变量size计数,但每次递推调用函数的时候size又会重新置为0,因此我们又考虑用static修饰size延长其生命周期,使得每次递推的时候都能使size保存上一个函数调用的值最后返回size的值得到该二叉树的结点个数。

但是,这明显存在一个错误,如果我们再次计算这个二叉树结点个数会出现什么情况?
在这里插入图片描述
我们打断点调试会发现size的值是从6开始的,这是为什么呢?
很明显,size被static修饰延长了生命周期,使得该变量不再是因为栈空间的结束而销毁。因此,该做法是不可取的。
在这里插入图片描述

错误示例2:

void BinaryTreeSize(BTNode* root, int* psize)
{
	if (root == NULL)
	{
		return 0;
	}
	++(*psize);
	BinaryTreeSize(root->left,psize);
	BinaryTreeSize(root->right,psize);
}

既然我们不能从函数内部计数,那么我们是否能在函数外部定义指针变量size(要使得形参的改变能影响实参)来计数呢?
虽然解决了生命周期被延长的做法,但下次调用函数依旧会出现问题,还是因为size没有从0开始(需要手动置为0)。

正确做法:

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

深刻理解了递归和栈帧空间的创建销毁思想后,一棵二叉树是由根结点、左子树和右子树组成的,那么计算结点个数自然而然就是 1 + 左子树 + 右子树

4. 层序遍历

建议看完此篇。
栈和队列 <-- 详见此文

层序遍历:从所在二叉树的根结点出发,先访问第⼀层的树根结点,然后从左到右访问第2
层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程。

实现层序遍历需要额外借助数据结构:队列

  1. 将二叉树的根节点传给队列(队列可根据根节点找到左子树和右子树);
  2. 取队头元素并打印,同时删除队头元素;
  3. 若存在左孩子或右孩子,依次入队列;
  4. 继续取队头元素并打印,删除队头元素同时将队头的左孩子和右孩子入队列(存在情况下);
  5. 如此循环下去,直到队列为空完成了层序遍历。

在这里插入图片描述

// 层序遍历
void LevelOrder(BTNode* root)
{
	//借助数据结构--队列
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		//取队头,出队头
		BTNode* top = QueueFront(&q);
		QueuePop(&q);
		printf("%c ", top->data);
		//队头左右孩子入队列(不为空)
		if (top->left)
		{
			QueuePush(&q, top->left);
		}
		if (top->right)
		{
			QueuePush(&q, top->right);
		}
	}
	QueueDestroy(&q);
}

5. 判断是否完全二叉树

非完全二叉树:
在这里插入图片描述
完全二叉树:
在这里插入图片描述

// 判断⼆叉树是否是完全⼆叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* top = QueueFront(&q);
		QueuePop(&q);
		if (top == NULL)
		{
			break;
		}
		//把不为空的队头节点的左右孩子入队列
		QueuePush(&q, top->left);
		QueuePush(&q, top->right);
	}
	//队列不一定为空,继续取队头出队头
	while (!QueueEmpty(&q))
	{
		BTNode* top = QueueFront(&q);
		QueuePop(&q);
		if (top != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}

在这里插入图片描述


在这里插入图片描述


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

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

相关文章

技术文档管理最佳实践:高效、专业、可持续

文章目录 技术文档管理最佳实践&#xff1a;高效、专业、可持续1. 技术文档的核心价值1.1 降低知识流失风险1.2 提升开发效率1.3 增强团队协作1.4 规范技术资产管理 2. 技术文档分类与规范2.1 代码相关文档2.2 过程与运维文档2.3 知识与培训文档 3. 工具选型&#xff1a;自动化…

56. Uboot移植实验

一、NXP官方Uboot编译与测试 1、将NXP提供的uboot拷贝到ubuntu中。 一个开发板也好运行uboot&#xff0c;DDR或者叫DRAM&#xff0c;串口&#xff0c;SD、EMMC、NAND。板子能工作。 测似结果&#xff1a; 1、uboot能正常启动 2、LCD驱动要根据所使用的屏幕修改。 3、NET初始…

AI大模型:本地部署deepseek

一、安装lmstudio 1、下载网站&#xff1a; LM Studio - Discover, download, and run local LLMs 2、直接安装即可&#xff0c;记住安装的路径 二、下载deepseek模型 2.1、下载的流程 1、下载网站 https://huggingface.co/models 2、在搜索框输入&#xff1a;deepseek …

RK3588平台开发系列讲解(DMA篇)DMA engine使用

文章目录 一、DMA 使用步骤二、DMA接口2.1、DMA 通道管理相关接口2.2、DMA 描述符相关接口2.3、DMA 启动与控制接口2.4、DMA 状态检查接口2.5、 DMA 缓存管理接口2.6、DMA 中断与同步机制沉淀、分享、成长,让自己和他人都能有所收获!😄 Linux 内核的 DMA 引擎提供了一组完整…

报名 | IEEE ICME 2025 音频编码器能力挑战赛正式开启

音频编码器是多模态大模型的重要组件&#xff0c;优秀的音频编码器在构建多模态系统中至关重要。在此背景下&#xff0c;小米集团、萨里大学、海天瑞声共同主办了 IEEE International Conference on Multimedia & Expo (ICME) 2025 Audio Encoder Capability Challenge。 …

ASP.NET Core标识框架Identity

目录 Authentication与Authorization 标识框架&#xff08;Identity&#xff09; Identity框架的使用 初始化 自定义属性 案例一&#xff1a;添加用户、角色 案例二&#xff1a;检查登录用户信息 案例三&#xff1a;实现密码的重置 步骤 Authentication与Authorizatio…

PFAS(全氟烷基和多氟烷基物质)测试流程详细介绍

PFAS&#xff08;全氟烷基和多氟烷基物质&#xff09;测试详细介绍 什么是PFAS&#xff1f; PFAS是(Per-and polyfluoroalkyl substances)的简称&#xff0c;中文名&#xff1a;全氟烷基和多氟烷基物质&#xff0c;是一系列合成有机氟化物的总称&#xff0c;是指至少含有一个…

宝塔面板端口转发其它端口至MySQL的3306

最近需要把服务器的MySQL服务开放给外网&#xff0c;但又希望公开给所有人。也不想用默认的3306端口。同时也不想改变MySQL的默认端口。 这时候最好的办法就是用一个不常用的端口来转发至3306上去。例如使用49306至3306&#xff0c;外网通过49306来访问&#xff0c;内网依然使用…

inquirer介绍及配合lerna在Vue中使用示例

目录 安装基本用法使用多个提示框动态选择&#xff08;动态选项&#xff09;表单式输入配合lerna在Vue中使用示例 Inquirer 是一个用于创建交互式命令行工具的 Node.js 库&#xff0c;常用于收集用户输入。它提供了多种类型的提示框&#xff0c;可以用于创建交互式应用程序&…

基于MODIS/Landsat/Sentinel/国产卫星遥感数据与DSSAT作物模型同化的作物产量估算

基于过程的作物生长模拟模型DSSAT是现代农业系统研究的有力工具&#xff0c;可以定量描述作物生长发育和产量形成过程及其与气候因子、土壤环境、品种类型和技术措施之间的关系&#xff0c;为不同条件下作物生长发育及产量预测、栽培管理、环境评价以及未来气候变化评估等提供了…

如何打开vscode系统用户全局配置的settings.json

&#x1f4cc; settings.json 的作用 settings.json 是 Visual Studio Code&#xff08;VS Code&#xff09; 的用户配置文件&#xff0c;它存储了 编辑器的个性化设置&#xff0c;包括界面布局、代码格式化、扩展插件、快捷键等&#xff0c;是用户全局配置&#xff08;影响所有…

【Uniapp-Vue3】从uniCloud中获取数据

需要先获取数据库对象&#xff1a; let db uniCloud.database(); 获取数据库中数据的方法&#xff1a; db.collection("数据表名称").get(); 所以就可以得到下面的这个模板&#xff1a; let 函数名 async () > { let res await db.collection("数据表名称…

【重生之学习C语言----杨辉三角篇】

目录 ​编辑 --------------------------------------begin---------------------------------------- 一、什么是杨辉三角&#xff1f; 二、问题分析 三、算法设计 使用二维数组存储杨辉三角&#xff1a; 递推关系&#xff1a; 格式化输出&#xff1a; 四、代码实现 完…

LabVIEW自定义测量参数怎么设置?

以下通过一个温度采集案例&#xff0c;说明在 LabVIEW 中设置自定义测量参数的具体方法&#xff1a; 案例背景 ​ 假设使用 NI USB-6009 数据采集卡 和 热电偶传感器 监测温度&#xff0c;需自定义以下参数&#xff1a; 采样率&#xff1a;1 kHz 输入量程&#xff1a;0~10 V&a…

Vim的基础命令

移动光标 H(左) J(上) K(下) L(右) $ 表示移动到光标所在行的行尾&#xff0c; ^ 表示移动到光标所在行的行首的第一个非空白字符。 0 表示移动到光标所在行的行首。 W 光标向前跳转一个单词 w光标向前跳转一个单词 B光标向后跳转一个单词 b光标向后跳转一个单词 G 移动光标到…

复制粘贴小工具——Ditto

在日常工作中&#xff0c;复制粘贴是常见的操作&#xff0c;但Windows系统自带的剪贴板功能较为有限&#xff0c;只能保存最近一次的复制记录&#xff0c;这对于需要频繁复制粘贴的用户来说不太方便。今天&#xff0c;我们介绍一款开源、免费且功能强大的剪贴板增强工具——Dit…

中国人名汉语拼音字母拼写规则

中国人名汉语拼音字母拼写规则 1. Lv and Lyu2. 中国人名汉语拼音字母拼写规则References 1. Lv and Lyu LongBench: A Bilingual, Multitask Benchmark for Long Context Understanding https://arxiv.org/abs/2308.14508 2. 中国人名汉语拼音字母拼写规则 http://www.moe.g…

MAC OS安装Homebrew

文章目录 1.下载Homebrew2.完成安装3.验证安装4.更新 Homebrew作为一个包管理器&#xff0c;提供了一种简便的方式来安装、更新和卸载各种命令行工具和应用程序。相比于手动下载和编译源代码&#xff0c;或者从不同的网站下载安装包&#xff0c;使用Homebrew可以显著减少这些操…

飞算JavaAI:开辟 AI + 行业趋势的编程新范式

在当今数字化浪潮汹涌澎湃的时代&#xff0c;科技的快速发展正以前所未有的速度重塑着各个行业的面貌。人工智能&#xff08;AI&#xff09;作为其中最具变革性的力量之一&#xff0c;已经深入渗透到众多领域&#xff0c;从金融、医疗到制造业、教育等&#xff0c;无一不在经历…

Axure PR 9 动效 设计交互

大家好&#xff0c;我是大明同学。 这期内容&#xff0c;我们来用Axure制作一组动效。 动效 创建动效元件 1.打开一个新的 RP 文件并在画布上打开 Page 1。 2.选中画布&#xff0c;将画布填充颜色设置为蓝色(#0052D9)。 3.在元件库中拖出一个圆形元件&#xff0c;选中矩形元件&…