数据结构-链式二叉树-四种遍历
博客主页:【夜泉_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硬盘。 服务器故障: 服务器突然宕机,尝试再次启动失败。将硬盘拆下检测,发现存在坏扇区。找当地一家数据恢复公司处理后ÿ…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...