数据结构-链式二叉树-四种遍历
博客主页:【夜泉_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硬盘。 服务器故障: 服务器突然宕机,尝试再次启动失败。将硬盘拆下检测,发现存在坏扇区。找当地一家数据恢复公司处理后ÿ…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
Linux系统部署KES
1、安装准备 1.版本说明V008R006C009B0014 V008:是version产品的大版本。 R006:是release产品特性版本。 C009:是通用版 B0014:是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存:1GB 以上 硬盘…...
