代码随想录笔记|C++数据结构与算法学习笔记-二叉树(一)|二叉树的递归遍历、二叉树的迭代遍历、二叉树的统一迭代法

news2024/11/17 15:45:47

全文基于代码随想录及相关讲解视频。
文字链接:《代码随想录》

文章目录

  • 二叉树的递归遍历
    • 二叉树的前序遍历
      • C++代码如下
    • 二叉树的中序遍历
    • 二叉树的后序遍历
  • 二叉树的迭代遍历
    • 前序遍历
      • 前序遍历C++代码
    • 右序遍历
      • 右序遍历C++代码
    • 中序遍历
      • 为什么中序遍历不同
      • 中序遍历迭代法的过程
      • 中序遍历C++代码
  • 二叉树的统一迭代法
    • 中序遍历
    • 前序遍历
    • 后序遍历

二叉树的递归三部曲:(非常重要,每一题几乎都要拿来分析)

  • 确定递归函数的参数和返回值
  • 确定终止条件
  • 确定单层递归的逻辑

二叉树的递归遍历

  • 144.二叉树的前序遍历
  • 145.二叉树的后序遍历
  • 94.二叉树的中序遍历

二叉树的前序遍历

前序是中左右的遍历顺序

  • 确定递归函数的参数和返回值:

​ 参数并不是要一次性确定的,在写递归函数的过程中我们需要什么样的参数,再传入什么样的参数即可,不过大多数二叉树的题目所需要的参数并不多,基本就是传入一个根结点,在传入一个数组来放我们遍历的结果返回值一般都是void,因为我们需要的结果已经放入到传入参数的数组里了

​ 就本题来说void traversal(TreeNode* cur, vector<int>& vec)

  • 确定终止条件:

​ 确定终止条件对于递归来说也非常重要,对于一个前序遍历,当他猛猛搜索一直搜索到null那肯定就往上返。

if (cur == NULL) return;

  • 确定单层递归的逻辑

​ 单层递归逻辑其实就是在递归函数中要写的东西,这里我们实现的中左右,所以我们要处理的结点首先是中间节点。所以数组要放我们遍历过的元素vec.push(cur->val);什么是左呢,就是向左遍历traversal(cur->left, val);右就是遍历它的右孩子traversal(cur->right, vec);这样我们就实现了中序遍历。

综上伪代码如下:

void traversal(TreeNode* cur, vector<int>& vec)
  if (cur == NULL) return;
	//中
	vec.push(cur->val)
	//左
  traversal(cur->left, val)
	//右
  traversal(cur->right, vec)

写前序、中序、后序代码的原则跟我们讲的前序的顺序一致:

中序:

	//左
  traversal(cur->left, val)
  //中
	vec.push(cur->val)
	//右
  traversal(cur->right, vec)

后序同理。

C++代码如下

class Solution {
public:
    void traversal(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        vec.push_back(cur->val);    // 中
        traversal(cur->left, vec);  // 左
        traversal(cur->right, vec); // 右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};

二叉树的中序遍历

void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    traversal(cur->left, vec);  // 左
    vec.push_back(cur->val);    // 中
    traversal(cur->right, vec); // 右
}

二叉树的后序遍历

void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    traversal(cur->left, vec);  // 左
    traversal(cur->right, vec); // 右
    vec.push_back(cur->val);    // 中
}

二叉树的迭代遍历

看完本篇大家可以使用迭代法,再重新解决如下三道leetcode上的题目:

  • 144.二叉树的前序遍历
  • 94.二叉树的中序遍历
  • 145.二叉树的后序遍历

文章链接:非递归遍历

视频链接:写出二叉树的非递归遍历很难么?(前序和后序)

写出二叉树的非递归遍历很难么?(中序)

前序遍历

​ A
/ \
B C
/ \
D E

对于以上的二叉树,遍历顺序是ABDEC。编程语言实现递归其实就是用栈来实现的,所以我们这里用迭代来模拟递归,也是用栈这种结构。理论上来讲所有递归都能用栈来模拟。

栈底[ ]栈顶 栈入作所示,先把A入栈,然后把A弹出。为什么要弹出呢,因为我们要把A放入数组里了,因为A是我们要处理的元素。A弹出之后,我们要处理A的左右孩子。然后依次把C、B入栈。此时:栈底[C, B]栈顶。本来我们要拿到B,但是先把C入栈,这是因为栈是先进后出的,这样我们就能先拿B,再拿C,实现中左右的遍历顺序。然后我们弹出B放入数组,现在数组是[A, B]。后面对于B的子树的处理同理。具体可以看上文视频。

再一个就是碰到叶子结点就不用处理它的左右孩子了。

详细代码如下:

前序遍历C++代码

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

右序遍历

20200808200338924

我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了

右序遍历C++代码

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

中序遍历

为什么中序遍历不同

之前代码有两个重点,一个是访问结点,一个是处理结点。访问结点就是在二叉树中从根结点开始一个结点一个结点开始访问;处理结点就是结点元素保存到数组中,这个数组就是我们要输出的顺序。

如果是中序遍历,对于上文的二叉树:

​ A

/
B C
/ \
D E

我们还是要先访问根结点,但是我们先处理的却不能是根结点,中序遍历的第一个元素应该是D。这就造成了我们访问的顺序和我们处理的顺序不一样。这就是为什么我们的中序遍历是另外一套写法。

中序遍历迭代法的过程

中序遍历为:DBEAC

首先还是要用栈来记录我们遍历过的顺序,因为我们处理元素的时候其实按遍历顺序逆向输出的

我们先访问A,然后把A入栈,然后访问B把B入栈,然后是D,把D入栈。此时我们到叶子结点了(怎么知道到叶子结点了呢?因为我们再访问D的左孩子是空,所以肯定是叶子结点)。也就是因为D的左孩子为空,我们从栈取元素,就是D,D就是我们要处理的第一个元素。此时把D看成一个独立的二叉树。D是中,左右都是空,所以第一个输出的元素是D,把D加入到数组里。

然后由于D右也是空,我们就把B弹出来加入到数组。

然后B的右孩子不为空,栈记录我们遍历的元素E,然后指针还是往E的左孩子走,又是空!所以我们就把E弹出。

随后看E的右孩子,也是空!,所以我们就再从栈内弹出元素A加入到数组中。

然后就找A的右孩子C加入到数组中。然后C的左孩子为空,所以把C弹出加入数组,再看C的右孩子,也为空,理论上我们应该从栈内弹出元素,但是栈也为空了,所以我们的遍历流程就结束了。

综上,数组中的顺序为DBEAC,与答案一致

伪代码如下:

 vector<int> traversal (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(); st.pop();
       result.push_back(cur->val);
       cur = cur->right;
     }
    return result;   
   }
 }

中序遍历C++代码

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

二叉树的统一迭代法

主要思想就是利用一个栈来完成遍历结点和处理结点的过程。

统一风格代码如下:

中序遍历

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

前序遍历

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/1537974.html

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

相关文章

【C#】使用C#窗体应用开启/停止Apache、MySQL服务

目录 一、前言 二、效果图 三、配置文件 四、代码 五、一键启动/停止所有服务 一、前言 使用C#窗体应用开启Apache、MySQL服务&#xff0c;不仅仅是Apache、MySQL&#xff0c;其他服务也可以使用同样的方法操作&#xff0c;包括开启自己写的脚本服务。 二、效果图 两种状…

【大数据】五、yarn基础

Yarn Yarn 是用来做分布式系统中的资源协调技术 MapReduce 1.x 对于 MapReduce 1.x 的版本上&#xff1a; 由 Client 发起计算请求&#xff0c;Job Tracker 接收请求之后分发给各个TaskTrack进行执行 在这个阶段&#xff0c;资源的管理与请求的计算是集成在 mapreduce 上的…

TCP重传机制详解——01概述

文章目录 TCP重传机制详解——01概述什么是TCP重传&#xff1f;TCP为什么要重传&#xff1f;TCP如何做到重传&#xff1f;TCP重传方式有哪些超时重传(timeout or timer-based retransmission)快速重传(fast retransmission或者fast retransmit)改进的重传机制&#xff0c;早期重…

单机模拟分布式MINIO(阿里云)

拉取的最新MINIO&#xff1a; minio version RELEASE.2024-03-15T01-07-19Z Runtime: go1.21.8 linux/amd64 分布式 MinIO 至少需要4个节点&#xff0c;也就意味着至少4个硬盘&#xff0c;对于囊中羞涩仅用来开发测试的人来说&#xff0c;这笔花销还是比较高昂。有没有更好的…

手机可以看到电脑在干什么吗

手机与电脑之间的连接与互动已成为我们日常生活和工作中的常态。 那么&#xff0c;一个常被提及的问题是&#xff1a;手机可以看到电脑在干什么吗&#xff1f; 答案是肯定的。 随着技术的不断进步&#xff0c;我们现在已经可以通过多种方式实现手机对电脑操作的实时监控。 首…

hadoop namenode 查看日志里面报错8485无法连接

一、通过日志排查问题&#xff1a; 1、首先我通过jpsall命令查看我的进程&#xff0c;发现namenode都没有开启 2、找到问题后首先进入我的日志目录里查看namenode.log [rootnode01 ~]# /opt/yjx/hadoop-3.3.4/logs/ [rootnode01 ~]# ll [rootnode01 ~]# cat hadoop-root-nam…

短视频矩阵系统--技术实际开发打板3年真实开发分享

短视频矩阵系统--技术实际开发打板3年真实开发分享&#xff0c;短视频矩阵系统/矩阵获客系统是一种基于短视频平台的获客游戏。短视频矩阵系统可以通过多账号发布来替代传统的单账号游戏。可以一键发布所有账号&#xff0c;批量制作多个视频AI智能剪辑。过去很多人只能完成的工…

JupyterNotebook 如何切换使用的虚拟环境kernel

在Jupyter Notebook中&#xff0c;如果需要修改使用的虚拟环境Kernel&#xff1a; 首先&#xff0c;需要确保虚拟环境已经安装conda上【conda基本操作】 打开Jupyter Notebook。 在Jupyter Notebook的顶部菜单中&#xff0c;选择 “New” 在弹出的窗口中&#xff0c;列出了…

练习 12 Web [极客大挑战 2019]BabySQL

本题复习&#xff1a;1.常规的万能语句SQL查询&#xff0c;union联合查询&#xff0c;Extractvalue()报错注入 extractvalue(1,concat(‘0x7e’,select(database())))%23 我一开始挨着试&#xff0c;感觉都无效 直到报错注入&#xff0c;查到了库名‘geek’ 尝试查表名&…

长三角科技盛会“2024南京国际人工智能,机器人,自动驾驶展览会”

2024南京国际人工智能,机器人,自动驾驶展览会 2024 Nanjing International Ai, Robotics, Autonomous Driving Expo 时间:2024年11月22-24日 地点:南京国际博览中心 南京&#xff0c;这座历史悠久的文化名城&#xff0c;如今正站在新一轮科技产业变革的前沿&#xff0c;以人工…

伪装目标检测之注意力CBAM:《Convolutional Block Attention Module》

论文地址&#xff1a;link 代码&#xff1a;link 摘要 我们提出了卷积块注意力模块&#xff08;CBAM&#xff09;&#xff0c;这是一种简单而有效的用于前馈卷积神经网络的注意力模块。给定一个中间特征图&#xff0c;我们的模块依次推断沿着两个独立维度的注意力图&#xff…

5.域控服务器都要备份哪些资料?如何备份DNS服务器?如何备份DHCP服务器?如何备份组策略?如何备份服务器状态的备份?

&#xff08;2.1) NTD(域控数据库&#xff09;备份 &#xff08;2.2&#xff09;DNS备份 &#xff08;2.3&#xff09;DHCP备份 &#xff08;2.4&#xff09;组策略备份 &#xff08;2.5&#xff09;CA证书备份 &#xff08;2.6&#xff09;系统状态备份 &#xff08;2.1)…

乳腺癌分类模型

乳腺癌分类模型的定义中&#xff0c;必须有_init_&#xff08;初始化&#xff09;函数和forward&#xff08;正向传播&#xff09;函数 乳腺癌分类模型定义 # 自定义模型 class MyModel(torch.nn.Module):def __init__(self,in_features):super(MyModel,self).__init__() #调用…

qt+ffmpeg 实现音视频播放(三)之视频播放

一、视频播放流程 &#xff08;PS&#xff1a;视频的播放流程跟音频的及其相似&#xff01;&#xff01;&#xff09; 1、打开视频文件 通过 avformat_open_input() 打开媒体文件并分配和初始化 AVFormatContext 结构体。 函数原型如下&#xff1a; int avformat_open_inpu…

Flask python 开发篇:链接mysql

一、历史回顾 根据上一篇&#xff1a;配置文件编写&#xff0c;已经把各种配置根据开发环境做了区分&#xff0c;再config.py中&#xff0c;我们可以分别处理测试、生产的相关配置&#xff0c;这节主要说一下数据库的链接和使用 二、配置数据库连接 Flask定义和链接数据库文…

手机可以格式化存储卡吗?格式化以后出现什么情况

随着智能手机的普及&#xff0c;存储卡&#xff08;如SD卡、MicroSD卡等&#xff09;已成为手机存储扩展的重要工具。然而&#xff0c;在使用过程中&#xff0c;我们有时可能会遇到需要格式化存储卡的情况。那么&#xff0c;手机能否直接格式化存储卡呢&#xff1f;格式化后存储…

【Flutter学习笔记】10.3 组合实例:TurnBox

参考资料&#xff1a;《Flutter实战第二版》 10.3 组合实例&#xff1a;TurnBox 这里尝试实现一个更为复杂的例子&#xff0c;其能够旋转子组件。Flutter中的RotatedBox可以旋转子组件&#xff0c;但是它有两个缺点&#xff1a; 一是只能将其子节点以90度的倍数旋转二是当旋转…

在服务器(Ubuntu20.04)安装用户级别的cuda11.8(以及仿照前面教程安装cuda11.3后安装cudnn和pytorch1.9.0)

1、cuda11.8的下载 首先在cuda官网下载我们需要的cuda版本&#xff0c;这里我下载的是cuda11.8&#xff08;我的最高支持cuda12.0&#xff09; 这里我直接使用wget命令下载不了&#xff0c;于是我直接在浏览器输入后面的链接下载到本地&#xff0c;之后再上传至服务器的&am…

数据分析概述、Conda环境搭建及JupyterLab的搭建

1. 数据分析职责概述 当今世界对信息技术的依赖程度在不断加深&#xff0c;每天都会有大量的数据产生&#xff0c;我们经常会感到数据越来越多&#xff0c;但是要从中发现有价值的信息却越来越难。这里所说的信息&#xff0c;可以理解为对数据集处理之后的结果&#xff0c;是从…

SQLiteC/C++接口详细介绍sqlite3_stmt类(十)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍sqlite3_stmt类&#xff08;九&#xff09; 下一篇&#xff1a; SQLiteC/C接口详细介绍sqlite3_stmt类&#xff08;十一&#xff09; 38、sqlite3_column_value sqlite3_column_valu…