【算法练习Day12】树的递归遍历非递归遍历

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:练题
🎯长路漫漫浩浩,万事皆有期待

文章目录

  • 递归遍历
    • 前序遍历
    • 中序遍历
    • 后序遍历
  • 非递归遍历
    • 前序遍历
    • 后序遍历
    • 中序遍历
    • 标记迭代法
  • 总结:

这一期讲树这个数据结构的相关知识

首先我们要明白树的两种通用遍历分别是深度优先搜索,和广度优先搜索。这里我们介绍深度优先搜索的三种表现形式:前序遍历,中序遍历和后序遍历。这三种搜索方式可以用递归法或者迭代法表示出来。事实上,很多递归能写出来的代码,大都可以使用迭代法表示出来。

递归遍历

前序遍历

class Solution 
{
public:
vector<int>result;
void dfs(TreeNode* root)
{if(root)result.push_back(root->val);if(root)dfs(root->left);if(root)dfs(root->right);
}vector<int> preorderTraversal(TreeNode* root) {if(root==nullptr)return result;dfs(root);return result;}
};

前序遍历的规则是“中左右“,即先遍历树的中间节点,再分别遍历左右两子树,并在其遍历左右两子树时仍然遵循此规则。所以我们可以容易的理解dfs代码部分先将中间节点保存后,分别进行左子树和右子树的递归。

中序遍历和后序遍历的递归代码,都和前序遍历差不多,只是略微调整一下进入子树的时机而已,下面直接给出代码。

中序遍历

class Solution 
{
public:void dfs(TreeNode* root,vector<int>& result){if(root==nullptr)return ;dfs(root->left,result);result.push_back(root->val);dfs(root->right,result);}vector<int> inorderTraversal(TreeNode* root) {vector<int>result;dfs(root,result);return result;}
};

后序遍历

class Solution 
{
public:void dfs(TreeNode* root,vector<int>& result){if(root==nullptr)return ;dfs(root->left,result);dfs(root->right,result);result.push_back(root->val);}vector<int> inorderTraversal(TreeNode* root) {vector<int>result;dfs(root,result);return result;}
};

说完了递归遍历,我们再来看看非递归遍历

非递归遍历

非递归遍历中的迭代遍历,前后序的代码是差不多的,但是中序遍历有很大差别

前序遍历

先说前序遍历的迭代,思路是用一个栈来模拟递归的操作

为什么我们会想到使用栈来模拟呢?

因为递归实际上就是编译器将函数各参数放入递归内部,返回时再将其弹出,所以我们用一个栈来模拟递归操作,是再合适不过的。我们写一个循环判断栈中是否为空,为空则说明没有元素要处理了,那么循环内的逻辑就是先创立一个临时的节点指针指向当前栈口处元素,先判断其是否为空,为空不能操作,否则会操作空指针。
不为空时我们先将遍历到的节点直接放入数组中,因为前序遍历是先处理中间节点,这之后我们按照先放入右节点再放入左节点的规律来使节点指针向后遍历。

这是为什么呢?

原因在于栈的独特定义,我们要先放入右节点再放入左节点,才能在下一步时候先处理左节点!以下代码

class Solution 
{
public:vector<int> preorderTraversal(TreeNode* root) {stack<TreeNode*> s;vector<int>result;if(root==nullptr)return result;s.push(root);//加入的是节点而并非节点对应的值,这里要尤其注意while(!s.empty()){TreeNode* node=s.top();s.pop();if(node)result.push_back(node->val);else continue;s.push(node->right);s.push(node->left);}return result;}
};

后序遍历

后序遍历思路很类似,需要注意的是后序遍历的情况为”左右中“,我们可以按照前序遍历的代码模板将前序遍历的压栈操作改为先放入左节点再放入右节点,这样它对应的原本遍历方法是中右左,当全部遍历完成之后,我们将数组部分反转,即可得到后序遍历。

class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {stack<TreeNode*>s;vector<int>result;if(root==nullptr)return result;s.push(root);while(!s.empty()){TreeNode*node=s.top();s.pop();if(node)result.push_back(node->val);else continue;s.push(node->left);s.push(node->right);}reverse(result.begin(),result.end());return result;}
};

这里可以看出,后序和前序的代码差别不大,也就是改了入栈的顺序,和反转了一下数组。

中序遍历

接着,我们再来看看中序遍历。

中序遍历的思路是:我们需要建立一个指针来存储各节点,然后我们将它们放入栈内,我们先一直向二叉树的左节点遍历直到无法继续为止,弹出栈顶元素,此时栈顶元素即为我们要找的元素,为什么呢?

因为我们是一直向左走,直到无法再向左走,弹出的那个元素,此时就是这个子树的中间节点(由于没有左节点)直接放入答案数组内,再遍历这个节点的右节点(不要忘记将其压入栈内),如果它有左节点接着遍历,如果它没有,那么就直接弹出,重复上述操作。

为什么中序遍历不能像前后序那样只调整位置呢?
拿前序遍历举例它是先遍历的中间节点也是先处理的中间节点,后序的代码前面思路也是如此,只是后面有调整位置,换句话说,是此时遍历的节点正是我们此时要处理的数据!

那我们在遍历中序时候可以先遍历左边子树达成一样的逻辑吗?答案肯定是否定的,因为我们一开始只有root这个节点,而它指向了根节点,也就是说我们注定是先遍历中间节点,所以这样的方法并不适合中序迭代。

class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {stack<TreeNode*>s;vector<int>result;TreeNode*cur=root;while(cur!=nullptr||!s.empty()){if(cur){s.push(cur);cur=cur->left;}else{cur=s.top();s.pop();if(cur)result.push_back(cur->val);cur=cur->right;}}return result;}
};

标记迭代法

那么有没有可以将三种迭代统一起来的代码呢?也是有的,这里的思路也是创建一个栈来模拟,不同的是,我们将凡是已经遍历过的数据一股脑地放进去,在我们要处理的数据之后紧接着加入一个null来标记,这种思路也被称为标记迭代法。当我们遍历到null的时候,就对下一数据进行加入答案数组的处理。

中序

class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {vector<int> result;stack<TreeNode*> st;if (root != NULL) st.push(root);while (!st.empty()) {TreeNode* node = st.top();if (node != NULL) {st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中if (node->right) st.push(node->right);  // 添加右节点(空节点不入栈)st.push(node);                          // 添加中节点st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。if (node->left) st.push(node->left);    // 添加左节点(空节点不入栈)} else { // 只有遇到空节点的时候,才将下一个节点放进结果集st.pop();           // 将空节点弹出node = st.top();    // 重新取出栈中元素st.pop();result.push_back(node->val); // 加入到结果集}}return result;}
};

该思路旨在if条件语句中处理压栈操作,else里处理加入答案数组,使代码变得简介,但是思路难想到,建议只记一种思路。

总结:

今天我们完成了树的递归遍历和非递归遍历,了解了一种新的方法标记迭代法,相关的思想需要多复习回顾。接下来,我们继续进行算法练习。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

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

相关文章

《计算机视觉中的多视图几何》笔记(12)

12 Structure Computation 本章讲述如何在已知基本矩阵 F F F和两幅图像中若干对对应点 x ↔ x ′ x \leftrightarrow x x↔x′的情况下计算三维空间点 X X X的位置。 文章目录 12 Structure Computation12.1 Problem statement12.2 Linear triangulation methods12.3 Geomet…

AndroidStudio精品插件集

官网 项目地址&#xff1a;Github博客地址&#xff1a;Studio 精品插件推荐 使用需知 所有插件在 Android Studio 2022.3.1.18&#xff08;长颈鹿&#xff09;上测试均没有问题&#xff0c;推荐使用此版本Android Studio 2022.3.1.18&#xff08;长颈鹿&#xff09;正式版下…

计算机网络(六):应用层

参考引用 计算机网络微课堂-湖科大教书匠计算机网络&#xff08;第7版&#xff09;-谢希仁 1. 应用层概述 应用层是计算机网络体系结构的最顶层&#xff0c;是设计和建立计算机网络的最终目的&#xff0c;也是计算机网络中发展最快的部分 早期基于文本的应用 (电子邮件、远程登…

【计算机网络】HTTPS协议详解

文章目录 一、HTTPS协议 介绍 1、1 HTTP协议不安全的体现 1、2 什么是 HTTPS协议 二、加密的一些概念 2、1 怎么理解加密 2、2 为什么要加密 2、3 常见的加密方式 2、2、1 对称加密 2、2、2 非对称加密 三、HTTPS协议探究加密过程 3、1 只使用对称加密 3、2 只是用非对称加密 3…

JVM篇---第三篇

系列文章目录 文章目录 系列文章目录一、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?二、Java内存结构三、说说对象分配规则一、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”? Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文…

23.3 Bootstrap 框架4

1. 轮播 1.1 轮播样式 在Bootstrap 5中, 创建轮播(Carousel)的相关类名及其介绍: * 1. carousel: 轮播容器的类名, 用于标识一个轮播组件. * 2. slide: 切换图片的过渡和动画效果. * 3. carousel-inner: 轮播项容器的类名, 用于包含轮播项(轮播图底下椭圆点, 轮播的过程可以显…

【Docker】搭建 Docker 镜像仓库

文章目录 前言&#xff1a;公有仓库和私有仓库公共镜像仓库私有镜像仓库 一、搭建 Docker 镜像仓库1.1 搭建简化版的镜像仓库1.2 搭建带有图形化界面的镜像仓库1.3 配置 Docker 信任地址 二、向私有镜像仓库推送和拉取镜像2.1 推送本地镜像到私有仓库2.2 拉取私有仓库中的镜像 …

机器学习笔记(二)

过拟合 如下图左边,模型出现了过拟合现象 为了解决过拟合现象, 其中一个做法是多收集数据,如右图。 第二种做法是减少模型的特征数量,即x 第三种做法是正则化 正则化就是减少x前面的参数 w的数值, 不用消除x 正则化的梯度下降如下, 因为只是缩小了w的值,而 b的值保持不变 …

通过BeanFactotyPostProcessor动态修改@FeignClient的path

最近项目有个需求&#xff0c;要在启动后&#xff0c;动态修改FeignClient的请求路径&#xff0c;网上找到的基本都是在FeignClient里使用${…}&#xff0c;通过配置文件来定义Feign的接口路径&#xff0c;这并不能满足我们的需求 由于某些特殊原因&#xff0c;我们的每个接口…

floyd算法细节

这个不是一篇学习性文章 主要是针对这几天思考的问题进行一些回答 floyD在计网和数据结构和图模型中有广泛的应用算法 很简单但是其中蕴含的原理值得细究。 弗洛伊德算法(Floyd)主要针对多源最短路径,且可以解决路径中有负权的情况(不包含负权回路),但是迪杰斯特拉算法只…

uni-app:实现页面效果3

效果 代码 <template><view><!-- 风速风向检测器--><view class"content_position"><view class"content"><view class"SN"><view class"SN_title">设备1</view><view class&quo…

【新的小主机】向日葵远程控制ubuntu

向日葵远程控制ubuntu 一、简介二、问题及解决方法2.1 向日葵远程连接Ubuntu22主机黑屏&#xff1f;2.2 Ubuntu如何向日葵开机自启&#xff1f;2.3 无显示器情况下&#xff0c;windows远程桌面连接Ubuntu? 三、美化桌面3.1 安装/解压3.2 设置3.3 右上角显示实时网速 四、安装d…

IBT机考-PBT笔考,优劣分析,柯桥口语学习,韩语入门,topik考级韩语

IBT机考&#xff0c;顾名思义就是在电脑上答题考试&#xff0c;区别于现在的PBT纸笔答题&#xff0c;不需要发卷、收卷&#xff0c;也不需要填涂和用笔写字。 考试不需要带任何文具&#xff0c;就连笔试要用到的修正带都将省去。因为听力、阅读的选择题都是用鼠标点击&#xf…

SpringCloud Alibaba - Seata 四种分布式事务解决方案(XA、AT)+ 实践部署(上)

目录 一、Seata 分布式事务解决方案 1.1、XA 模式 1.1.1、XA模式理论 第一阶段&#xff1a; 第二阶段&#xff1a; 1.1.2、Seata 框架中的 XA 模式 第一阶段&#xff1a; 第二阶段&#xff1a; 1.1.3、XA 模式的优缺点 1.2.4、实现Seata 的 XA 模式 a&#xff09;修改…

FFmpeg:打印音/视频信息(Meta信息)

多媒体文件基本概念 多媒体文件其实是个容器在容器里面有很多流(Stream/Track)每种流是由不同的编码器编码的从流中读出的数据称为包在一个包中包含着一个或多个帧 几个重要的结构体 AVFormatContextAVStreamAVPacket FFmpeg操作流数据的基本步骤 打印音/视频信息(Meta信息…

idea Springboot 教师标识管理系统开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot 教师标识管理系统是一套完善的信息系统&#xff0c;结合springboot框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统 具有完整的源代码和数据库&…

无状态自动配置 DHCPv6无状态配置 DHCPv6有状态配置

1、无状态自动配置 配置命令 AR1 ipv6 #开启路由器ipv6报文转发功能 interface GigabitEthernet0/0/0 ipv6 enable #开启路由器接口IPv6报文转发功能 ipv6 address FC01::1/64 …

【C++】AVL树 红黑树

AVL树 AVL树也是二叉搜索树的一种。因为对于普通的二叉搜索树&#xff0c;当插入的数据在有序或接近有序的情况下&#xff0c;二叉搜索树很可能退化成单支树&#xff0c;导致查找效率低下。而AVL树就很好的解决了这个问题。 首先&#xff0c;AVL树是一棵二叉搜索树。同时对于A…

二十二,加上各种贴图

使用pbr的各种贴图&#xff0c;albedo,金属度&#xff0c;ao,法线&#xff0c;粗糙度&#xff0c;可以更好的控制各个片元 1&#xff0c;先加上纹理坐标 texCoords->push_back(osg::Vec2(xSegment, ySegment)); geom->setVertexAttribArray(3, texCoords, osg::Array::BI…

Linux基本指令(上)——“Linux”

各位CSDN的uu们好呀&#xff0c;今天&#xff0c;小雅兰的内容是Linux啦&#xff01;&#xff01;&#xff01;主要是Linux的一些基本指令和Linux相关的基本概念&#xff08;系统层面&#xff09;&#xff0c;下面&#xff0c;让我们进入Linux的世界吧&#xff01;&#xff01;…