代码随想录算法训练营day14||二叉树part01、理论基础、递归遍历、迭代遍历、统一迭代

递归遍历 (必须掌握)

本篇将介绍前后中序的递归写法,一些同学可能会感觉很简单,其实不然,我们要通过简单题目把方法论确定下来,有了方法论,后面才能应付复杂的递归。

这里帮助大家确定下来递归算法的三个要素。每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!

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

  2. 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。

  3. 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。

好了,我们确认了递归的三要素,接下来就来练练手:

以下以前序遍历为例:

  1. 确定递归函数的参数和返回值:因为要打印出前序遍历节点的数值,所以参数里需要传入List来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:
     public void preorder(TreeNode root, List<Integer> result) 
  2. 确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:
  if (root == null){return; 
}

       3.确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中间节点的数值,代码如下:

        result.add(root.val); //中preorder(root.left, result); //左preorder(root.right, result); //右

单层递归的逻辑就是按照中左右的顺序来处理的,这样二叉树的前序遍历,基本就写完了,再看一下完整代码:

class Solution {//前序遍历·递归·LC144_二叉树的前序遍历public List<Integer> preorderTraversal(TreeNode root) {
//定义了一个名为preorderTraversal 的公共方法,该方法接受一个TreeNode类型的参数root,表示二叉树的根节点。
//该方法的返回类型是List<Integer>,表示二叉树的前序遍历结果。List<Integer> result = new ArrayList<Integer>();//创建一个名为result的ArrayList对象,用于存储遍历结果preorder(root,result);//调用preorder方法进行前序遍历,传入根节点root和结果列表result。return result;//返回遍历结果。}public void preorder(TreeNode root,List<Integer> result){
//定义了一个名为preorder的方法,用于执行前序遍历。
//该方法接受一个TreeNode类型的参数root,表示当前遍历的节点,以及一个List<Integer>类型的参数result,用于存储遍历结果。if(root == null){ //如果当前节点root为空,则直接返回,结束当前递归。return;}result.add(root.val);//中;将当前节点的值添加到结果列表中,表示当前节点的访问顺序是在中间。preorder(root.left,result); //递归地对左子树和右子树进行前序遍历。preorder(root.right,result); }
}

同day04中总结的递归法一样:

递归法的宗旨就是紧紧抓住原来的函数究竟返回的是什么?作用是什么? 即可

其余的细枝末节不要细究,编译器会帮我们自动完成。

递归函数preorder()需要传入TreeNode类型的节点和一个List<Integer>类型的参数result用来存储遍历结果,调用递归 直接把相关的参数填进去即可。

 preorder(root.left,result); //递归地对左子树和右子树进行前序遍历。
 preorder(root.right,result); 

class Solution {//中序遍历·递归·LC145_二叉树的后序遍历public List<Integer> postorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<Integer>();posorder(root,result);return result;}public void posorder(TreeNode root,  List<Integer> result){if(root == null){return;}posorder(root.left,result); //左posorder(root.right,result); //右result.add(root.val);  //中}
}
class Solution {public List<Integer> inorderTraversal(TreeNode root) {//中序遍历·递归·LC94_二叉树的中序遍历List<Integer> result = new ArrayList<Integer>();inorder(root,result);return result;}public void inorder(TreeNode root,  List<Integer> result){if(root == null){return;}inorder(root.left,result); //左result.add(root.val);  //中inorder(root.right,result); //右}
}

 迭代遍历 (基础不好的录友,迭代法可以放过)

思路

为什么可以用迭代法(非递归的方式)来实现二叉树的前后中序遍历呢?

我们在栈与队列:匹配问题都是栈的强项 (opens new window)中提到了,递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。

此时大家应该知道我们用栈也可以是实现二叉树的前后中序遍历了。

前序遍历(迭代法)

我们先看一下前序遍历。

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

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

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

此时是不是想改一点前序遍历代码顺序就把中序遍历搞出来了?其实还真不行!

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

class Solution {//前序遍历顺序:中-左-右,入栈顺序:中-右-左public List<Integer> preorderTraversal(TreeNode root) {
//定义了一个名为preorderTraversal 的公共方法,该方法接受一个TreeNode 类型的参数root,表示二叉树的根节点。
//该方法的返回类型是List<Integer>,表示二叉树的前序遍历结果。List<Integer> result = new ArrayList<Integer>();//创建一个名为 result 的 ArrayList 对象,用于存储遍历结果。//然后检查根节点是否为空,如果为空,则直接返回空结果列表。if(root == null){return result;}Stack<TreeNode> stack = new Stack<>();//创建一个名为stack的栈,用于存储待访问的节点。并将根节点root入栈。stack.push(root);while(!stack.isEmpty()){//使用循环来遍历栈中的节点,直到栈为空为止。TreeNode node = stack.pop();//从栈中弹出一个节点,表示当前要访问的节点。result.add(node.val);//将当前节点的值添加到结果列表中,表示当前节点的访问顺序是在中间。//将当前节点的右子节点和左子节点依次入栈。//由于栈的特性,右子节点先入栈,然后左子节点入栈,这样在下一次循环时先访问左子节点,符合前序遍历的顺序。if(node.right != null){stack.push(node.right);}if(node.left != null){stack.push(node.left);//然后继续执行while{}中的内容,使用循环来遍历栈中的节点,直到栈为空为止} }return result;//最后返回结果列表result,其中存储了二叉树的前序遍历结果。}
}

有了前序遍历,先讨论后序遍历(迭代法),比较容易:

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

所以后序遍历只需要前序遍历的代码稍作修改就可以了,代码如下: 

// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {public List<Integer> postorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null){return result;}Stack<TreeNode> stack = new Stack<>();stack.push(root);while (!stack.isEmpty()){TreeNode node = stack.pop();result.add(node.val);if (node.left != null){stack.push(node.left);}if (node.right != null){stack.push(node.right);}}Collections.reverse(result);return result;}
}

中序遍历(迭代法):(这个过程需要多理解以下,第一遍有点绕没看懂)

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

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

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

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

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

// 中序遍历顺序: 左-中-右 入栈顺序: 左-右
class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null){return result;}//创建一个名为stack的栈,用于存储待访问的节点。//同时创建一个名为cur的节点,初始值为根节点root,用于迭代遍历二叉树。Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;//使用循环来遍历二叉树的所有节点,直到当前节点为空 且 栈为空为止。while (cur != null || !stack.isEmpty()){//如果当前节点cur不为空,则将当前节点入栈,并将当前节点移动到其左子节点。if (cur != null){stack.push(cur);cur = cur.left;}else{//如果当前节点为空,则从栈中弹出一个节点,表示当前要访问的节点,//将该节点的值添加到结果列表中,然后将当前节点移动到其右子节点。cur = stack.pop();result.add(cur.val);cur = cur.right;}}return result;//最后返回结果列表result,其中存储了二叉树的中序遍历结果。}
}

 

统一迭代(基础不好的录友,迭代法可以放过)

这是统一迭代法的写法, 如果学有余力,可以掌握一下

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

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

相关文章

licheepi nano 从零开始使用sd卡启动

本文目的&#xff1a;licheepi nano从零开始&#xff0c;使用sd卡启动&#xff1b; 某些原因导致需要重新捣鼓uboot&#xff0c;但过程中频繁出错&#xff0c;后悔最初没有记录详细的操作方法&#xff0c;此帖主要为自己出口气&#xff0c;重新记录&#xff1b; 持续完善&#…

P1176 路径计数2

网址如下&#xff1a; P1176 路径计数2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 动归典中典 代码如下&#xff1a; #include<iostream> using namespace std; bool map[1001][1001]; int dp[1001][1001];int main(void) {//输入int N, M;cin >> N >&g…

使用CubeMX快速开始STM32微控制器开发

CubeMX是一款由STMicroelectronics提供的集成开发环境&#xff0c;可以帮助开发者快速启动STM32微控制器的开发。屏蔽了底层配置的繁琐&#xff0c;简化了开发流程&#xff0c;减少了开发时间。本文将向您介绍使用CubeMX进行STM32开发的基本步骤&#xff0c;并附上部分示例代码…

【CV论文精读】【MVDet】Multiview Detection with Feature Perspective Transformation

0.论文摘要 合并多个摄像机视图进行检测减轻了拥挤场景中遮挡的影响。在多视图检测系统中&#xff0c;我们需要回答两个重要问题。首先&#xff0c;我们应该如何从多个视图中聚合线索&#xff1f;第二&#xff0c;我们应该如何从空间上相邻的位置聚集信息&#xff1f;为了解决…

C#调用WechatOCR.exe实现本地OCR文字识别

最近遇到一个需求&#xff1a;有大量的扫描件需要还原为可编辑的文本&#xff0c;很显然需要用到图片OCR识别为文字技术。本来以为这个技术很普遍的&#xff0c;结果用了几个开源库&#xff0c;效果不理想。后来&#xff0c;用了取巧的方法&#xff0c;直接使用了WX的OCR识别模…

彩虹系统7.0免授权+精美WAP端模板源码

最低配置环境 PHP7.2 1、上传源码到网站根目录&#xff0c;导入数据库文件 2、修改数据库配置文件&#xff1a;/config.php 3、后台&#xff1a;/admin 账号&#xff1a; 4、前台用户&#xff1a;123456 密码&#xff1a;1234561

回顾2023展望2024,追逐光成为光

&#x1f469; 个人主页&#xff1a;不爱吃糖的程序媛 &#x1f64b;‍♂️ 作者简介&#xff1a;前端领域新星创作者、CSDN内容合伙人&#xff0c;专注于前端各领域技术&#xff0c;成长的路上共同学习共同进步&#xff0c;一起加油呀&#xff01; ✨系列专栏&#xff1a;前端…

【前端素材】bootstrap4实现葡萄酒品牌电商网站Bidouno(附带源码)

一、需求分析 葡萄酒品牌电商网站是一个专门销售葡萄酒的在线平台。它提供各种类型和品牌的葡萄酒&#xff0c;包括红葡萄酒、白葡萄酒、起泡酒等。葡萄酒品牌电商网站的功能可以从以下几个方面来分析&#xff1a; 1. 提供多样化的选择&#xff1a;葡萄酒品牌电商网站通常会提…

手动汉化unity编辑器,解决下载中文语言报错问题

手动汉化unity编辑器&#xff0c;解决下载中文语言报错问题 START 最近在下载支持微信小程序版本的编辑器时&#xff0c;中文语言包&#xff0c;一直无法下载。记录一下 手动汉化unity编辑器的方法 &#xff0c;帮助和我遇到同样问题的人。 解决方案 1. 下载汉化包 https:…

【原理图PCB专题】Cadence17.4版本新增加的Cutout和Design_Outline层有什么用?

在Cadence 17.4版本中我们发现在Board Geometry下面多出了Cutout和Design_Outline两层,其实这两层在高版本的软件中都做为板框使用。 如下所示在输出光绘时,如果没有将Cutout和Desing_Outline两层加入,还是使用16版本的Outline来定义板框的话,在出光绘时会提示:WA…

automative

car sevice car-lib

【python量化交易】qteasy使用教程01 - 安装方法及初始化配置

qteasy教程1 - 安装方法及初始化配置 qteasy教程1 - 安装方法及初始配置qteasy安装前的准备工作1&#xff0c; 创建安装环境2&#xff0c;安装MySQL数据库 (可选)安装pymysql 3&#xff0c;创建tushare账号并获取API token (可选)4&#xff0c;安装TA-lib (可选)WindowsMac OSL…

简单说网络:TCP+UDP

TCP和UPD: (1)都工作在传输层 (2)目的都是在程序之中传输数据 (3)数据可以是文本、视频或者图片(对TCP和UDP来说都是一堆二进制数没有太大区别) 一、区别:一个基于连接一个基于非连接 将人与人之间的通信比喻为进程和进程之前的通信:基本上有两种方式(1)写信;(2)打电话;这…

FPGA_ip_Rom

一 理论 Rom存储类ip核&#xff0c;Rom是只读存储器的简称&#xff0c;是一种只能读出事先存储数据的固态半导体存储器。 特性&#xff1a; 一旦储存资料&#xff0c;就无法再将之改变或者删除&#xff0c;且资料不会因为电源关闭而消失。 单端口Rom: 双端口rom: 二 Rom ip核…

问题:在下列选项中,下列哪种情况不属于生理排泄过程的是() #媒体#学习方法#经验分享

问题&#xff1a;在下列选项中&#xff0c;下列哪种情况不属于生理排泄过程的是&#xff08;&#xff09; A.CO2由呼吸系统排出 B.食物残渣由消化道排出 C.皮肤排出汗液 D.肾脏排出尿液 E.由消化道排出的胆色素 参考答案如图所示

BGP 双归不同运营商并且客户之间互为主备的部署实验

一、拓朴&#xff1a; 要求&#xff1a; 1、双方 ISP 均不得将客户 AS 做为穿越 AS 2、对于客户业务的出流量&#xff1a;客户 AS100 和 200 访问 ISP 时&#xff0c;AS100优选从 Line-1 线路&#xff0c;AS200 优选从 Line-2 访问&#xff0c;但当 Line-1 和 …

第三章 搜索与图论(二)(最短路)

一、最短路问题 1、对于稠密图&#xff0c;由于朴素版的dijkstra算法与边数无关使用这种算法的复杂度较低。稀疏图用堆优化版的算法&#xff1b;单源最短路中存在负权边用SPFA 算法通常较好&#xff1b;多源用floyd算法&#xff1b; 难点&#xff1a;如何建图&#xff0c;抽象…

spring boot学习第十二篇:mybatis框架中调用存储过程控制事务性

1、MySQL方面&#xff0c;已经准备好了存储过程&#xff0c;参考&#xff1a;MYSQL存储过程&#xff08;含入参、出参&#xff09;-CSDN博客 2、pom.xml文件内容如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"…

【Web】vulhub Shiro-550反序列化漏洞复现学习笔记

目录 Shiro简介 复现流程 工具一把梭 半脚本半手动 原理分析 反序列化入口 常见的key 登录过程 验证过程 利用原理 Shiro简介 Apache Shiro 是一个强大且易于使用的 Java 安全框架&#xff0c;用于身份验证、授权、加密和会话管理等安全功能。Shiro 的设计目标是简单…

猫头虎分享已解决Bug || Python Error: NameError: name ‘variable_name‘ is not defined

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …