代码随想录刷题笔记 DAY12 | 二叉树的理论基础 | 二叉树的三种递归遍历 | 二叉树的非递归遍历 | 二叉树的广度优先搜索

news2024/9/27 5:53:35

Day 12

01. 二叉树的理论基础


1.1 二叉树的种类
  1. 满二叉树:除了叶子节点以外,每个节点都有两个子节点,整个树是被完全填满的
  2. 完全二叉树:除了底层以外,其他部分是满的,底部可以不是满的但是必须是从左到右连续的

image-20240123141309343 在这里插入图片描述

  1. 二叉搜索树:节点是有顺序的,可查找的
  2. 平衡二叉搜索树:左子树和右子树的高度值不能超过 1

比如上面的树,比 6 大的在左边,小的在右边,且每个节点都是这样的,有顺序的,查询时间复杂度为 logn

很显然我们中间节点的选择会影响左子树和右子树的高度,左子树和右子树高度不超过 1 的被称为平衡二叉搜索树。

1.2 二叉树的存储方式

链式存储:顺序存储即采用链表的方式来存储,是我们常用的存储方式,即使用链表的方式来存储:一个树节点分别有它的左节点和右节点,他们的左节点和右节点又连接着其他的树节点。

顺序存储(了解即可):

我们给每个节点编号,节点的左节点就是 2n 右节点就是 2n - 1

1.3 遍历方式

深度优先搜索:即先一路搜索到最底部再递归返回,我们常见的前序、中序、后序遍历都是深度优先搜索。

可以使用递归的方式和非递归的方式,迭代的方式有可能在面试中会考察

广度优先搜索:一层一层的去遍历,使用队列先进先出的特性实现广度优先搜索。

1.4 定义方式
class TreeNode {
	int val;
	TreeNode rightNode;
	TreeNode leftNode;
}

和我们上面说的相同,和链表的定义方式相同,但分为左子树和右子树。

02. 二叉树的递归遍历


​ 这一部分应该是很多朋友一开始学算法十分困惑的一个点,总是想不明白递归的题也看不明白递归的解法,我大一刚开始刷算法的时候也是这样,当时真是一入递归深似海,之后有一篇文章启发了我

东哥带你刷二叉树(纲领篇)

拉不拉东老师的这篇关于二叉树文章。

我来借用一下里面的思维方式

我们之所以无法理解递归是因为我们还是利用之前读代码和写代码的方式:“将自己的脑子当作计算机来执行一遍代码”,这在前面那些简单的顺序结构的题目中当然是可行而且有效的,但当我们解决的题目复杂起来,就比如现在的二叉树遍历的题目,我们的脑子能装下几个栈呢?能跑几层递归呢?

所谓理解和读懂递归就是要将它当作自己编写代码、解决问题的一种工具,而不是尝试去用脑子执行它,弄懂它的执行步骤,我们先来看一段二叉树 前序 遍历的代码

class Solution {
    List<Integer> res = new ArrayList();
    public List<Integer> preorderTraversal(TreeNode root) {
        reverse(root);
        return res;
    }

    public void reverse(TreeNode root) {
        if (root == null) {
            return;
        }
        res.add(root.val);
        reverse(root.left);
        reverse(root.right);
    }
}

我们将重点放在第二段的代码上,如果单单的问你我不看递归,我这一次做了什么?

  1. 首先我先判断当前的节点是否为空节点,这就是我们常说的递归的出口
  2. 然后我们将当前节点的值放到了 reslist 中作为结果
  3. 然后我们去递归遍历左子树
  4. 然后我们去递归遍历右子树

看我们上面的图是不是非常熟悉,这不就是前序遍历的遍历顺序吗?先中间再左边再右边,前序遍历无非就是对每个节点执行如上的相同的操作,那如何对每个节点操作呢?

递归的作用就是 帮助我们为每一个节点做相同的操作

我们只需要关注一个节点做的事情然后写到递归中,让递归帮我们去执行即可。

所有的递归都可以套用二叉树的模型来理解,我们知道,二叉树除了前序遍历,还有后序遍历和中序遍历:

我们每次进入一个节点都可以分为三个位置:

  1. 前序位置
  2. 中序位置
  3. 后序位置

对应着上图中的 1 2 3 三个部分

前序中的操作即是我们进入这个节点后立马执行的操作,这不就是我们上面的前序遍历吗?进入节点后立马输出

中序就是在节点中执行的结果,即上图中的 2 ,这不就是左子树返回东西的地方吗?

那中序遍历的代码该怎么写?

    public void reverse(TreeNode root) {
        if (root == null) {
            return;
        }
        reverse(root.left);
        
        res.add(root.val);
        
        reverse(root.right);
    }
}

到这里是不是对递归主键的清晰起来了?

后序遍历的代码我们也可以很轻松的写出来

    public void reverse(TreeNode root) {
        if (root == null) {
            return;
        }
        reverse(root.left);
        
        res.add(root.val);
        
        reverse(root.right);
    }
}

如果上面的内容都能看懂,那么恭喜你已经解决了力扣上的三道题目:

No.144 二叉树的前序遍历

No.94 二叉树的中序遍历

No.145 二叉树的后序遍历

这里我们来看一个小小的问题:使用递归实现链表的倒序输出

如果将上面的部分看懂,那这道题相信对你来说很简单了

链表就是简化的二叉树,上面的每一个节点就可以通过上面的思路来处理

链表没有中序位置,只有前序和后序,既然要倒序输出链表,那我们要考虑的是我们的输出语句放在哪里

非常简单的二选一题目,肯定是放在后序的位置,呈现在代码中就是这样的:

public void reverse(ListNode head) {
	if (head == null) {
		return;
	}
	reverse(head.next);
	System.out.println(head.val);
}

03. 二叉树的非递归遍历

3.1 非递归实现前序遍历

二叉树的递归遍历上面已经提到过了,代码实现也非常容易,但如果让我们使用非递归的方式来模拟前序遍历就比较困难了。

递归的实现是通过栈来完成的,所以我们自然的去想使用栈来模拟前序遍历,之前刷关于栈的题目的时候总结过,一道题能否用栈去解决关键在于能否满足先进后出的特点。

前序遍历是对 每个节点 进行 中 左 右 顺序的遍历,我们要对每个节点都进行这种操作,就会出现将节点放入又取出来,就会有先进后出的特性。

比如我们来模拟一下对这个二叉树的遍历

step 1

在这里插入图片描述

step2

在这里插入图片描述

step3

step4

和上面提到的一样,我们不用去模拟整个遍历的过程,我们只需要对每个节点进行的操作熟悉即可,再来总结一下上面的步骤:

  1. 将节点放入栈中
  2. 取出节点,存入结果数组
  3. 将节点的左节点放入栈中
  4. 将节点的右节点放入栈中

这样我们就用 非递归 的方式实现了前序遍历

这边给出代码,题目还是

No.144 二叉树的前序遍历

/**
 * Definition for a binary tree node.
 * 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;
 *     }
 * }
 */
class Solution {
    Stack<TreeNode> stack = new Stack<>(); // 栈
    List<Integer> res = new ArrayList<>(); // 结果数组

    public List<Integer> preorderTraversal(TreeNode root) {
        stack.push(root);
        // 在 while 中持续执行上面提到的顺序
        while(!stack.isEmpty()) {
            TreeNode node = stack.pop();
            if (node != null) {
                res.add(node.val); 
                stack.push(node.right);
                stack.push(node.left);
            }
        }
        return res;
    }
}
3.2 非递归实现二叉树的后序遍历

后序遍历的顺序是 左 右 中,而我们上面前序遍历的顺序是 中 左 右,如果我们入栈的时候先放入 左节点,也就是让右节点先弹出,遍历的顺序就变成了 中 右 左,这不就是后序遍历的逆序吗?

对入栈的元素进行上述处理后,再反转数组就可以实现后序遍历的效果

No.145 二叉树的后序遍历

/**
 * Definition for a binary tree node.
 * 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;
 *     }
 * }
 */
class Solution {
    Stack<TreeNode> stack = new Stack<>(); // 栈
    List<Integer> res = new ArrayList<>(); // 结果数组

    public List<Integer> postorderTraversal(TreeNode root) {
        stack.push(root);
        // 在 while 中持续执行上面提到的顺序
        while(!stack.isEmpty()) {
            TreeNode node = stack.pop();
            if (node != null) {
                res.add(node.val); 
                stack.push(node.left);  // 先放入右节点
                stack.push(node.right);
            }
        }
        Collections.reverse(res);
        return res;
    }
}
3.3 非递归实现中序遍历

中序遍历的顺序是 右 中 左,但这不像上面的后序遍历可以通过调整顺序再倒置的方式解出。

对于这种遍历顺序和输出次序不同的方式,给出另一个思路,可以通过指针加栈的方式

因为中序遍历是深度优先遍历,我们输出的第一个元素是遍历到左边最底部再输出的,所以我们指定一个指针一直持续向左遍历,直到 current.left 指向 null 的时候,也就是所有的左节点全都遍历完,这就是我们要输出的第一个元素,此时将栈中的元素弹出放入数组中。

然后我们来单独看 current 此时指向这个节点,这是我们要 输出 的第一个节点,再次来回顾一下中序遍历的过程

  1. 进入节点一直向左遍历
  2. 左边没有元素的时候直接输出当前节点
  3. 向右边遍历,对右边的节点执行相同的操作

当我们将当前的节点放入结果数组的时候,就已经执行完了前两个步骤,接下来就是去遍历右节点,并且对右节点执行相同的操作。

这道题的重点仍然是将中心放在某一个节点上,因为我们用栈记录了遍历的节点,通过弹栈就可以实现对每个节点实现相同的操作,接下来只需要将思路翻译成代码即可。

/**
 * Definition for a binary tree node.
 * 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;
 *     }
 * }
 */
class Solution {
    List<Integer> res = new ArrayList();
    TreeNode current;
    Stack<TreeNode> stack = new Stack<>();

    public List<Integer> inorderTraversal(TreeNode root) {
        if (root == null){
            return res;
        }
        current = root;
        while (current != null || !stack.isEmpty()) {
            if (current != null) {
                stack.push(current);
                current = current.left;
            } else {
                current = stack.pop();
                res.add(current.val);   
                current = current.right;           
            }  
        }
        return res;
    }
}

04. 二叉树的广度优先搜索

层序遍历需要借助队列来实现,我们将每一层的元素一个个放到队列中,再逐一输出即可

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

重要的就是要区分每一层有哪些元素,因为下一层元素的 add 是可以通过上一层元素弹出后再将其左右节点加入来实现的,所以我们需要一个变量 size 来记录每一层的元素个数。

利用 for 循环来输出每层的元素,此时 栈内剩下的元素 便是下一层的所有元素,此时更新我们的 size ,比如上图中的第二层中的元素遍历完后,栈内剩余的元素就是第三层的元素

No.102 二叉树的层序遍历

/**
 * Definition for a binary tree node.
 * 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;
 *     }
 * }
 */
class Solution {
    Queue<TreeNode> queue = new LinkedList<>();
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> levelOrder(TreeNode root) {
        if (root == null) {
            return res;
        }
        int size = 1; // 记录每层的元素个数
        queue.add(root);
        while (!queue.isEmpty()) {
            List<Integer> tempList = new ArrayList<>();
            // 对每一层进行遍历
            for (int i = size; i > 0; i--) {
                TreeNode tempNode = queue.poll();// 弹出队列中的元素
                if (tempNode != null) {
                    tempList.add(tempNode.val);
                    queue.add(tempNode.left);
                    queue.add(tempNode.right);
                }       
            }
            if (!tempList.isEmpty()){
                res.add(new ArrayList(tempList));
            }
            size = queue.size();// 更新 size 的长度
        }
        return res;
    }
}

1

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

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

相关文章

Java 设计者模式以及与Spring关系(六) 装饰和模版方法模式

简介: 本文是个系列一次会出两个设计者模式作用&#xff0c;如果有关联就三个&#xff0c;除此外还会讲解在spring中作用。 23设计者模式以及重点模式 我们都知道设计者模式有3类23种设计模式&#xff0c;标红是特别重要的设计者模式建议都会&#xff0c;而且熟读于心&#…

41.while语句

目录 一.什么是while语句 二.语法 三.执行流程图 四.举例 五.视频教程 一.什么是while语句 只要条件为真&#xff0c;while循环中的语句会一直重复执行。 二.语法 while&#xff08;表达式&#xff09;{//代码块 } 三.执行流程图 从流程图可以看出&#xff0c;while循环…

【JAVA语言-第14话】集合框架(一)——Collection集合,迭代器,增强for,泛型

目录 集合框架 1.1 概述 1.2 集合和数组的区别 1.3 Collection集合 1.3.1 概述 1.3.2 常用方法 1.4 迭代器 1.4.1 概述 1.4.2 常用方法 1.4.3 使用步骤 1.5 增强for循环 1.5.1 概述 1.5.2 使用 1.6 泛型 1.6.1 概述 1.6.2 使用泛型的利弊 1.6.2.1 好处 1…

基于TriDet的时序动作检测算法训练自己的slowfast数据

最近一直在研究时序动作识别和检测&#xff0c;也一直关注着目前的最新进展&#xff0c;有好的算法&#xff0c;我都会在我自己的数据集上运行看看&#xff0c;一方面是为自己累积相关算法&#xff0c;另一方面也是想看看&#xff0c;目前最新的算法是否可以应用到一些项目上。…

leetcode刷题(剑指offer) 240.搜索二维矩阵Ⅱ

240.搜索二维矩阵Ⅱ 编写一个高效的算法来搜索 *m* x *n* 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,11,15],[2,5,8,12,19],[3,…

SpringBoot使用mybatis-plus代码生成器且xml文件生成在resource文件下

SpringBoot使用mybatis-plus代码生成器且xml文件生成在resource文件下 一、Pom依赖二、核心代码三、效果 一、Pom依赖 <!--项目代码初始化生成器两个依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator<…

axi_quad_spi

文章目录 系统框图正常模式XIP模式 性能IP 核配置AXI Interface OptionXIP ModePerformance Mode SPI OptionsModeTransaction WidthFrequency RatioSlave DeviceEnable Master ModeEnable STARTUP Primitive 寄存器映射0x40 (SRR) 软件复位0x60 (SPICR) SPI控制0x64 (SPISR) S…

WAF攻防相关知识点总结2-代码免杀绕过

WAF的检测除了有对于非正常的流量检测外还对于非正常的数据包特征进行检测 以宝塔为例 在宝塔的后台可以放置一句话木马的文件 宝塔不会对于这个文件进行拦截&#xff0c;但是一旦我们使用菜刀蚁剑等webshell工具去进行连接的时候&#xff0c;数据报中有流量特征就会被拦截 …

【仿网易云H5部署】Nodejs后台 + uniapp前台 部署

前言 之前在b站跟着学习写了前锋的<仿网易云音乐>uniapp项目 , 在这里记录一下H5版本的部署上线的过程. 这是该项目的b站链接:【千锋教育】前端项目_uni-app入门到实战项目之《仿网易云音乐》_哔哩哔哩_bilibili 一.后端 使用宝塔面板来部署 (1) 百度搜索宝塔面板, 复…

Java中的this和super

①this 在Java中&#xff0c;this关键字代表当前对象的引用。它可以用于以下几个方面&#xff1a; 引用当前对象的成员变量&#xff1a;使用this关键字可以引用当前对象的成员变量&#xff0c;以区分成员变量和方法参数或局部变量之间的命名冲突。例如&#xff0c;如果一个方法…

微信小程序wx.getRealtimeLogManager无法查看log内容

解决方案&#xff1a; 首先&#xff0c;检查在we分析是否启用实时日志&#xff0c;入口如下&#xff1a; 其次&#xff0c;检查基本语法是否正确&#xff0c;参考如下&#xff1a; var logger wx.getRealtimeLogManager() logger.error("error message") 最后&a…

基于若依的ruoyi-nbcio流程管理系统一种简单的动态表单模拟测试实现(四)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/n…

《Linux高性能服务器编程》笔记04

Linux高性能服务器编程 本文是读书笔记&#xff0c;如有侵权&#xff0c;请联系删除。 参考 Linux高性能服务器编程源码: https://github.com/raichen/LinuxServerCodes 豆瓣: Linux高性能服务器编程 文章目录 Linux高性能服务器编程第09章I/O复用9.1 select系统调用9.2 po…

C++ | 六、栈 Stack、队列 Queue

栈的基础知识 栈&#xff08;stack&#xff09;是一种数据结构&#xff0c;在C中属于STL&#xff08;标准库&#xff09;特点&#xff1a;先进后出 栈的使用&#xff1a; 一、引入头文件<stack>二、创建栈变量&#xff08;类似容器、集合的创建方式&#xff09;&#xf…

【心得】java从CC1链入门CC链个人笔记

来劲了&#xff0c;感觉离真正的CTF又近了一步。 本文仅从一个萌新的角度去谈&#xff0c;如有纰漏&#xff0c;纯属蒟蒻。 目录 CC链概念 CC链学习前置知识 CC1链 Version1 Version2 Version3 CC链概念 CC链 Commons Collections apache组织发布的开源库 里面主要对…

JS之打地鼠案例

需要素材的同学可以私信我 效果图&#xff1a; 上代码&#xff1a; <!DOCTYPE html> <html> <head><meta charset"utf-8"><title></title><style>* {margin: 0;padding: 0;}.box {position: relative;width: 320px;heigh…

论文阅读:Vary-toy论文阅读笔记

目录 引言整体结构图方法介绍训练vision vocabulary阶段PDF数据目标检测数据 训练Vary-toy阶段Vary-toy结构数据集情况 引言 论文&#xff1a;Small Language Model Meets with Reinforced Vision Vocabulary Paper | Github | Demo 说来也巧&#xff0c;之前在写论文阅读&…

更新至2023年各省环境规制数据合集(七种测算方法)

更新至2023年各省环境规制数据合集&#xff08;七种测算方法&#xff09; 一、2002-2023年全国各省ZF报告词频环境规制关键词词频统计数据 1、时间&#xff1a;2001-2022年 2、指标&#xff1a;文本总长度、仅中英文-文本总长度、文本总词频-全模式、文本总词频-精确模式、环…

计算机中找不到vcomp140.dll无法继续执行代码有哪些解决方法

vcomp140.dll是微软Visual C编译器的一个组件&#xff0c;主要用于支持并行计算和OpenMP库的实现。以下是这个DLL文件的属性介绍&#xff1a; 文件名&#xff1a;vcomp140.dll 这个文件名中的“140”指的是与Visual C的版本相对应的内部版本号&#xff0c;这里对应的是2015版…

聚焦AI4S,产学研专家齐聚,探讨AI工具在多领域应用的现状与趋势

2023 和鲸社区年度科研闭门会以“对话 AI for Science 先行者&#xff0c;如何抓住科研范式新机遇”为主题&#xff0c;邀请了多个领域的专家学者共同探讨人工智能在各自领域的发展现状与未来趋势。 闭门会圆桌论坛由和鲸科技联合创始人、执行总裁兼首席产品官殷自强主持&…