二叉树遍历算法和应用

二叉树是指度为 2 的树。它是一种最简单却又最重要的树,在计算机领域中有这广泛的应用。

二叉树的递归定义如下:二叉树是一棵空树,或者一棵由一个根节点和两棵互不相交的分别称为根节点的左子树和右子树所组成的非空树,左子树和右子树同样都是一棵二叉树。在二叉树中,每个节点的左子树的根节点被称为左孩子节点,右子树被称为右孩子节点。

二叉树通常使用如下数据结构来表示:

data 表示二叉树节点的数值,left_child 表示节点的左孩子节点,right_child 表示节点的右孩子节点。

struct node {int data;struct node *left_chile;struct node *right_child;
}

从二叉树的定义来看,有以下两点;

(1) 二叉树可以是一棵空树

(2) 二叉树的左子树和右子树不一定存在,可能同时不存在,可能同时存在,也可能左子树和右子树只存在一个

为什么要有二叉树 ?

在我们使用数据结构来存储数据的时候,最常见的两个使用场景是查找和插入。查找就是从数据结构中找到我们想要的值,插入是将一个新值插入到数据结构正确的位置。

在数据结构中,最常用的两个基础的数据结构是数组和链表,数组和链表的区别可以从如下 3 个方面来看:

① 插入元素。数组的插入效率比链表低,因为数组插入的时候,插入位置后边的元素都要向后移动;而向链表中插入元素的时候,只需要改变节点的指向就可以。前者的时间复杂度是 O(n) 的,后者的时间复杂度是 O(1) 的。

② 读取元素。读取数组中的第几个元素的时候,使用数组的下标直接读取就可以,而读取链表中的第几个元素的时候,需要对链表进行遍历。前者的时间复杂度是 O(1) 的,后者的时间复杂度是 O(n) 的。

③ cache。数组用一块连续的内存来维护,链表中每个节点的地址并不连续。从 cpu cache  LRU 算法的角度来看的话,数组中的元素离的更近,所以访问性能会更高。

从数组和链表的对比来看,没有哪种数据结构是具备所有的优点的。

二叉树有 3 个重要的变种,分别是二叉排序树,二叉平衡树,红黑树。

(1)二叉排序树

二叉排序数是一棵特殊的二叉树,在一般的二叉树中,区分左子树和右子树,但结点的值是无序的。在二叉排序树中,不仅区分左子树和右子树,而且整个树的节点是有序的。

二叉排序树又称二叉搜索树,它或者是一棵空树,或者是一棵非空二叉树,具备如下 3 个特点:

① 如果左子树非空,那么左子树上所有节点的值小于根节点的值

② 如果右子树非空,那么右子树上所有节点的值大于根节点的值,如果允许节点的值相等,那么大于等于

③ 左、右子树本身又各是一棵二叉排序树

从分析可以得出,在查找的性能上,二叉排序树的时间复杂度是 O(logn),相对于链表的 O(n),是有优化的。但是二叉排序树最差的情况,是每个节点都只包含一个子节点,这种情况下的时间复杂度就成为了 O(n),所以在二叉排序树的基础上出现了二叉平衡树。

(2)二叉平衡树

二叉平衡树首先是一颗二叉排序树,在此基础上又增加了一个条件,即每个节点的左子树和右子树的高度之差不大于 1。

有了这个限制之后,查找的时间复杂度就不会出现 O(n) 的情况。但是当向平衡树中插入元素的时候,为了保证插入之后二叉树还能满足平衡树的要求,需要进行旋转操作。对于频繁插入,删除的场景,旋转操作过于频繁,反而会增加时间的消耗。所以出现了红黑树,红黑树在查找和插入操作之间取得了平衡(这样的结论,只有专门研究红黑树算法的专业人员才能了解,在很多文章和书籍中也是这么说的,我们只需要知道这个事情即可)

(3)红黑树

红黑树的前提也是一颗二叉排序,在此基础上增加了以下 5 个条件:

① 红黑树中的节点要么是黑色,要么是红色

② 红黑树的根节点是黑色

③ 红黑树中红色节点不能连续,也就是说一个红色节点的父节点和子点都不能是红色

④ 从根节点开始,到叶子节点,每条路径上的黑色节点个数相等

⑤ 叶子节点是黑色,在红黑树中的叶子结点不是真实存在的,叶子结点在下图中用矩形来表示

在工程应用中,本人还没见过使用平衡树的场景,只见过使用红黑树的场景。

① 在 linux 内核中,维护 struct task_struct 使用了红黑树;维护 tcp 接收侧的乱序队列,使用了红黑树;epoll 中维护监听的 fd,也是用了红黑树

② 在 c++,java 内置的 map 数据结构中,使用了红黑树

本文只记录二叉树的基本算法,不涉及二叉排序树,二叉平衡树,红黑树相关的算法。

1 二叉树遍历

二叉树的遍历有 4 中算法:前序遍历,中序遍历,后序遍历以及层序遍历。前 3 种遍历算法中的前、中、后,说的是根节点的位置:前序遍历的顺序是根节点,左子节点,右子节点;中序遍历的顺序是左子节点,根节点,右子节点;后序遍历的顺序是左子节点,右子节点,根节点。不管哪种遍历,左子节点都在右子节点之前遍历。

如果了解图的遍历算法,可以知道图的遍历算法包括两种:深度优先遍历和广度优先遍历。二叉树的遍历方法中,前 3 种类似于图中的深度优先遍历,层序遍历类似于广度优先遍历。

二叉树跟递归算法联系比较紧密,递归算法有两点组成,一个是递归退出条件,一个是递归计算逻辑,也可以称为递归体,这一点和循环是有点相似的,循环也要有循环停止条件和循环处理逻辑。每种算法的对象都是围绕着一个数据结构,都需要有一个驱动力,这个驱动力往往就是对数据结构中的元素进行遍历,对于数组来说往往就是沿着数组下标进行遍历,对于链表来说往往就是从 head 开始沿着 next 向下遍历,对于二叉树来说就是沿着 left child 和 right child 进行遍历。对数据进行遍历是算法的基础。

递归算法,不太适合从一开始沿着算法逐渐向下思考,使用递归算法的时候我们可以使用一个最简单的场景来理解。比如,对于二叉树来说,我们以只有 3 个节点(一个 root,一个 left child,一个 right child)的二叉树来思考和理解。

1.1 前序遍历

力扣:. - 力扣(LeetCode)

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {if (root == nullptr) {return ret;}preScan(root);return ret;}void preScan(TreeNode *root) {ret.push_back(root->val);if (root->left) {preScan(root->left);}if (root->right) {preScan(root->right);}}private:vector<int> ret;
};

1.2 中序遍历

力扣:. - 力扣(LeetCode)

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {if (root == nullptr) {return ret;}midScan(root);return ret;}void midScan(TreeNode *root) {if (root->left) {midScan(root->left);}ret.push_back(root->val);if (root->right) {midScan(root->right);}}private:vector<int> ret;
};

1.3 后续遍历

力扣:. - 力扣(LeetCode)

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {if (root == nullptr) {return ret;}postScan(root);return ret;}void postScan(TreeNode *root) {if (root->left) {postScan(root->left);}if (root->right) {postScan(root->right);}ret.push_back(root->val);}private:vector<int> ret;
};

1.4 层序遍历

力扣:. - 力扣(LeetCode)

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {std::queue<TreeNode *> q;std::vector<vector<int>> ret;if (root == nullptr) {return ret;}q.push(root);while (!q.empty()) {// 用来保存二叉树一层的数据// 如果只是遍历,不用保存数据的话,那么是用不着这个数据结构的vector<int> line;int q_size = q.size();for (int i = 0; i < q_size; i++) {TreeNode *node = q.front();q.pop();line.push_back(node->val);if (node->left) {q.push(node->left);}if (node->right) {q.push(node->right);}}ret.push_back(line);}return ret;}
};

2 二叉树遍历的应用

2.1 求二叉树高度

力扣:. - 力扣(LeetCode)

求二叉树的高度,也是使用递归算法。有两种算法:

(1)求出左子树和右子树的高度,然后两者的较大值加 1 就是二叉树的高度

(2)使用动态规划的算法,每一次递归计算的时候,相当于向下增加了一层,在递归函数的形参中实时维护已经遍历的二叉树的高度,二叉树遍历完之后,最大值保存的就是二叉树的高度

2.1.1 左子树和右子树较大值

int maxDepth(struct TreeNode* root) {if (root == NULL) {return 0;}int left_height = 0;int right_height = 0;if (root->left) {left_height = maxDepth(root->left);}if (root->right) {right_height = maxDepth(root->right);}if (left_height > right_height) {return 1 + left_height;} else {return 1 + right_height;}
}

2.1.2 动态规划

class Solution {
public:int maxDepth(TreeNode* root) {maxDepthHelper(root, 0);return max_depth_;}void maxDepthHelper(TreeNode *root, int depth) {if (root == nullptr) {return;}depth++;if (depth > max_depth_) {max_depth_ = depth;}if (root->left) {maxDepthHelper(root->left, depth);}if (root->right) {maxDepthHelper(root->right, depth);}}private:int max_depth_ = 0;
};

2.2 求二叉树宽度

求二叉树的宽度和求二叉树的高度,算法是类似的,都可以通过动态规划的方式来进行。

在递归函数中用一个形参实时表示当前遍历的是哪一层的节点,每遍历一个节点都将这一层的宽度加 1。使用一个数组保存每一层的宽度,遍历结束之后,再遍历数组找到宽度最大的这一层。

class Solution {
public:int maxWidth(TreeNode* root) {maxWidthHelper(root, 0);for (int i = 0; i < max_depth_; i++) {if (width[i] > max_width_) {max_width_ = width[i];}}return max_width_;}void maxDepthHelper(TreeNode *root, int depth) {if (root == nullptr) {return;}width[depth] = width[depth] + 1;depth++;if (depth > max_depth_) {max_depth_ = depth;}if (root->left) {maxWidthHelper(root->left, depth);}if (root->right) {maxWidthHelper(root->right, depth);}}private:int max_width_ = 0;int max_depth_ = 0;int width[100] = {0}; // 定义一个保存每一层宽度的数组,假设二叉树的高度不会超过 100
};

2.3 二叉树路径

力扣:. - 力扣(LeetCode)

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<string> binaryTreePaths(TreeNode* root) {tmp_data_path.push_back(root->val);binaryTreeScan(root);return ret;}void binaryTreeScan(TreeNode *root) {if (root->left == nullptr && root->right == nullptr) {string str_path = makePath();ret.push_back(str_path);return;}if (root->left) {// 使用递归算法找路径的典型代码// 三段式:push back --> 递归运算 --> pop back// 为什么递归运算结束之后,需要将元素 pop 出来// 因为通过递归算法,和这个节点相关的路径都已经遍历到了// 后边再遍历的路径,与这个节点无关了,所以需要把这个节点 pop 出来// 以防影响后边的路径// 图的遍历找路径的算法题中,主要的递归逻辑也是类似的tmp_data_path.push_back(root->left->val);binaryTreeScan(root->left);tmp_data_path.pop_back();}if (root->right) {tmp_data_path.push_back(root->right->val);binaryTreeScan(root->right);tmp_data_path.pop_back();}}string makePath() {int size = tmp_data_path.size();std::string str;for (int i = 0; i < size; i++) {if (i <  size - 1) {str += std::to_string(tmp_data_path[i]) + "->";} else {str += std::to_string(tmp_data_path[i]);}}return str;}private:vector<string> ret;vector<int> tmp_data_path;
};

2.4 二叉树翻转

力扣:. - 力扣(LeetCode)

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:TreeNode* invertTree(TreeNode* root) {if (root == nullptr) {return root;}binaryTreeScan(root);return root;}void binaryTreeScan(TreeNode *root) {TreeNode *left = root->left;root->left = root->right;root->right = left;if (root->left) {binaryTreeScan(root->left);}if (root->right) {binaryTreeScan(root->right);}}
};

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

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

相关文章

【前端】解决前端图表大数据配色难题:利用HSL动态生成颜色方案

解决前端图表大数据配色难题&#xff1a;利用HSL动态生成颜色方案 在数据可视化项目中&#xff0c;尤其是当需要绘制包含大量数据点的图表时&#xff0c;一个常见的挑战是如何为每个数据点分配一个独特而又视觉上容易区分的颜色。使用固定的颜色列表可能在数据点数量超过列表限…

信号完整性之特性阻抗那些事儿

原文来自微信公众号&#xff1a;工程师看海&#xff0c;与我联系&#xff1a;chunhou0820 看海原创视频教程&#xff1a;《运放秘籍》 大家好&#xff0c;我是工程师看海。 我们经常说控制阻抗&#xff0c;这个阻抗是什么意思呢&#xff1f; 信号在传输线中&#xff0c;是一步…

使用Android Studio制作一个蓝牙软件 ---(一)

一、创建项目&#xff08;项目名称---BluetoothActivity&#xff09; 二、创建HomeActivity页面 1.点击一个文件夹 --- 鼠标右击 --- 新建 --- Acyivity --- Empty Views Activity 三、创建styles.xml 点击 values文件夹 --- 鼠标右击 --- 新建 --- Values Resource File sty…

【数据结构】04串

串 1. 定义2. 串的比较3. 串的存储结构4. 具体实现5. 模式匹配5.1 常规思路实现5.2 KMP模式匹配算法5.2.1 next数组计算5.2.1 代码计算next数组5.2.2 KMP算法实现 1. 定义 串(string)是由零个或多个字符组成的有限序列&#xff0c;又叫字符串。 一般记为s a 1 , a 2 , . . . ,…

NGO-VMD+皮尔逊系数+小波阈值降噪+重构

NGO-VMD皮尔逊系数小波阈值降噪重构 NGO-VMD皮尔逊系数小波阈值降噪重构代码获取戳此处代码获取戳此处 以西储大学轴承数据为例&#xff0c;进行VMD&#xff0c;且采用NGO进行K a参数寻优 并对分解分量计算皮尔逊相关系数筛选含噪声分量&#xff0c;对其进行小波软硬阈值降噪&a…

C/C++内存泄漏及检测

“该死系统存在内存泄漏问题”&#xff0c;项目中由于各方面因素&#xff0c;总是有人抱怨存在内存泄漏&#xff0c;系统长时间运行之后&#xff0c;可用内存越来越少&#xff0c;甚至导致了某些服务失败。内存泄漏是最难发现的常见错误之一&#xff0c;因为除非用完内存或调用…

【JAVA基础篇教学】第十篇:Java中Map详解说明

博主打算从0-1讲解下java基础教学&#xff0c;今天教学第十篇&#xff1a;Java中Map详解说明。 在 Java 编程中&#xff0c;Map 接口代表了一种键值对的集合&#xff0c;每个键对应一个值。Map 接口提供了一系列操作方法&#xff0c;可以方便地对键值对进行增删改查等操作。本…

模板方法模式:定义算法骨架的设计策略

在软件开发中&#xff0c;模板方法模式是一种行为型设计模式&#xff0c;它在父类中定义一个操作的算法框架&#xff0c;允许子类在不改变算法结构的情况下重定义算法的某些步骤。这种模式是基于继承的基本原则&#xff0c;通过抽象类达到代码复用的目的。本文将详细介绍模板方…

NASA数据集——亚洲夏季季风化学与气候影响项目超高灵敏度气溶胶光谱(UHSAS)数据

ACCLIP_Aerosol_AircraftInSitu_WB57_Data 简介 ACCLIP_Aerosol_AircraftInSitu_WB57_Data 是亚洲夏季季风化学与气候影响项目&#xff08;ACCLIP&#xff09;期间收集的原地气溶胶数据。本数据集收录了来自下一代激光质谱仪&#xff08;PALMS-NG&#xff09;、单颗粒烟尘光度…

一文读懂Partisia Blockchain,被严重低估的隐私区块链生态

在今年 3 月&#xff0c;隐私公链 Partisia Blockchain 迎来了重要的进展&#xff0c;该生态通证 $MPC 上线了交易所&#xff0c;目前 $MPC 通证可以在 Kucoin、Gate、BitMart、Bitfinex、Bitture 等平台交易&#xff0c;并将在不久后上线 MEXC 平台。 ​ 在上个月上线市场至今…

【项目实战】记录一次PG数据库迁移至GaussDB测试(上)

目录 一、说明 1.1、参考文档 1.2、注意事项 1.3、环境基本情况 二、GaussDB新环境安装 2.1 配置操作环境变量 2.1.1 关闭防火墙 步骤1 执行以下命令&#xff0c;检查防火墙是否关闭。 步骤2 执行以下命令&#xff0c;关闭防火墙并禁止开机启动。 步骤3 修改/etc/sel…

单细胞RNA测序(scRNA-seq)Cellranger流程入门和数据质控

单细胞RNA测序(scRNA-seq)Cellranger流程入门和数据质控 单细胞RNA测序(scRNA-seq)基础知识可查看以下文章: 单细胞RNA测序(scRNA-seq)工作流程入门 单细胞RNA测序(scRNA-seq)细胞分离与扩增 1. 单细胞RNA-seq样本数据说明 样本数据来源文章:Acquired cancer re…

【计算机毕业设计】基于微信小程序的开发项目150套(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f9e1;今天给大家分享200的微信小程序毕业设计&#xff0c;后台用Java开发&#xff0c;这些项目都经过精心挑选&#xff0c;涵盖了不同的实战主题和用例&#xff0c;可做毕业设…

MQ概览及Kafka详解

文章目录 概览MQ优点MQ缺点常见MQ对比JMS消息模型点对点模式发布订阅模式 kafka基础架构发布订阅工作流程生产者生产者文件存储生产者分区策略生产者数据可靠性保证生产者数据一致性保证生产者ack机制ExactlyOnce生产者发送消息流程 消费者消费者分区分配策略消费者消费数据问题…

算法设计与分析实验报告c++实现(TSP问题、哈夫曼编码问题、顾客安排问题、最小生成树问题、图着色问题)

一、实验目的 1&#xff0e;加深学生对贪心算法设计方法的基本思想、基本步骤、基本方法的理解与掌握&#xff1b; 2&#xff0e;提高学生利用课堂所学知识解决实际问题的能力&#xff1b; 3&#xff0e;提高学生综合应用所学知识解决实际问题的能力。 二、实验任务 用贪心算…

streamlit 大模型前段界面

结合 langchain 一起使用的工具&#xff0c;可以显示 web 界面 pip install streamlit duckduckgo-search 运行命令 streamlit run D:\Python_project\NLP\大模型学习\test.py import os from dotenv import load_dotenv from langchain_community.llms import Tongyi load…

matlab学习002-函数及流程控制语句

目录 一&#xff0c;matlab编程基础 1&#xff09;matlab脚本和函数文件 ①脚本文件 ②函数文件 2&#xff09;函数的定义和调用 ①定义 ②调用 3&#xff09;程序流程控制 ①使用for求 122^2……2^622^63之和 ②使用while语句求122^2……2^622^63之和 ③使用matl…

Python学习(三)

函数扩展 多返回值 参数扩展 位置参数 注意:传递的参数和定义的参数的顺序及个数必须一致 关键字参数 关键字名称必须和形参名称相同&#xff0c;形参叫name&#xff0c;那么关键字也要写name 不定长参数 缺省参数

【优选算法专栏】专题十:哈希表(一)

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

mutable关键字的作用(c++)

常成员变量、常成员函数与常对象 常成员变量 声明为常成员变量的成员变量&#xff0c;在对象被创建后就不能被修改常成员变量必须在对象的构造函数初始化列表中赋值&#xff0c;不能在构造函数体中赋值初始化列表的执行是在函数体执行之前就执行了的。上面这种写法和下面的写…