代码随想录算法训练营 DAY 14 | 二叉树的递归遍历和迭代遍历

news2025/2/25 0:10:02

二叉树基础

种类

满二叉树:深度为k,有2^k-1个节点的二叉树

完全二叉树:除了最底层可能没满,且都在靠左侧

  • 优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系。

二叉搜索树:二叉搜索树是一个有序树

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树

平衡二叉搜索树(AVL树):一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

  • java容器中的二叉树:

TreeSet集合是基于TreeMap的实现,而TreeMap是基于二叉树(红黑树)结构,也就是说TreeSet集合的底层使用的二叉树(红黑树)结构。

存储方式

  • 链式存储

在这里插入图片描述

  • 顺序存储
    在这里插入图片描述

数组存储二叉树的遍历:如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。

遍历方式

有两种遍历方式:

  1. 深度优先遍历:先往深走,遇到叶子节点再往回走。
    • 前序遍历(递归法,迭代法)
    • 中序遍历(递归法,迭代法)
    • 后序遍历(递归法,迭代法)
  2. 广度优先遍历:一层一层的去遍历。
    • 层次遍历(迭代法)

前中后,其实指的就是中间节点的遍历顺序。左和右指的是左子树和右子树!

  • 前序遍历:中左右
  • 中序遍历:左中右
  • 后序遍历:左右中

在这里插入图片描述

栈其实就是递归的一种实现结构,前中后序遍历的逻辑其实都可以借助栈使用递归的方式来实现的。

广度优先遍历的实现一般使用队列来实现。

java定义方式

public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

二叉树的递归遍历

视频链接:https://www.bilibili.com/video/BV1Wh411S7xt/?vd_source=80cf8293f27c076730af6c32ceeb2689

讲解连接:https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%80%92%E5%BD%92%E9%81%8D%E5%8E%86.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE

对应题目:

144.二叉树的前序遍历

145.二叉树的后序遍历

94.二叉树的中序遍历

这类问题一般都是传入一个根节点,要求返回一个存放遍历顺序的list。

核心思路

递归问题按照三步走:

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

前序遍历

  1. 确定递归函数的参数和返回值

    递归函数:void traversal(cur, list)

  2. 确定终止条件

    什么时候终止遍历开始返回?遇到null

    if(cur==null) return;

  3. 确定单层递归的逻辑

    中:list.add(cur)

    左:traversal(cur.left, list)

    右:traversal(cur.right, list)

  • java代码
//144.前序遍历
class Solution {
    void transfer (TreeNode cur, List<Integer> res) {
        if(cur == null) return;
        res.add(cur.val);
        transfer(cur.left, res);
        transfer(cur.right, res);
    }

    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        transfer(root, res);
        return res;
    }
}

//145.后序遍历
class Solution {
    void transfer (TreeNode cur, List<Integer> res) {
        if(cur == null) return;
        transfer(cur.left, res);
        transfer(cur.right, res);
        res.add(cur.val);
    }

    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        transfer(root, res);
        return res;
    }
}

//94.中序遍历
class Solution {
    void transfer (TreeNode cur, List<Integer> res) {
            if(cur == null) return;
            transfer(cur.left, res);
            res.add(cur.val);
            transfer(cur.right, res);
        }

    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        transfer(root, res);
        return res;
    }
}

迭代遍历

我们用栈来模拟迭代遍历。

一共分为两步操作:访问节点+处理节点

前序

在这里插入图片描述

前序:中 左 右

注意空节点不入栈!

首先我们把5入栈。然后把5弹出,放进数组里。

然后把6 4入栈。为什么按照右 左的顺序入栈?–因为栈先进后出,要保证出栈的顺序是左 右!

然后把4弹出,(把4当做中)再处理4的左和右:把2 1入栈。然后弹出1,弹出2,弹出6。

  • java代码
 //前序:中左右 入栈顺序:中右左
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Stack<TreeNode> st = new Stack<>();
        if(root == null) return res;
        st.push(root);  //先把根节点放进去
        while(!st.empty()) {  //一次循环就是一次中左右的处理
            TreeNode node = st.peek();  //保存一下栈顶元素(中)
            st.pop();  //先把中 出栈
            res.add(node.val);
            if (node.right != null){
                st.push(node.right);
            }
            if (node.left != null){
                st.push(node.left);
            }
        }
        return res;
    }
}

后序

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

在这里插入图片描述

  • java代码
 //前序中左右--反转左右--变成中右左---整个res反转--左右中(后序)
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Stack<TreeNode> st = new Stack<>();
        if(root == null) return res;
        st.push(root);  //先把根节点放进去
        while(!st.empty()) {  //一次循环就是一次中左右的处理
            TreeNode node = st.peek();  //保存一下栈顶元素(中)
            st.pop();  //先把中 出栈
            res.add(node.val);
            if (node.left != null){
                st.push(node.left);
            }
            if (node.right != null){
                st.push(node.right);
            }
        }
        Collections.reverse(res);
        return res;
    }
}

中序

中序是左中右,先访问中,但是要先处理的是左。这就造成了访问顺序和处理顺序不一样!

那怎么办?

我们需要一个指针cur帮助我们遍历二叉树,处理的时候把元素加入到res数组里。

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

非空就入栈,指针往左走,当前为空了就出栈 同时输出到数组 往右走!

  • 遍历终止条件:while(cur==null || stk.empty())

右孩子不为空,此时右孩子的这个点就当作根节点一样处理,是一棵新树,然后右孩子要入栈,接着像最开始一样继续找左,这个是迭代循环的关键

  • java代码
// 中序遍历顺序: 左-中-右 入栈顺序: 左-右
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;  //cur从根节点开始
        while (cur != null || !stack.isEmpty()){  //循环终止条件
           if (cur != null){  //如果cur为空就入栈,同时往左
               stack.push(cur);
               cur = cur.left;
           }else{  //cur不为空就出栈一个 同时令cur=出栈的 把val加入数组 同时往右遍历(把它当作根 开始新一轮)
               cur = stack.pop();  
               result.add(cur.val);
               cur = cur.right;
           }
        }
        return result;
    }
}

统一迭代法

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

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

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

迭代法中序遍历

在这里插入图片描述

//中
st.push(node);               
st.push(null);

//右
if (node.right!=null) st.push(node.right);  
    
//左
if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)
  • 中序遍历Java代码 右中左
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new LinkedList<>();
        Stack<TreeNode> st = new Stack<>();
        if (root != null) st.push(root);
        while (!st.empty()) {
            TreeNode node = st.peek();
            if (node != null) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
                if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)
                st.push(node);                          // 添加中节点
                st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
                
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.peek();    // 重新取出栈中元素
                st.pop();
                result.add(node.val); // 加入到结果集
            }
        }class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new LinkedList<>();
    Stack<TreeNode> st = new Stack<>();
    if (root != null) st.push(root);
    while (!st.empty()) {
        TreeNode node = st.peek();
        if (node != null) {
            st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
            if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
            st.push(node);                          // 添加中节点
            st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。

            if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)
        } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
            st.pop();           // 将空节点弹出
            node = st.peek();    // 重新取出栈中元素
            st.pop();
            result.add(node.val); // 加入到结果集
        }
    }
    return result;
}
}
        return result;
    }
}
  • 前序遍历 右左中
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new LinkedList<>();
        Stack<TreeNode> st = new Stack<>();
        if (root != null) st.push(root);
        while (!st.empty()) {
            TreeNode node = st.peek();
            if (node != null) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右左中节点添加到栈中
                if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
                if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)
                st.push(node);                          // 添加中节点
                st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
                
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.peek();    // 重新取出栈中元素
                st.pop();
                result.add(node.val); // 加入到结果集
            }
        }
        return result;
    }
}
  • 后序遍历 中右左
class Solution {
   public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new LinkedList<>();
        Stack<TreeNode> st = new Stack<>();
        if (root != null) st.push(root);
        while (!st.empty()) {
            TreeNode node = st.peek();
            if (node != null) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将中右左节点添加到栈中
                st.push(node);                          // 添加中节点
                st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
                if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
                if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)     
                
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.peek();    // 重新取出栈中元素
                st.pop();
                result.add(node.val); // 加入到结果集
            }
        }
        return result;
   }
}

day14总结

  • 翻转数组:Collections.reverse(传一个list)

  • 递归遍历按照三步走:

    • 确定递归函数的参数和返回值
    • 确定终止条件
    • 确定单层递归的逻辑
  • 迭代遍历,先看前序。用栈来模拟迭代遍历。

    一共分为两步操作:访问节点+处理节点

    前序:中 左 右。但是按照右-左顺序入栈

    后序就是交换左右顺序,然后整个res反转。

  • 迭代遍历中序:需要一个指针cur帮助我们遍历二叉树,处理的时候把元素加入到res数组里。

    非空就入栈,指针往左走,当前为空了就出栈 同时输出到数组 往右走!

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

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

相关文章

redis学习-String类型的命令介绍以及特殊情况分析

目录 1. set key value 2. get key 3. append key string 4. strlen key 5. incr key 和 decr key 6. incrby key num 和 decrby key num 7. getrange key start end 8. setrange key start string 9. setex key time value 10. setnx key value 11. mset key1 val…

AI论文速读 | UniST:提示赋能通用模型用于城市时空预测

本文是时空领域的统一模型——UniST&#xff0c;无独有偶&#xff0c;时序有个统一模型新工作——UniTS&#xff0c;感兴趣的读者也可以阅读今天发布的另外一条。 论文标题&#xff1a;UniST: A Prompt-Empowered Universal Model for Urban Spatio-Temporal Prediction 作者&…

Linux进程通信补充——System V通信

System V进程通信 ​ System V是一个单独设计的内核模块&#xff1b; ​ 这套标准的设计不符合Linux下一切皆文件的思想&#xff0c;尽管隶属于文件部分&#xff0c;但是已经是一个独立的模块&#xff0c;并且shmid与文件描述符之间的兼容性做的并不好&#xff0c;网络通信使…

Linux入门级别命令(下载远程连接工具)

$pwd&#xff08;当前所在位置&#xff0c;显示打印当前工作目录&#xff09;$mkdir 创建目录$cd dir 换个位置&#xff08;进入某一个目录&#xff09;$cd 什么都不加回到最开始的目录$ls当前目录位置下的文件信息&#xff08;列出当前所在位置下有哪些东西&#xff09;$mv移动…

SpringBoot与SpringCloud的版本对应详细版

在实际开发过程中&#xff0c;我们需要详细到一一对应的版本关系&#xff1a;Spring 官方对应版本地址&#xff1a; (https://start.spring.io/actuator/info)&#xff0c;建议用firefox浏览器打开&#xff0c;你会看见格式化好了json信息&#xff1a; 手动记录一些经本人实际…

导师评价网最全整合版

目录 简介 下载地址 打开index.html即可查询。 简介 下载地址 链接&#xff1a;https://pan.baidu.com/s/1QU6PgoF3Fi8NqtaGHzfAuw?pwdoc0s 提取码&#xff1a;oc0s --来自百度网盘超级会员V5的分享

【Frida】06_分析扫雷游戏的数据,显示地雷位置

&#x1f6eb; 系列文章导航 【Frida】 00_简单介绍和使用 https://blog.csdn.net/kinghzking/article/details/123225580【Frida】 01_食用指南 https://blog.csdn.net/kinghzking/article/details/126849567【Frida】 03_初识frida-node https://blog.csdn.net/kinghzking/ar…

python基于django的高校迎新系统 flask新生报到系统

系统的登录界面和业务逻辑简洁明了&#xff0c;采用一般的界面窗口来登录界面,整个系统更加人性化&#xff0c;用户操作更加简洁方便。本系统在操作和管理上比较容易&#xff0c;还具有很好的交互性等特点&#xff0c;在操作上是非常简单的。因此&#xff0c;本系统可以进行设计…

【代码】YOLOv8标注信息验证

此代码的功能是标注信息验证&#xff0c;将原图和YOLOv8标注文件&#xff08;txt&#xff09;放在同一个文件夹中&#xff0c;作为输入文件夹 程序将标注的信息还原到原图中&#xff0c;并将原图和标注后的图像一同保存&#xff0c;以便查看 两个draw_labels函数&#xff0c;分…

【译文】使用ANSI码丰富命令行输出

每个人都习惯了在终端中打印输出的程序&#xff0c;当新文本出现时&#xff0c;它会滚动&#xff0c;但这并不是您所能做的全部:您的程序可以为文本上色&#xff0c;上下左右移动光标&#xff0c;或者在以后要重新打印时清除屏幕的部分内容。这就是为什么像Git这样的程序可以实…

远程过程调用-buttonrpc源码解析2-元组与可变参模板

在不考虑远程调用的情况下&#xff0c;假设我们调用的是本地某个函数&#xff0c;为了支持参数的数量和类型可变&#xff0c;需要使用可变参模板&#xff0c;常见形式如下&#xff1a; // 具体实现函数&#xff1a;利用C17提供的折叠表达式来解析参数包 template<typename …

JAVA实战手册-开篇总述

该专题以实战为出发点&#xff0c;总结概述了实际工作中常用的java知识点&#xff0c;掌握了这些知识点&#xff0c;日常工作开发以及面试都不在话下。 话不多说&#xff0c;直入正题&#xff0c;以下为JAVA知识点概括总结&#xff08;总计涵盖了10大类78小项&#xff09; 针对…

激光打标机:精准定位,实现个性化标识需求

激光打标机&#xff1a;精准定位&#xff0c;实现个性化标识需求 激光打标机&#xff0c;以其精准定位的特性&#xff0c;成为实现个性化标识需求的得力工具。在现代制造业中&#xff0c;个性化标识已成为产品差异化、品牌塑造和提升附加值的重要手段。激光打标机通过其独特的打…

前后端项目笔记

前端项目创建 准备工作 nodejs安装 vue cli安装 vue create frontend 最后一个y的话 它会保存 方便下次创建项目 我这是手快敲错了 随自己 前端项目组件及作用 Element-UI引入 安装 npm i element-ui -S main.js中引入 清空路口App.vue 清空Home页面 随便写个按钮 原因…

【docker】Docker打包SpringBoot镜像

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;中间件 ⛺️稳中求进&#xff0c;晒太阳 前置说明 最为原始的打包方式spring-boot-maven-plugin插件jib-maven-plugin插件dockerfle-maven-plugin插件 最为原始的方式 也就是使用Docker的打…

Ubuntu Desktop - Desktop

Ubuntu Desktop - Desktop 1. Amazon2. Ubuntu Software3. Desktop4. 系统桌面快捷方式5. 用户桌面快捷方式References 1. Amazon Amazon -> Unlock from Launcher 2. Ubuntu Software Installed -> Games -> Remove 3. Desktop /home/strong/Desktop 4. 系统桌面…

爆肝五千字!ECMAScript核心概念与现代JavaScript特性全解析

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

KEY ENERGY欧洲意大利能源光伏储能展

3月1号第18届意大利里米尼国际可再生能源展&#xff08;KEY ENERGY&#xff09;由知名主办方ITALIAN EXHIBITION GROUP S.P.A组织举办&#xff0c;每年一届&#xff0c;是欧洲第二大能源展&#xff0c;也是覆盖范围最全知名度最高的可再生能源展览会。 该展会将于2024扩大规模…

【Mysql数据库基础03】分组函数(聚合函数)、分组查询

分组函数(聚合函数&#xff09;、分组查询 1 分组函数1.1 简单的使用1.2 是否忽略null值1.3 和关键字搭配使用1.4 count函数的详细介绍1.5 练习 2 分组查询Group by2.1 简单的分组查询2.2 练习 3 格式投票:yum: 1 分组函数 1.1 简单的使用 COUNT(expression)&#xff1a;计算符…

腾讯云服务器租用价格多少钱一个月?2024优惠价格表

2024腾讯云服务器多少钱一个月&#xff1f;5元1个月起&#xff0c;腾讯云轻量服务器4核16G12M带宽32元1个月、96元3个月&#xff0c;8核32G22M配置115元一个月、345元3个月&#xff0c;腾讯云轻量应用服务器61元一年折合5元一个月、4核8G12M配置646元15个月、2核4G5M服务器165元…