数据结构-链式二叉树-四种遍历
博客主页:【夜泉_ly】
本文专栏:【数据结构】
欢迎点赞👍收藏⭐关注❤️
数据结构-链式二叉树-四种遍历
- 1.前言
- 2.前、中、后序遍历
- 2.1前序遍历
- 2.1中、后序遍历
- 3.层序遍历
- 3.1递归实现
- 3.2队列实现
- 关于在`Pop`之后为什么还能用`tmp`访问节点?
- 关于都已经把队列`Pop`为空了为什么还要`QueueDestroy`?
- 4.浅谈DFS与BFS
1.前言
在我之前的文章数据结构-堆-详解中,我对堆这种特殊的完全二叉树做了详细介绍。
完全二叉树非常适合用数组存储,但一般的二叉树呢?如下图所示:

可以发现,普通二叉树若使用数组存储,会浪费大量空间,这时,链式存储结构成为更好的选择。
在这种结构中,每个节点应包含自身所存储的数据,以及指向左右子树的指针。
可以通过以下结构体定义二叉树节点:
typedef char BTDataType;typedef struct TreeNode
{BTDataType val;struct TreeNode* left;struct TreeNode* right;
}TreeNode;
为了便于理解,我手搓了一个简单的二叉树:

代码如下:
TreeNode* BuyTreeNode(BTDataType x)
{TreeNode* tmp = (TreeNode*)malloc(sizeof(TreeNode));if (!tmp){perror("BuyTreeNode::malloc");return NULL;}tmp->val = x;tmp->left = NULL;tmp->right = NULL;return tmp;
}
TreeNode* CreatBinaryTree()
{TreeNode* root = BuyTreeNode('a');TreeNode* n1 = BuyTreeNode('b');TreeNode* n2 = BuyTreeNode('c');TreeNode* n3 = BuyTreeNode('d');TreeNode* n4 = BuyTreeNode('e');TreeNode* n5 = BuyTreeNode('f');TreeNode* n6 = BuyTreeNode('g');root->left = n1;root->right = n2;n1->left = n3;n1->right = n4;n2->left = n5;n2->right = n6;return root;
}
注意:这并不是二叉树真正的创建方法,等对于二叉树的结构有更深入的了解后,再讲创建。
在对二叉树进行各项操作时,应对其结构有明确的认识:

对任意一个二叉树,都由 根 、左子树 、右子树 组成。
而左右子树,也是二叉树,也有对应的 根 、左子树 、右子树。
因此,二叉树的定义是递归的,在对二叉树进行处理时也常常使用递归。
而对二叉树的递归操作也应以 根 、左子树 、右子树 为基础展开。
2.前、中、后序遍历
2.1前序遍历
二叉树的前序遍历:先访问根,再遍历左子树,最后遍历右子树。
void BinaryTreePrevOrder(TreeNode* root)
{if (!root){printf("NULL ");return;}printf("%c ", root->val);BinaryTreePrevOrder(root->left);BinaryTreePrevOrder(root->right);
}
先判断根,如果为空,就打印NULL并返回;
若不为空,则先打印根的值,再遍历左子树,最后遍历右子树。
在二叉树这块,可以先画画逻辑结构图:

虽然画的不全,但大概就是这么个意思。
如果难以理解时可以再画画递归展开图:把代码是怎么一行行执行的给画出来。
经过分析,可知打印结果应该是:a b d NULL NULL e NULL NULL c f NULL NULL g NULL NULL。

也可以选择不打印空,结果是:a b d e c f g。

在画过图后,应对二叉树的遍历有更清楚的认识:
在打印d后为什么直接打印e?
并非是从d的节点直接到e的节点,
而是先访问d的两个空的左右子树,d结束了左右子树的遍历,返回到b。
此时b结束了它的左子树的遍历,于是开始遍历右子树,然后才到了e处。
2.1中、后序遍历
二叉树的中序遍历:先遍历左子树,再访问根,最后遍历右子树。
void BinaryTreeInOrder(TreeNode* root)
{if (!root){printf("NULL ");return;}BinaryTreeInOrder(root->left);printf("%c ", root->val);BinaryTreeInOrder(root->right);
}
二叉树的后序遍历:先遍历左子树,再遍历右子树,最后访问根。
void BinaryTreePostOrder(TreeNode* root)
{if (!root){printf("NULL ");return;}BinaryTreePostOrder(root->left);BinaryTreePostOrder(root->right);printf("%c ", root->val);
}
此时,可以写个代码测试一下:
int main()
{TreeNode* root = CreatBinaryTree();printf("BinaryTreePrevOrder:");BinaryTreePrevOrder(root);printf("\nBinaryTreeInOrder:");BinaryTreeInOrder(root);printf("\nBinaryTreePostOrder:");BinaryTreePostOrder(root);return 0;
}
运行结果:

也可以不打印NULL:

3.层序遍历
就是从上至下从左至右的遍历:

3.1递归实现
此实现方法并不重要,所以略讲。
先求个高度:
int TreeHeight(TreeNode* root)
{if (!root)return 0;int leftheight = TreeHeight(root->left);int rightheight = TreeHeight(root->right);return leftheight > rightheight ? 1 + leftheight : 1 + rightheight;
}
再写个打印第k层元素的函数:
void BinaryTreeLevelPrint(TreeNode* root, int level)
{if (!root)return;if (level == 1)printf("%c ", root->val);else{BinaryTreeLevelPrint(root->left, level - 1);BinaryTreeLevelPrint(root->right, level - 1);}
}
最后组合起来,即一层一层的打印:
void BinaryTreeLevelOrder(TreeNode* root)
{int level = TreeHeight(root);for (int i = 1; i < level; i++){BinaryTreeLevelPrint(root, i);}
}
测试一下:
int main()
{TreeNode* root = CreatBinaryTree();printf("BinaryTreeLevelOrder:");BinaryTreeLevelOrder(root);return 0;
}
结果:

3.2队列实现
二叉树层序遍历使用递归比较麻烦,且不太直观,因此,通常使用另一种方法,即队列:
- 先将根节点入队列

- 将队首的节点出队列,并带入当前节点的两个非空子节点

- 重复

- 再重复

- 队列为空,停止

其中,有关队列的函数可直接CV --> 数据结构-栈、队列-详解。
需注意的是,存入队列的是指向节点的指针,因此,需改变一下队列存储的数据类型:
//typedef int QDatatype;
typedef TreeNode* QDatatype;
代码实现:
void BinaryTreeLevelOrder(TreeNode* root)
{Queue q;QueueInit(&q);if (!root);QueuePush(&q, root);while (!QueueEmpty(&q)){TreeNode* tmp = QueueFront(&q);QueuePop(&q);printf("%c ", tmp->val);if (tmp->left)QueuePush(&q, tmp->left);if (tmp->right)QueuePush(&q, tmp->right);}printf("\n");QueueDestroy(&q);
}
常见疑问解答:
关于在Pop之后为什么还能用tmp访问节点?

因为,Pop的是队列的节点,tmp为局部变量,保存了队首元素的值,作为指针,指向树中的节点。
因此,在Pop之后还能用tmp访问节点。
关于都已经把队列Pop为空了为什么还要QueueDestroy?
我所写的队列是不带哨兵位头结点的,所以把队列Pop空了后,用不用QueueDestroy都无所谓,但如果其他人写的队列带了哨兵位头结点,不Destroy就会造成内存泄漏。
这里有个词叫耦合,还有个词叫解耦:
耦合是指两个或两个以上的体系或两种运动形式间通过相互作用而彼此影响以至联合起来的现象。
解耦就是用数学方法将两种运动分离开来处理问题,常用解耦方法就是忽略或简化对所研究问题影响较小的一种运动,只分析主要的运动。
在使用队列时,不管是怎样操作的,在最后都加上QueueDestroy,这也算是一种解耦,因为这样,无论队列是如何实现的,无论实现者用的数组还是链表、单链还是双链、带不带哨兵位的头结点,都可以避免问题的产生。
4.浅谈DFS与BFS
DFS:即Depth First Search,深度优先搜索。
二叉树的DFS就是前序遍历,放宽一点就是前、中、后序遍历。
特点是一条路走到底,再返回并走其他的路。多用递归实现。
BFS:即Breadth First Search,广度优先搜索。
二叉树的BFS就是层序遍历。
特点是一点点扩大搜索范围,类似于地毯式搜索。多用队列实现。
希望本篇文章对你有所帮助!并激发你进一步探索数据结构的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!
相关文章:
数据结构-链式二叉树-四种遍历
博客主页:【夜泉_ly】 本文专栏:【数据结构】 欢迎点赞👍收藏⭐关注❤️ 数据结构-链式二叉树-四种遍历 1.前言2.前、中、后序遍历2.1前序遍历2.1中、后序遍历 3.层序遍历3.1递归实现3.2队列实现关于在Pop之后为什么还能用tmp访问节点&#x…...
【YashanDB知识库】数据库获取时间和服务器时间不一致
本文转自YashanDB官网,具体内容可见数据库获取时间和服务器时间不一致 【问题分类】功能使用 【关键字】服务器时间、数据库时间 【问题描述】数据库获取的时间和服务器时间不一致。 【问题原因分析】YashanDB并没有时区的概念,数据库的时间以数据库启…...
十大排序之:冒泡排序
目录 一、简介 实现过程 时间复杂度 二、代码实现 函数声明 Swap函数 单趟 多趟 测试 优化 一、简介 冒泡排序是一种简单的排序算法,它重复地比较相邻的两个元素,如果顺序错误就交换它们,直到没有元素需要交换为止。这个过程类…...
【MPC】无人机模型预测控制复现Data-Driven MPC for Quadrotors项目(Part 1)
无人机模型预测控制复现Data-Driven MPC for Quadrotors项目 参考链接背景和问题方法与贡献实验结果安装ROS创建工作空间下载RotorS仿真器源码和依赖创建Python虚拟环境下载data_driven_mpc仓库代码下载并配置ACADO求解器下载并配置ACADO求解器的Python接口下载并配置rpg_quadr…...
微信小程序开发——比较两个数字大小
在这里我们使用的工具是 需要自行安装和配置。 在微信小程序中比较两个数字大小有以下几种方式: 一、普通条件判断 在小程序的.js 文件中,先定义两个数字,如let num1 5; let num2 3;。通过if - else if - else语句,根据num1与…...
Java多线程3
1.有序性在并发编程中的含义。 有序性在并发编程中指的是在多线程环境下,程序的执行顺序应与单线程情况下保持一致,以避免出现不确定或错误的执行结果。 2.为何需要使用多线程进行程序设计? 使用多线程可以提高程序的效率,利用…...
node+Vue项目环境创建
nodeVue项目环境创建 使用淘宝镜像源使用官方镜像源()清除缓存取消取消ssl验证安装vue 使用淘宝镜像源 npm config set registry https://registry.npm.taobao.org/使用官方镜像源() 由于国内网络问题,安装报错 npm install -g cnpm --registryhttps://registry.…...
云智AI人工智能平台——与众不同之处
人工智能领域、深度学习、强化学习、大小模型盛行的时代,人工智能技术正以前所未有的速度改变着我们的世界。然而,在众多AI平台中,如何选择一个既高效又灵活的工具,成为了每个开发者心中的难题。今天,我们特别介绍一款…...
国庆节有什么好物值得入手?精选国庆节必选好物合集
一年一度的国庆节马上来临了,平时舍不得买的好物可以在国庆节这段时间大采购了,毕竟这可是年度购物的好时机,千万不要错过这个享受优惠的机会。还不知道买什么国庆节好物的朋友可以看看本篇文章,提前做好功课噢! 好物…...
并发安全与锁
总述 这篇文章,我想谈一谈自己对于并发变成的理解与学习。主要涉及以下三个部分:goroutine,channel以及lock 临界区 首先,要明确下面两组概念 并发和并行 并行:指几个程序每时每刻都同时进行 并发:指…...
细胞分裂检测系统源码分享
细胞分裂检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…...
openssl 生成多域名 多IP 的数字证书
openssl.cnf 文件内容: [req] default_bits 2048 distinguished_name req_distinguished_name copy_extensions copy req_extensions req_ext x509_extensions v3_req prompt no [req_distinguished_name] countryName CN stateOrProvinceName GuangDong l…...
电影评论|基于springBoot的电影评论网站设计与实现(附项目源码+论文+数据库)
私信或留言即免费送开题报告和任务书(可指定任意题目) 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取: 一、摘要 随着信息技术在管理上越来越深入而广泛的应用,…...
【C++】虚函数
一、什么是虚函数 在类的成员函数前加上virtual关键字,这个函数就是虚函数。 虚函数的所用就是完成多态。多态示例如下: class A {public:virtual void func()//虚函数{cout << "A" << endl;}void ftwo()//普通函数{cout <&…...
esxi虚拟机启用cbt备份(增量备份)
在虚拟机中启用CBT 1.关闭虚拟机。 右键点按虚拟机,Edit Settings-VM Options-Advanced-Configuration Parameters-Edit Configuration- Add parameters,添加ctkEnabled参数,并将其值设置为true。 Add parameters,添加scsi0:0…...
mysql 8.0 时间维度表生成(可运行)
文章目录 mysql 8.0 时间维度表生成实例时间维度表的作用时间维度表生成技术细节使用时间维度表的好处 mysql 8.0 时间维度表生成实例 时间维度表的作用 dim_times(时间维度表)在数据仓库(Data Warehouse)中的作用至关重要。作为…...
打造高效实时数仓,从Hive到OceanBase的经验分享
本文作者:Coolmoon1202,大数据高级工程师,专注于高性能软件架构设计 我们的业务主要围绕出行领域,鉴于初期采用的数据仓库方案面临高延迟、低效率等挑战,我们踏上了探索新数仓解决方案的征途。本文分享了我们在方案筛选…...
15.3 JDBC数据库编程
15.3 JDBC数据库编程 15.3.1 创建数据库和表 创建一个名为webstore的数据库,并向其中添加数据,代码如下: 1.创建数据库 CREATE TABLE products( id int PRIMARY KEY, pname VARCHAR(20) brand VARCHAR(20), price FLOAT(7,2), stock SMALLINT, ) …...
SSH公私钥后门从入门到应急响应
目录 1. SSH公私钥与SSH公私钥后门介绍 1.1 SSH公私钥介绍 1.1.1 公钥和私钥的基本概念 1.1.2 SSH公私钥认证的工作原理(很重要) 1.2 SSH公私钥后门介绍 2. 如何在已拿下控制权限的主机创建后门 2.1 使用 Xshell 生成公钥与私钥 2.2 将公钥上传到被需要被植入后门的服务…...
服务器数据恢复—Linux操作系统环境下网站数据的恢复案例
服务器数据恢复环境: 一台linux操作系统服务器上跑了几十个网站,服务器上只有一块SATA硬盘。 服务器故障: 服务器突然宕机,尝试再次启动失败。将硬盘拆下检测,发现存在坏扇区。找当地一家数据恢复公司处理后ÿ…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...
绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化
iOS 应用的发布流程一直是开发链路中最“苹果味”的环节:强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说,这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发(例如 Flutter、React Na…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...
数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)
目录 🔍 若用递归计算每一项,会发生什么? Horners Rule(霍纳法则) 第一步:我们从最原始的泰勒公式出发 第二步:从形式上重新观察展开式 🌟 第三步:引出霍纳法则&…...
年度峰会上,抖音依靠人工智能和搜索功能吸引广告主
上周早些时候举行的第五届年度TikTok World产品峰会上,TikTok推出了一系列旨在增强该应用对广告主吸引力的功能。 新产品列表的首位是TikTok Market Scope,这是一个全新的分析平台,为广告主提供整个考虑漏斗的全面视图,使他们能够…...
