Day16 二叉树的递归遍历,迭代遍历,层序遍历

递归遍历

每次写递归,都要考虑三要素:

1、确定递归函数的参数和返回值:哪些参数是递归的过程中要处理的,那么就在递归函数里加入这个参数,并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。

2、确定终止条件。

3、确定单层递归的逻辑:确定每一层递归需要处理的信息,就可以重复调用自己来实现递归。

        下面以前序遍历为例:

1、确定递归函数的参数和返回值:因为要打印前序遍历节点的数值,所以参数里需要传入vector来存放数值,同时不需要返回值,代码如下:

void traversal(TreeNode* cur, vector<int>& vec)

 2、确定终止条件:如果当前遍历的结点是空,那么本层递归就要结束了,代码如下;

if (cur == NULL) return;

 3、确定单层递归的逻辑:前序遍历是中左右的顺序,代码如下:

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

         这样就写完了前序递归的代码,完整的代码如下:

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);    // 中
}

 迭代遍历

前序遍历(迭代法)

         整体的思路如下:先建立一个栈用来存放已经访问过的元素,定义一个vector存储结果。如果根节点为空,则直接return。先将根节点root压入栈中,进入一个while循环,定义一个node=栈顶元素,弹出,再将其value放入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->right) st.push(node->right);           // 右(空节点不入栈)if (node->left) st.push(node->left);             // 左(空节点不入栈)}return 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;}
};

 中序遍历(迭代法)

        既然后序遍历都可以对前序遍历的代码进行修改,那么中序遍历是否也可以呢?答案是不行。注意迭代过程中右两个操作:1、访问:遍历结点。2、处理:将元素放入result数组中。因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,要访问和处理的元素是一致的,都是中间节点所以可以访问。而中序遍历是左中右,先访问的事二叉树顶部的节点,然后一层一层向下访问,直到到达左下方的最底部,才开始处理结点,这导致了访问顺序和处理顺序的不一致。所以我们采用指针的遍历来帮助访问节点,用栈来处理结点。

         代码的整体思路是这样的:首先还是创建一个result和stack,这时定义一个指针cur指向root,这时候开始while循环,条件是cur不为空或者栈不为空,cur不为空说明还有结点要处理,因为在中序遍历中,我们沿着左子树一直走到底部时,当前节点可能还有右子树需要继续处理,栈不为空说明还有节点等待处理,因为处理完一个结点的左子树以后,需要回溯到其父节点,然后处理父节点的右子树。中间用或是因为处理完最后一个结点的右子树后,仍然需要回溯到根节点。

        接下来是while里面的语句。如果结点不为空,就持续进栈往左访问,同时都存入栈中,直到访问到了最左边的最后一个元素位置,举个例子,像1这种情况,如果左右孩子都没有并且作为上一个的左孩子,再经过遍历后cur会处于栈中4的位置,之后4会访问2,如果是2这种情况,左右孩子都没有并且作为上一个结点的右孩子,再访问完以后就会直接回到5。总之,可这左先访问到头,之后访问左的上面,再访问右,之后直接退回到上上的爷爷结点。代码如下:

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和stack,同时判断一下根节点是否为空,之后进入循环,定义一个node指向栈顶,如果node不为空,那就先将其弹出来,因为后面要加三个,会重复,在加入中节点时,要同时加一个null来表示中节点访问过但没有处理的标记。当遇到空节点的时候,也就是要处理中节点了,先将空节点弹出,将这个元素存到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> 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;}
};

层序遍历

        层序遍历一个二叉树,需要借用一个辅助数据结构来实现,队列先进先出,符合一层一层遍历的逻辑,栈先进后出符合递归的逻辑。

        

         代码的思路如下:首先创建一个que用来存放正在访问的元素,将根节点root放入到队列中,之后进入循环,定义一个size记录队列的大小,同时在循环里定义一个vector用来记录每层的元素,这里一定要用固定大小size而不要用que.size(),因为que.size()是不断变化的,之后循环遍历每层元素,取出队头元素存入vector,再将这个队头的左右孩子入队即可。代码如下:

class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {queue<TreeNode*> que;vector<vector<int>> result;if(root != NULL)que.push(root);while(!que.empty()){int size = que.size();vector<int> vec;while(size--){TreeNode* node = que.front();que.pop();vec.push_back(node->val);if(node->left)que.push(node->left);if(node->right)que.push(node->right);}result.push_back(vec);}return result;}
};

        当然,本题也可以使用递归法:开始列写递归三要素:

1、确定递归函数的参数和返回值:这里cur指针是必备的,同时还有结果容器result,树的深度depth也有用,代码如下:

void order(TreeNode* cur, vector<vector<int>>& result, int depth)

2、确定终止条件:如果遍历到最后一个了,那么指针就会为空,代码如下:

if (cur == nullptr) return;

3、确定单层递归的逻辑:如果此时结果二维数组的深度等于树高,说明这是新的一层,那么就向result中传入一个一维整型数组,接下来进入正题,将当前节点的值添加到对应深度的一维数组中,开始递归,左子树,深度+1,右子树,深度+1,代码如下:

if (result.size() == depth) result.push_back(vector<int>());result[depth].push_back(cur->val);order(cur->left, result, depth + 1);order(cur->right, result, depth + 1);

         完整的代码如下所示:

# 递归法
class Solution {
public:void order(TreeNode* cur, vector<vector<int>>& result, int depth){if (cur == nullptr) return;if (result.size() == depth) result.push_back(vector<int>());result[depth].push_back(cur->val);order(cur->left, result, depth + 1);order(cur->right, result, depth + 1);}vector<vector<int>> levelOrder(TreeNode* root) {vector<vector<int>> result;int depth = 0;order(root, result, depth);return result;}
};

         这篇文章覆盖了有关二叉树遍历的所有内容,非常重要!

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

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

相关文章

计算机软考有哪些科目?都考什么内容?

一、软考初级科目 1、程序员 考核内容&#xff1a;计算机相关基础知识&#xff1b;基本数据结构和常用算法&#xff1b;C程序设计语言以及C、JAVA中的一种程序设计语言。 岗位描述&#xff1a;从事软件开发和调试工作的初级技术人员。 2、网络管理员 考核内容&#xff1a;…

接口响应过慢怎样排查?

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 服务变慢服务器全局慢包括cpu 慢&#xff0c;内存慢&#xff0c;io/磁盘慢&#xff0c;io/网络慢。 服务器局部慢指得是发现某一个或者多个服务很慢。 1.全局查询思…

【分享】4个方法打开PDF文件

PDF是很多人工作中经常使用的电子文档格式&#xff0c;但是可能有些刚接触的小伙伴不知道用什么工具来打开PDF文件&#xff0c;今天小编就来分享一下4种常用的工具。 1. 使用浏览器 只要有电脑基本都会安装一到两款浏览器&#xff0c;其实浏览器也可以用来打开PDF文件。 只需…

分享5款为你生活带来便捷的小工具

​ 生活需要一些小巧而贴心的工具&#xff0c;它们能够在细节处为我们带来便捷。这五款工具简洁而实用&#xff0c;看看它们是否适合融入你的生活。 1.图片压缩——TinyPNG ​ TinyPNG是一款图片压缩工具&#xff0c;可以智能地减少WebP、PNG和JPEG图片的文件大小。TinyPNG通…

代码随想录算法训练营第59天| 503.下一个更大元素II 42. 接雨水

java代码编写 503.下一个更大元素II 给定一个循环数组 nums &#xff08; nums[nums.length - 1] 的下一个元素是 nums[0] &#xff09;&#xff0c;返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按数组遍历顺序&#xff0c;这个数字之后的第一个…

怎么为pdf文件添加水印?

怎么为pdf文件添加水印&#xff1f;PDF是一种很好用的文件格式&#xff0c;这种格式能够很有效的保护我们的文件&#xff0c;但有时可能还会被破解&#xff0c;这种时候在PDF上添加水印就是比较好的方法。 综上所述&#xff0c;PDF是保密性很强的文件&#xff0c;但添加水印能够…

数字技术:引领未来的创新驱动力

数字技术&#xff0c;作为当代最具创新性和影响力的技术领域之一&#xff0c;已经在全球范围内引起了广泛的关注和研究。当前&#xff0c;数字技术正以惊人的速度改变着我们的世界&#xff0c;从日常生活到商业领域&#xff0c;无一不受到其影响。数字技术的发展不仅改变了人们…

Nginx快速入门:nginx各类转发、代理配置详解|location、proxy_pass参数详解(五)

0. 引言 咱们上节讲解了nginx的负载均衡配置&#xff0c;但是还有很多其他的转发情况&#xff0c;包括不同路径转发至不同的业务服务&#xff0c;通配符识别路径转发等。 今天一起来学习nginx的转发配置 1. location模块的匹配模式 首先我们要了解nginx进行转发代理的核心在…

【音视频】remb twcc原理

目录 twcc简介 WebRTC REMB 参考文档 twcc简介 TWCC全称是Transport wide Congestion Control&#xff0c;是webrtc的最新的拥塞控制算法。其原理是在接收端保存数据包状态&#xff0c;然后构造RTCP包反馈给发送端&#xff0c;反馈信息包括包到达时间、丢包状态等&#xff…

基于JSP+Servlet+Mysql的调查管理系统

基于JSPServletMysql的调查管理系统 一、系统介绍二、功能展示1.项目内容2.项目骨架3.数据库3.登录4.注册3.首页5.系统管理 四、其它1.其他系统实现五.获取源码 一、系统介绍 项目名称&#xff1a;基于JSPServlet的调查管理系统 项目架构&#xff1a;B/S架构 开发语言&#…

云原生文件存储 CFS 线性扩展到千亿级文件数,百度沧海·存储论文被 EuroSys 2023 录用

恭喜百度沧海云存储和中科大合作的论文《CFS: Scaling Metadata Service for Distributed File System via Pruned Scope of Critical Sections》&#xff08;以下简称论文&#xff09;被 EuroSys 2023 录用。 EuroSys 全称欧洲计算机系统会议&#xff08;The European Confer…

PIC单片机项目(7)——基于PIC16F877A的智能灯光设计

1.功能设计 使用PIC16F877A单片机&#xff0c;检测环境关照&#xff0c;当光照比阈值低的时候&#xff0c;开灯。光照阈值可以通过按键进行设置&#xff0c;同时阈值可以保存在EEPROM中&#xff0c;断电不丢失。使用LCD1602进行显示&#xff0c;第一行显示测到的实时光照强度&a…