代码随想录算法训练营DAY14|C++二叉树Part.1|二叉树的递归遍历、二叉树的迭代遍历、二叉树的统一迭代法

本文主要是介绍代码随想录算法训练营DAY14|C++二叉树Part.1|二叉树的递归遍历、二叉树的迭代遍历、二叉树的统一迭代法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 二叉树的递归遍历
    • 思路
    • CPP代码
  • 二叉树的迭代遍历
    • 思路
      • 前序遍历
      • 后序遍历
      • 后序遍历
  • 二叉树的统一迭代法

二叉树的递归遍历

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

文章讲解:二叉树的递归遍历

视频讲解:每次写递归都要靠直觉? 这次带你学透二叉树的递归遍历!

状态:搞清楚本题就一定要先搞清楚递归是什么,曾经可能需要靠栈来想想递归的工作流程,现在我觉得树结构就很好得向我们展示了递归这个方法。

思路

首先搞清楚前中后序的遍历方法,这里以前序遍历进行举例。

20200806191109896

如果用递归的思路来思考前序遍历,其实就是每到一个结点,我们就对其先进性处理,然后分别去遍历左、右孩子。

要写明白递归,就要有三大要素:

  • 确定递归的参数和返回值:这里我们遍历到结点处,通过数组进行存储,所以返回值是void,数组作为参数传进来即可:
void traversal(TreeNode* cur, vector<int> res){...
}
  • 确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:
if (cur == NULL) return;
  • 确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:
vec.push_back(cur->val);    // 中
traversal(cur->left, vec);  // 左
traversal(cur->right, vec); // 右

剩下的中序遍历、后序遍历只要把遍历顺序换个位置即可:

//中序
traversal(cur->left, vec);  // 左
vec.push_back(cur->val);    // 中
traversal(cur->right, vec); // 右//后序
traversal(cur->left, vec);  // 左
traversal(cur->right, vec); // 右
vec.push_back(cur->val);    // 中

CPP代码

//前序遍历
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;}
};

二叉树的迭代遍历

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

文章讲解:二叉树的迭代遍历

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

状态:迭代中的第一步没处理好,一直在想我怎么在迭代中让结点遍历起来呢?本质就是栈里面存储的其实不是结点数值,而是把整个结点都存储进去了。

迭代遍历总结两点:栈内存储的是结点,这样才知道如何结点是如何遍历起来的;把结点存储在栈的核心就是,只要不处理,就不应该弹出,处理的时候才弹出

思路

这篇文章已经把思路解释的非常清楚了:二叉树的递归遍历、二叉树的迭代遍历、二叉树的统一迭代法

前序遍历

  • 搞清楚栈内到底存的是值还是结点:存储结点,如果存储值的话,我们没有手段去遍历二叉树了

记得提前存入根结点,如果没有根结点,直接返回空。

  • 搞清楚循环体内如何去遍历整个二叉树:

同第三点的强调,栈顶元素是哪一个,我们才遍历哪一个

  • 搞清楚在模拟出栈入栈过程中,我们关注的其实是栈顶结点,栈顶结点跑到哪里,我们就遍历哪个子树。
TreeNode* node = st.top();
st.pop();
if(node->right) st.push(node->right);
if(node->left) st.push(node->left);

整体代码如下:

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

后序遍历

前序遍历是中左右-----(调整代码左右循环)---->中右左----(反转result数组)---->左右中

我们的后序遍历就是左右中!所以前序遍历的代码很重要!

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->left) st.push(node->left);             // 左(空节点不入栈)if (node->right) st.push(node->right);           // 右(空节点不入栈)}reverse(result.begin(), result.end());return result;}
};

后序遍历

后序遍历最难的点就在于,我们遍历的结点不是我们要处理的结点,我们遍历过该结点后可能要到后面才能去处理,这样应该怎么办呢?一个办法:借助指针

我们用指针来遍历整个二叉树,然后用栈来存储遍历的顺序

再进一步,中序遍历的规矩就是,左孩子不是空,左孩子压栈,如果空了(左),就要处理栈顶元素(中),然后压入右孩子,如果右孩子是空,那就继续处理栈顶元素和栈顶的右孩子(右)。

class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {stack<int> st;vector<int> result;TreeNode* cur = root;while(cur != NULL|| !st.empty()){	//0 1 则 1	if(cur != NULL){st.push(cur);cur = cur->left;}else{cur = st.top();st.pop();result.push_bacl(cur->val);cur->right;}}return result;}
};

二叉树的统一迭代法

文章讲解:二叉树的统一迭代法

状态:感觉有点厉害,以后有时间在进行研究。

这篇关于代码随想录算法训练营DAY14|C++二叉树Part.1|二叉树的递归遍历、二叉树的迭代遍历、二叉树的统一迭代法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/870661

相关文章

C++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

JAVA项目swing转javafx语法规则以及示例代码

《JAVA项目swing转javafx语法规则以及示例代码》:本文主要介绍JAVA项目swing转javafx语法规则以及示例代码的相关资料,文中详细讲解了主类继承、窗口创建、布局管理、控件替换、... 目录最常用的“一行换一行”速查表(直接全局替换)实际转换示例(JFramejs → JavaFX)迁移建

Go异常处理、泛型和文件操作实例代码

《Go异常处理、泛型和文件操作实例代码》Go语言的异常处理机制与传统的面向对象语言(如Java、C#)所使用的try-catch结构有所不同,它采用了自己独特的设计理念和方法,:本文主要介绍Go异... 目录一:异常处理常见的异常处理向上抛中断程序恢复程序二:泛型泛型函数泛型结构体泛型切片泛型 map三:文

Springboot3统一返回类设计全过程(从问题到实现)

《Springboot3统一返回类设计全过程(从问题到实现)》文章介绍了如何在SpringBoot3中设计一个统一返回类,以实现前后端接口返回格式的一致性,该类包含状态码、描述信息、业务数据和时间戳,... 目录Spring Boot 3 统一返回类设计:从问题到实现一、核心需求:统一返回类要解决什么问题?

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

C++构造函数中explicit详解

《C++构造函数中explicit详解》explicit关键字用于修饰单参数构造函数或可以看作单参数的构造函数,阻止编译器进行隐式类型转换或拷贝初始化,本文就来介绍explicit的使用,感兴趣的可以... 目录1. 什么是explicit2. 隐式转换的问题3.explicit的使用示例基本用法多参数构造

C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解

《C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解》:本文主要介绍C++,C#,Rust,Go,Java,Python,JavaScript性能对比全面... 目录编程语言性能对比、核心优势与最佳使用场景性能对比表格C++C#RustGoJavapythonjav

MyBatis中的两种参数传递类型详解(示例代码)

《MyBatis中的两种参数传递类型详解(示例代码)》文章介绍了MyBatis中传递多个参数的两种方式,使用Map和使用@Param注解或封装POJO,Map方式适用于动态、不固定的参数,但可读性和安... 目录✅ android方式一:使用Map<String, Object>✅ 方式二:使用@Param

SpringBoot实现图形验证码的示例代码

《SpringBoot实现图形验证码的示例代码》验证码的实现方式有很多,可以由前端实现,也可以由后端进行实现,也有很多的插件和工具包可以使用,在这里,我们使用Hutool提供的小工具实现,本文介绍Sp... 目录项目创建前端代码实现约定前后端交互接口需求分析接口定义Hutool工具实现服务器端代码引入依赖获

C++打印 vector的几种方法小结

《C++打印vector的几种方法小结》本文介绍了C++中遍历vector的几种方法,包括使用迭代器、auto关键字、typedef、计数器以及C++11引入的范围基础循环,具有一定的参考价值,感兴... 目录1. 使用迭代器2. 使用 auto (C++11) / typedef / type alias