DAY14|二叉树理论基础、递归遍历、迭代遍历、统一迭代

news2025/2/28 15:43:37

理论基础、递归遍历、迭代遍历、统一迭代

  • 理论基础
  • 递归遍历
  • 迭代遍历
    • 前序
    • 中序
    • 后序
  • 统一迭代

理论基础

今天的内容极其基础也极其重要,今天的不掌握好,之后一个半月都要坐大牢…
以前算法课上学的还行,可能还能记得一些(希望)
第一眼看这个代码,居然能没看懂构造函数,C++11白学了,寄!

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
  //构造函数⬆️
};

在二叉树的定义中,TreeNode(int x) : val(x), left(NULL), right(NULL) {} 是一个构造函数,用于创建一个二叉树节点。它接受一个整数参数 x,并将其赋值给节点的 val 成员变量。同时,它将节点的左子节点和右子节点初始化为 NULL。

这段代码使用了初始化列表(initializer list)的方式来初始化成员变量。val 是节点的值,left 和 right 分别是指向左子节点和右子节点的指针。通过将 left 和 right 初始化为 NULL,表示当前节点没有左子节点和右子节点。

这个构造函数可以用于创建二叉树的节点对象,并为节点对象设置初始值和子节点的指针。在构建二叉树时,可以使用这个构造函数来方便地创建节点对象,并设置节点的值和子节点的指针。

递归遍历

突然想起来今年一件很有意思的事,据说力扣今年过年开始一个多月的每日一题全都是树,每天都是树树树,搞了很多人心态(绷)
另外
确实也如卡哥所说
一看就会
一写就废
但给的三道题还是最基础,秒了

迭代遍历

前序

我们先看一下前序遍历。

前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。

为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。

动画如下:
在这里插入图片描述

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if (root == NULL) return result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();                       // 中
            st.pop();
            result.push_back(node->val);
            if (node->right) st.push(node->right);           // 右(空节点不入栈)
            if (node->left) st.push(node->left);             // 左(空节点不入栈)
        }
        return result;
    }
};

此时会发现貌似使用迭代法写出前序遍历并不难,确实不难。

此时是不是想改一点前序遍历代码顺序就把中序遍历搞出来了?

其实还真不行!

但接下来,再用迭代法写中序遍历的时候,会发现套路又不一样了,目前的前序遍历的逻辑无法直接应用到中序遍历上。

中序

为了解释清楚,我说明一下 刚刚在迭代的过程中,其实我们有两个操作:

1.处理:将元素放进result数组中
2.访问:遍历节点

分析一下为什么刚刚写的前序遍历的代码,不能和中序遍历通用呢,因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。

那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。

那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

动画如下:
在这里插入图片描述

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) { // 指针来访问节点,访问到最底层
                st.push(cur); // 将访问的节点放进栈
                cur = cur->left;                // 左
            } else {
                cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                st.pop();
                result.push_back(cur->val);     // 中
                cur = cur->right;               // 右
            }
        }
        return result;
    }
};

后序

再来看后序遍历,先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了,如下图:
在这里插入图片描述
所以后序遍历只需要前序遍历的代码稍作修改就可以了,代码如下:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if (root == NULL) return result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            result.push_back(node->val);
            if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
            if (node->right) st.push(node->right); // 空节点不入栈
        }
        reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
        return result;
    }
};

此时我们用迭代法写出了二叉树的前后中序遍历,大家可以看出前序和中序是完全两种代码风格,并不像递归写法那样代码稍做调整,就可以实现前后中序。

这是因为前序遍历中访问节点(遍历节点)和处理节点(将元素放进result数组中)可以同步处理,但是中序就无法做到同步!

上面这句话,可能一些同学不太理解,建议自己亲手用迭代法,先写出来前序,再试试能不能写出中序,就能理解了。

那么问题又来了,难道 二叉树前后中序遍历的迭代法实现,就不能风格统一么(即前序遍历 改变代码顺序就可以实现中序 和 后序)?

当然可以,这种写法,还不是很好理解,我们将在下一篇文章里重点讲解,敬请期待!

统一迭代

我们发现迭代法实现的先中后序,其实风格也不是那么统一,除了先序和后序,有关联,中序完全就是另一个风格了,一会用栈遍历,一会又用指针来遍历。

实践过的同学,也会发现使用迭代法实现先中后序遍历,很难写出统一的代码,不像是递归法,实现了其中的一种遍历方式,其他两种只要稍稍改一下节点顺序就可以了。

其实针对三种遍历方式,使用迭代法是可以写出统一风格的代码!

重头戏来了,接下来介绍一下统一写法。

我们以中序遍历为例,使用栈的话,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况

那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记

如何标记呢,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法

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;
    }
};

在这里插入图片描述
动画中,result数组就是最终结果集。

可以看出我们将访问的节点直接加入到栈中,但如果是处理的节点则后面放入一个空节点, 这样只有空节点弹出的时候,才将下一个节点放进结果集。

此时我们再来看前序、中序遍历代码。(各仅仅改了两行代码)
前序

class Solution {
public:
    vector<int> preorderTraversal(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);  // 右
                if (node->left) st.push(node->left);    // 左
                st.push(node);                          // 中
                st.push(NULL);
            } else {
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

后序

class Solution {
public:
    vector<int> postorderTraversal(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();
                st.push(node);                          // 中
                st.push(NULL);

                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左

            } else {
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

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

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

相关文章

机器人视觉软件实现目标检测通常借助深度学习技术和计算机视觉算法

机器人视觉软件实现目标检测通常借助深度学习技术和计算机视觉算法。以下是一般而言的目标检测实现步骤&#xff1a; 1、数据收集与标注&#xff1a;首先需要收集包含目标物体的大量图像数据&#xff0c;并对这些图像进行标注&#xff0c;标注出目标物体的位置和类别信息。这些…

深度强化学习(DRL)算法 附录 6 —— NLP 回顾之预训练模型篇

Self-Attention 模型结构 上图架构以 batch_size 为 1&#xff0c;两个时间步的 X 为例子&#xff0c;计算过程如下&#xff1a; 位置编码 根据 self-attention 的模型结构&#xff0c;改变 X 的输入顺序&#xff0c;不影响 attention 的结果&#xff0c;所以还需要引入额外的…

读《AI营销画布》步骤三 扩场景(八)

前言 扩场景写起来有点吃力和干巴&#xff0c;为了文章的连续性&#xff0c;还是写点。 扩场景实际上是需要考虑在第一步和第二步之后是否增加业务流程、节点和场景。在确定增加场景之前&#xff0c;我们需要确保的是第一二步的成功&#xff0c;而且模式是可以复制的。所以&…

【数据分析面试】22.补充缺失数据(Python:数据插值interpolate()用法)

题目 数据集来自一个气候研究组织&#xff0c;列表里带有不同城市每日温度读数的时间序列数据。该数据框有三列&#xff1a;date、city 和 temperature。 由于数据记录问题&#xff0c;某些日期的温度读数可能丢失。该组织需要每天的温度读数&#xff0c;因此他们要求你用线性…

2024中国内燃机展-北京汽车发动机零部件展

2024第二十三届中国国际内燃机与零部件展览会 由中国内燃机工业协会主办、中国机床专用技术设备有限公司、汽车工艺装备成套开发集团协办的2024中国国际内燃机及动力装备博览会&#xff08;简称“动博会”&#xff09;将于2024年10月11日-13日在亦创国际会展中心隆重举办。本届…

linux@内核@内核版本发展@镜像文件查看内核

文章目录 linux内核介绍简介小结 linux发行版和内核各个linux发行版和内核的关系内核更新追踪GAHWE版的内核 内核版本查看&#x1f60a;linux当前系统内核查看未安装时查看网络搜索内核版本号挂载镜像查看虚拟机启动镜像体验版查看内核版本 linux(内核)版本演进&#x1f60a;相…

Android 自定义SwitchPreference

1. 为SwitchPreference 添加背景&#xff1a;custom_preference_background.xml <?xml version"1.0" encoding"utf-8"?> <selector xmlns:android"http://schemas.android.com/apk/res/android"><item><shape android:s…

计算机网络——实现smtp和pop3邮件客户端

实验目的 运用各种编程语言实现基于 smtp 协议的 Email 客户端软件。 实验内容 1. 选择合适的编程语言编程实现基于 smtp 协议的 Email 客户端软件。 2. 安装 Email 服务器或选择已有的 Email 服务器&#xff0c;验证自己的 Email 客户端软件是否能进行正常的 Email 收发功…

BoostCompass( 查找功能实现 )

阅读导航 一、查找功能基本思路二、详细代码三、代码介绍四、运行结果 一、查找功能基本思路 通过实现一个基于倒排索引的搜索引擎&#xff0c;来提供高效、准确的搜索服务。其核心在于快速准确地从大量文档中检索出与用户查询关键词相关的文档&#xff0c;并按照相关性对结果…

小程序视频怎么保存到mp4

小程序上的视频如何下载成mp4&#xff0c;本文就将教大家如何将小程序视频保存到mp4&#xff0c;这里要用到一个工具:下载高手 下载高手的文件我已经打包好了 下载高手链接&#xff1a;https://pan.baidu.com/s/1qJ81sNBzzzU0w6DWf-9Nxw?pwdl09r 提取码&#xff1a;l09r -…

项目7-音乐播放器3(删除模块+播放音乐模块设计)

1.播放音乐模块设计 1.1 请求响应设计 请求&#xff1a; { get, /music/get?pathxxx.mp3 } 响应&#xff1a; { 音乐数据本身的字节信息 } 1.2 后端代码 1. Files.readAllBytes(String path) : 读取文件中的所有字节&#xff0c;读入内存 &#xff…

实体识别方法

文章目录 实体识别方法求观测序列的概率 实体识别方法 从文本中识别实体边界及其类型 实体识别的常用方法&#xff1a;基于模板和规则。将文本与规则进行匹配来识别出命名实体“说”、“老师”;“大学”、“医院”。优点&#xff1a;准确&#xff0c;有些实体识别只能依靠规则…

APEX开发过程中需要注意的小细节5.5

oracle保留小数点后两位的函数 在日常开发中经常用到百分比做数据对比&#xff0c;但是有可能得到的数据是一个多位小数&#xff0c;结果如下所示&#xff1a; 如果想截取部分小数如保留小数点后两位可以怎么做呢&#xff1f; 在Oracle中&#xff0c;可以使用ROUND函数来四舍…

Nature Climate Change | CO2施肥效应导致1981-2020年的全球陆地植被光合作用增加

在全球范围内&#xff0c;光合作用是大气和生物圈之间最大的单一CO2通量。因此&#xff0c;光合作用的长期变化&#xff0c;例如对大气CO2上升的响应&#xff0c;可能为气候变化提供重要的反馈。理论预测&#xff0c;CO2的增加增加了全球光合作用&#xff0c;这一过程被称为CO2…

根据 Figma 设计稿自动生成 Python GUI | 开源日报 No.221

ParthJadhav/Tkinter-Designer Stars: 8.0k License: BSD-3-Clause Tkinter-Designer 是一个用于快速创建 Python GUI 的工具&#xff0c;通过使用 Figma 设计软件&#xff0c;可以轻松地生成美观的 Tkinter GUI。 主要功能和优势包括&#xff1a; 拖放界面设计比手写代码更快…

SCI一区 | Matlab实现POA-TCN-BiGRU-Attention鹈鹕算法优化时间卷积双向门控循环单元注意力机制多变量时间序列预测

SCI一区 | Matlab实现POA-TCN-BiGRU-Attention鹈鹕算法优化时间卷积双向门控循环单元注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现POA-TCN-BiGRU-Attention鹈鹕算法优化时间卷积双向门控循环单元注意力机制多变量时间序列预测预测效果基本介绍模型描述程序设计参考…

Spring Boot 处理过滤器(filter )中抛出的异常

前言&#xff1a; 在改造老项目登录功能的时候&#xff0c;使用了过滤器对 token 进行有效性验证&#xff0c;验证通过继续进行业务请求&#xff0c;验证不通过则抛出校验异常。 过程&#xff1a; 技术方案拟定后&#xff0c;就着手开始改造&#xff0c;一切都很顺畅&#x…

OpenStack云平台实战

1、环境准备 主机CPU数量内存硬盘IPV4发行版controller48GB100GBens33: 192.168.110.27/24 esn34: 192.168.237.131/24CentOS 7.9compute48GB200GB、100GBens33: 192.168.110.26/24 esn34: 192.168.237.132/24CentOS 7.9 1.1 虚拟机安装部署 1.1.1 创建虚拟机 这里16或者17都…

机器学习——模型评价

概述 在机器学习中&#xff0c;模型评价是评估和比较不同模型性能的关键步骤之一。它是通过对模型的预测结果与真实标签进行比较&#xff0c;从而量化模型的预测能力、泛化能力和稳定性。模型评价旨在选择最佳的模型&#xff0c;理解模型的行为&#xff0c;并为模型的改进提供…

c++11 标准模板(STL)本地化库 - 平面类别 - 在字符编码间转换,包括 UTF-8、UTF-16、UTF-32 (七)

本地化库 本地环境设施包含字符分类和字符串校对、数值、货币及日期/时间格式化和分析&#xff0c;以及消息取得的国际化支持。本地环境设置控制流 I/O 、正则表达式库和 C 标准库的其他组件的行为。 平面类别 在字符编码间转换&#xff0c;包括 UTF-8、UTF-16、UTF-32 std::…