每天一道算法练习题--Day15 第一章 --算法专题 --- -----------二叉树的遍历

news2024/11/29 6:27:05

概述

二叉树作为一个基础的数据结构,遍历算法作为一个基础的算法,两者结合当然是经典的组合了。很多题目都会有 ta 的身影,有直接问二叉树的遍历的,有间接问的。比如要你找到树中满足条件的节点,就是间接考察树的遍历,因为你要找到树中满足条件的点,就需要进行遍历。

你如果掌握了二叉树的遍历,那么也许其他复杂的树对于你来说也并不遥远了

二叉数的遍历主要有前中后遍历和层次遍历。 前中后属于 DFS,层次遍历则可以使用 BFS 或者 DFS 来实现。只不过使用 BFS 来实现层次遍历会容易些,因为层次遍历就是 BFS 的副产物啊,你可以将层次遍历看成没有提前终止的 BFS。

DFS 和 BFS 都有着自己的应用,比如 leetcode 301 号问题和 609 号问题。

DFS 都可以使用栈来简化操作,并且其实树本身是一种递归的数据结构,因此递归和栈对于 DFS 来说是两个关键点。

DFS 图解:
在这里插入图片描述
BFS 的关键点在于如何记录每一层次是否遍历完成, 我们可以用一个标识位来表式当前层的结束。

对于前中后序遍历来说。首先不管是前中还是后序遍历,变的只是根节点的位置, 左右节点的顺序永远是先左后右。 比如前序遍历就是根在前面,即根左右。中序就是根在中间,即左根右。后序就是根在后面,即左右根。

下面我们依次讲解:

前序遍历

相关问题144.binary-tree-preorder-traversal
前序遍历的顺序是根-左-右

思路是:

  • 先将根结点入栈
  • 出栈一个元素,将右节点和左节点依次入栈
  • 重复 2 的步骤

总结: 典型的递归数据结构,典型的用栈来简化操作的算法。

其实从宏观上表现为:自顶向下依次访问左侧链,然后自底向上依次访问右侧链,如果从这个角度出发去写的话,算法就不一样了。从上向下我们可以直接递归访问即可,从下向上我们只需要借助栈也可以轻易做到。

整个过程大概是这样:
在这里插入图片描述

这种思路有一个好处就是可以统一三种遍历的思路. 这个很重要,如果不了解的朋友,希望能够记住这一点。

中序遍历

相关问题94.binary-tree-inorder-traversal
中序遍历的顺序是 左-根-右,根节点不是先输出,这就有一点点复杂了。

  • 根节点入栈
  • 判断有没有左节点,如果有,则入栈,直到叶子节点

此时栈中保存的就是所有的左节点和根节点。

  1. 出栈,判断有没有右节点,有则入栈,继续执行 2

值得注意的是,中序遍历一个二叉查找树(BST)的结果是一个有序数组,利用这个性质有些题目可以得到简化, 比如230.kth-smallest-element-in-a-bst, 以及98.validate-binary-search-tree

后序遍历

相关问题145.binary-tree-postorder-traversal
后序遍历的顺序是 左-右-根
这个就有点难度了,要不也不会是 leetcode 困难的 难度啊。

其实这个也是属于根节点先不输出,并且根节点是最后输出。 这里可以采用一种讨巧的做法, 就是记录当前节点状态,如果:

  • 当前节点是叶子节点或者
  • 当前节点的左右子树都已经遍历过了,那么就可以出栈了。

对于 1. 当前节点是叶子节点或者当前节点的左右子树都已经遍历过了,那么就可以出栈了。
对于 2. 当前节点的左右子树都已经遍历过了, 只需要用一个变量记录即可。最坏的情况,我们记录每一个节点的访问状况就好了,空间复杂度 O(n) 但是仔细想一下,我们使用了栈的结构,从叶子节点开始输出,我们记录一个当前出栈的元素就好了,空间复杂度 O(1), 具体请查看上方链接。

层次遍历

层次遍历的关键点在于如何记录每一层次是否遍历完成, 我们可以用一个标识位来表式当前层的结束。
在这里插入图片描述
具体做法:

  • 根节点入队列, 并入队列一个特殊的标识位,此处是 null
  • 出队列
  • 判断是不是 null, 如果是,则代表本层已经结束。我们再次判断是否当前队列为空,如果不为空继续入队一个 null,否则说明遍历已经完成,我们什么都不不用做
  • 如果不为 null,说明这一层还没完,则将其左右子树依次入队列。

相关问题:
在这里插入图片描述

双色标记法

我们知道垃圾回收算法中,有一种算法叫三色标记法。 即:

  • 用白色表示尚未访问
  • 灰色表示尚未完全访问子节点
  • 黑色表示子节点全部访问

那么我们可以模仿其思想,使用双色标记法来统一三种遍历。

其核心思想如下:

  • 使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。
  • 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。
  • 如果遇到的节点为灰色,则将节点的值输出。

使用这种方法实现的中序遍历如下:

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        WHITE, GRAY = 0, 1
        res = []
        stack = [(WHITE, root)]
        while stack:
            color, node = stack.pop()
            if node is None: continue
            if color == WHITE:
                stack.append((WHITE, node.right))
                stack.append((GRAY, node))
                stack.append((WHITE, node.left))
            else:
                res.append(node.val)
        return res

可以看出,实现上 WHITE 就表示的是递归中的第一次进入过程,Gray 则表示递归中的从叶子节点返回的过程。 因此这种迭代的写法更接近递归写法的本质。

如要实现前序、后序遍历,只需要调整左右子节点的入栈顺序即可。可以看出使用三色标记法, 其写法类似递归的形式,因此便于记忆和书写,缺点是使用了额外的内存空间。不过这个额外的空间是线性的,影响倒是不大。

虽然递归也是额外的线性时间,但是递归的栈开销还是比一个 0,1 变量开销大的。换句话说就是空间复杂度的常数项是不同的,这在一些情况下的差异还是蛮明显的。

划重点:双色迭代法是一种可以用迭代模拟递归的写法,其写法和递归非常相似,要比普通迭代简单地多。

Morris 遍历

我们可以使用一种叫做 Morris 遍历的方法,既不使用递归也不借助于栈。从而在 O ( 1 ) O(1) O(1) 空间完成这个过程。

如果你需要使用 O ( 1 ) O(1) O(1) 空间遍历一棵二叉树,那么就要使用 Morris 遍历。

这个算法考察相对少,作为了解即可。

def MorrisTraversal(root):
    curr = root

    while curr:
        # If left child is null, print the
        # current node data. And, update
        # the current pointer to right child.
        if curr.left is None:
            print(curr.data, end= " ")
            curr = curr.right

        else:
            # Find the inorder predecessor
            prev = curr.left

            while prev.right is not None and prev.right is not curr:
                prev = prev.right

            # If the right child of inorder
            # predecessor already points to
            # the current node, update the
            # current with it's right child
            if prev.right is curr:
                prev.right = None
                curr = curr.right

            # else If right child doesn't point
            # to the current node, then print this
            # node's data and update the right child
            # pointer with the current node and update
            # the current with it's left child
            else:
                print (curr.data, end=" ")
                prev.right = curr
                curr = curr.left

划重点:Morris 是一种可以在 O ( 1 ) O(1) O(1) 空间遍历二叉树的算法。

总结

本文详细讲解了二叉树的层次遍历和深度优先遍历。

对于深度优先遍历,我们又细分为前中后序三种遍历方式。

最后我们讲解了双色遍历和 Morris 遍历。这两种方式可以作为了解,不掌握也没关系。

另外,如果题目要求你实现迭代器(就是调用一次输出一个二叉树的值),那么前面讲的迭代的方式就非常适用了。比如这道题 Binary Search Tree Iterator

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

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

相关文章

STM32物联网实战开发(3)——串口打印

串口打印 串口的使用在单片机开发过程中经常出现,因为他在显示数据和调试过程中特别的方便,使用起来也很简单。 1.用STM32CubeMx配置串口 串口1模式选择异步,不开启硬件控制流(串口通信分为同步通信和异步通信,他们往…

云HIS : 电子病历模板制作过程技术经验分享

电子病历的制作就是按照医院机构的特色,根据不同业务需求,使用模板编辑与预览工具, 综合运用工具模块制作个性化、实用化、特色化电子病历模板的过程。 按照制作流程分为以下几个步骤: 1.明确病历类型:根据业务方向…

掌握好这几款TikTok数据分析工具,让你轻松提高曝光率!

为什么别人在TikTok发的普普通通的视频却有那么高的流量、几天内疯狂涨粉,而自己想破脑袋装饰自己的视频,结果却不如人意。 其实原因很简单,TikTok不像国内的抖音只面向中华民族,而是覆盖了150多个国家和75种语言用户&#xff0c…

【五一创作】Scratch资料袋

Scratch软件是免费的、免费的、免费的。任何需要花钱才能下载Scratch软件的全是骗子。 1、什么是Scratch Scratch是麻省理工学院的“终身幼儿园团队”开发的一种图形化编程工具。是面向青少年的一款模块化,积木化、可视化的编程语言。 什么是模块化、积木化&…

【VM服务管家】VM4.x算子SDK开发_3.1 环境配置类

目录 3.1.1 环境配置:CSharp算子SDK开发环境配置方法3.1.2 算子封装:使用C封装算子SDK的方法3.1.3 异常中断:算子SDK软件运行报错“托管调试助手”中断的解决方法3.1.4 深度学习:GPU运行深度学习算子引发StackOverFlow异常的方法 …

FP独立站推广成本太高?那是因为你没看到这篇!

近年来,越来越多的商家开始搭建自己的跨境电商独立站,做起了FP独立站。那么用独立站做FP到底有什么优势?还有,推广成本真的很高吗?今天这期就给大家扒一扒。 用独立站做FP的优势 1、塑造品牌,扩大经营触及…

【HarmonyOS】元服务WebView组件 H5使用localstorage

在日常开发中我们会在应用种接入H5网页,localStorage作为H5本地存储web storage特性的API之一,主要作用是将数据保存在客户端中。对于快速开发元服务,通过WebView组件运行H5如何使用localstorage呢?下文以API7 JavaUI为例为大家做…

k8s 集群搭建详细教程

参考: Kubernetes 文档 / 入门 / 生产环境 / 使用部署工具安装 Kubernetes / 使用 kubeadm 引导集群 / 安装 kubeadm B. 准备开始 一台兼容的 Linux 主机。Kubernetes 项目为基于 Debian 和 Red Hat 的 Linux 发行版以及一些不提供包管理器的发行版提供通用的指令每…

3.3 Linux shell命令(权限、输入输出)

目录 shell shell概述 shell分类 查看当前系统的shell 权限相关命令(也是shell命令) 基本命令 输入输出相关操作 输出命令 输入输出重定向 通配符 管道 历史查询、补齐功能 历史查询 自动补齐 命令置换 shell 什么是shell shell是一种负…

【VM服务管家】VM4.0软件使用_1.2 工具类

目录 1.2.1 文本保存:逐行保存格式化模块输出的方法1.2.2 脚本模块:循环模块搭配脚本使用的方法1.2.3 几何查找:彩色图像的几何查找方法1.2.4 深度学习:图像分割的面积的获取方法1.2.5 颜色识别:使用颜色识别工具做分类…

【Leetcode -86.分隔链表 -92.反转链表Ⅱ】

Leetcode Leetcode -86.分隔链表Leetcode -92.反转链表Ⅱ Leetcode -86.分隔链表 题目:给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每…

java数据结构之HashMap

目录 前言 1、初始化 1.1、初始化 1.2、插入第一条数据 2、数组 链表 2.1、插入数据:没有hash冲突 2.2、插入数据:Key不同,但产生hash冲突 2.3、插入数据:Key相同 3、数组 红黑树 3.1、链表如何转化为红黑树? 3.…

Postman测试实践笔记

Postman测试实践 文章目录 Postman测试实践一、Postman安装与使用1.1 Postman下载及安装1.1.2 Postman Mac版 1.2 Postman 更新1.2.1 mac 版更新 1.3 Postman 其他问题 二、网络相关知识2.1 接口2.1.1 软件为什么需要接口 2.2 接口测试2.2.1 什么是接口测试:2.2.2 为…

VTK下载并安装

去官网下载https://vtk.org/download/ 选择最新稳定版本 然后点击source后边的压缩包进行下载。 下载完成后将其解压到特定的文件夹下,然后打开cmake-gui.exe,第一行选择刚刚解压的文件夹,这个文件夹下有一个CMakeLists.txt文件&#xff0c…

【6. 激光雷达接入ROS】

欢迎大家阅读2345VOR的博客【6. 激光雷达接入ROS】🥳🥳🥳 2345VOR鹏鹏主页: 已获得CSDN《嵌入式领域优质创作者》称号👻👻👻,座右铭:脚踏实地,仰望星空&#…

Go | 一分钟掌握Go | 8 - 并发

作者:Mars酱 声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联系本人。 转载:欢迎转载,转载前先请联系我! 前言 当今编程界,一个好的编译型语言如果不支持并发&#xff0c…

工控老司机告诉你热电偶和RTD的区别

热电偶和热电阻都是温度传感器,但它们的原理、功能特性和应用场景有所不同。 一、原理区别 首先,热电偶是利用两种不同金属之间的热电效应来测量温度的。其原理是利用温度差引起的金属之间的热电势差进行测量。两种金属之间存在一种热电势(…

Yolov8优化:卷积变体---分布移位卷积(DSConv),提高卷积层的内存效率和速度

论文: https://arxiv.org/pdf/1901.01928v1.pdf 摘要:提出了一种卷积的变体,称为DSConv(分布偏移卷积),其可以容易地替换进标准神经网络体系结构并且实现较低的存储器使用和较高的计算速度。 DSConv将传统的卷积内核分解为两个组件:可变量化内核(VQK)和分布偏移。 通过…

双亲委派机制的原理和作用

双亲委派机制,就必须弄清楚Java的类加载器。 什么是类加载器 Java类加载器(ClassLoader)是Java运行时环境(JRE)的一部分,负责动态的将Java类加载到Java虚拟机的内存空间。 类加载器有哪些 主要有三个: 引导类加载器(Bootstrap ClassLoade…

前端开发在本地开发与后台进行联调阶段时,接口自动重定向https、HSTS 与 307 状态码

开发者在本地开发与后台进行联调阶段时,Chrome 浏览器上出现 307 状态码,并跳转到 https 版 但是 307 代码是什么含义呢?页面又为何会出现 307 状态码呢?我之前都没见过这个状态码,查了才知道原来它也是一种重定向。 …