树的层次遍历

层次遍历简介

        广度优先在面试里出现的频率非常高,整体属于简单题。而广度优先遍历又叫做层次遍历,基本过程如下:

image.png

        层次遍历就是从根节点开始,先访问根节点下面一层全部元素,再访问之后的层次,类似金字塔一样,逐层访问。我们可以看到上面例子就是从左到右一层一层遍历二叉树,先访问3,之后访问1的左右孩子9和10,之后分别访问9和20的左右孩子[4,5]和[6,7] ,最后得到结果[3,9,20,8,13,15,7]

        这里需要关注的问题是:将遍历过的元素的左右孩子保存起来。例如:访问9时,其左右孩子8和13应该先保存一下,直到处理20之后才会处理。使用队列就是一个比较好的方法。

        以上图为例,过程如下

  1. 先将3入队(使用链表实现的队列)
  2. 然后3出队,接着将3的左右孩子9和10  保存在队列中
  3. 然后9 出队,将9的左右孩子8和13入队
  4. 接着20出队,将15和7入队
  5. 最后8  13  15  7  分别出队,此时都是叶子节点了,没有入队的元素了。 

拓展

如果能将树的每层次分开了,是否可以整点新花样?

        首先,能否将每层的元素顺序给反转一下呢?

        能否奇数行不变,只将偶数行反转呢?

        能否将输出层次从低到root逐层输出呢?

        再来,既然能拿到每一层的元素了,能否找到当前层最大的元素? 最小的元素? 最右的元素(右视图)? 最左的元素(左视图) ? 整个层的平均值? 

而以上问题就是经典的层次遍历题。

题号如下

102.二又树的层序遍历
107.二又树的层次遍历lIl
199.二又树的右视图
637.二又树的层平均值
429.N又树的前序遍历
515.在每个树行中找最大值
116.填充每个节点的下一个右侧节点指针
117.填充每个节点的下一个右侧节点指针Il
103 锯齿层序遍历 

基本的层序遍历与变换

题目

        遍历并输出全部元素,二叉树示例如下:

*              3
*            /   \
*          9      20
*                 /  \
*               15   7

代码实现思路

         先访问根节点,然后将其左右孩子放到队列里,接着继续出队列;出来的元素,将其左右孩子放入队列中,直到队列为空了才退出来。

代码实现

节点代码

public TreeNode{public int data;public TreeNode left;public TreeNode right;public TreeNode(int data){this.data = data;}
}

实现代码

public List<Integer> simpleLevelTraverse(TreeNode root){// 创建一个数组实现的集合接收遍历后的结果List<Integer> res = new ArrayList<>();// 如果该树是一颗空树,返回空的集合if(root == null){return res;}// 使用链表当做队列,用于存储树的节点// 从尾插入,从头删除LinkedList<TreeNode> queue = new LinkedList<>();// 将根节点放入队列中,然后不断遍历根节点queue.add(root);// 有多少元素就遍历多少次while(queue.size() > 0){// remove():获取并移除此队列的头(第一个元素)TreeNode t = queue.remove();// 使用集合搜集遍历后的节点数据部分res.add(t.data);// 如果该节点有左节点(不为空),则将其加入队列中,用于下次遍历if(t.left != null){queue.add(t.left);}// 如果该节点有右节点(不为空),则将其加入队列中,用于下次遍历if(t.right != null){queue.add(t.right);}}// 返回遍历后的结果return res;}

         根据树的结构可以看到,一个节点在一层访问之后,其子孩子都是在下一层按照FIFO的顺序处理的,因此,队列就相当于一个缓冲区。这题是逐层自左向右执行的,如果将每层的元素分开打印,便有了另一种题目。

二叉树的层序遍历(每层元素分开打印)

题目

        给你一个二叉树,请你返回其按照程序遍历得到的节点值。(即逐层从左到右遍历)

示例

     * 二叉树:[3,9,20,null,null,15,7]*              3*            /   \*          9      20*                 /  \*               15   7
*        返回后程序遍历结果*        [*        [3],*        [9,20],*        [15,7]*        ]

 解析

        此题依旧可以使用队列存储每一层的节点。但题目要求逐层节点打印,那就需要考虑某一层是否访问完。针对这个问题,可以使用一个变量size标记一下某一层的元素个数,只要出队列一个节点,就将size的值减1,减到0,说明该层元素访问完了(当然,也可以从0开始遍历,遍历到size个的时候(i < size)就说明这一层的节点都访问过了),然后重新将size标记为下一层节点的个数,继续执行(因此,size需要在循环中创建)。

        对于结果集,可以看作外层一个集合(用于存储层数),里面一层集合(用于存储每一层的节点值)

代码实现 

public List<List<Integer>> getEveryLevelNodeFromTop(TreeNode root){List<List<Integer>> res = new ArrayList<List<Integer>>>();if(root == null){return res;}LinkedList<TreeNode> queue = new LinkedList<>();queue.add(root);while(queue.size()>0){List<Integer> temp = new  ArrayList<>();int size = queue.size();   for(int i = 0 ; i < size ; i++){TreeNode t = queue.remove();temp.add(t.data);if(t.left != null){queue.add(t.left);}if(t.right != null){queue.add(t.right);}}res.add(temp);}return res;
}

重要性

         上面的代码是整个算法体系的核心算法之一,与链表反转、二分查找属于同一个级别!

层序遍历(自底向上)

题目

        给定一个二叉树,返回其节点值自底向上的层序遍历。(即:按从叶子节点所在层到根节点所在的层,逐层自左向右遍历)

*              3
*            /   \
*          9      20
*                 /  \
*               15   7

示例

[

        [15,7],

        [9,20],

        [3]

]

分析

        如果要求从上到下输出每一层的节点值,做法是很直观的。在遍历完一层节点之后,将存储该层节点值的列表添加到结果列表的尾部。这道题要求从下到上输出每一层的节点值,只要对上述操作稍作修改即可在遍历完一层节点之后,将存储该层节点值的列表添加到结果列表的头部。 

        为了降低在结果列表的头部添加一层节点值的列表的时间复杂度,结果列表可以使用链表的结构,在链表头部添加一层节点值的列表的时间复杂度是 O(1)。在 Java 中,由于我们需要返回的 List 是一个接口,这里可以使用链表实现。

代码实现 

public List<List<Integer>> getEveryNodeFromBottom(TreeNode root){List<List<Integer>> res = new LinkedList<List<Integer>>();if(root == null){return res;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while(! queue.isEmpty()){List<Integer> temp = new ArrayList<>();int size = queue.size();for(int i = 0 ; i < size ; i++){TreeNode t = queue.pull();temp.add(t.data);TreeNode left = t.left , right = t.right;if(left != null){queue.offer(left);}if(right != null){queue.offer(right);}}// 栈结构:将先进入结果集的集合放入后面res.add(0,temp);}return res;
}

二叉树的锯齿形遍历

题目

给定一个二叉树,返回其节点值的锯齿形层序遍历(即:先从左往右,再从右往左进行下一层遍历。以此类推,层与层之间交替执行)

*              3
*            /   \
*          9      20
*                 /  \
*               15   7

示例

[

        [3],

        [20,9],

        [15,7]

]

分析

        这个题与之前的区别只是最后输出的要求有所变化,要求我们按层数的奇偶来决定每一层的输出顺序如果当前层数是偶数,从左至右输出当前层的节点值,否则,从右至左输出当前层的节点值。为了满足题目要求的返回值为[先从左往右,再从右往左]交替输出的锯齿形,可以利用[双端队列,的数据结构来维护当前层节点值输出的顺序。双端队列是一个可以在队列任意一端插入元素的队列。在广度优先搜索遍历当前层节点拓展下一层节点的时候我们仍然从左往右按顺序拓展,但是对当前层节点的存储我们维护一个变量 isOrderLeft 记录是从左至右还是从右至左的:
        如果从左至右,我们每次将被遍历到的元素插入至双端队列的末尾。
        从右至左,我们每次将被遍历到的元素插入至双端队列的头部。

代码实现

  /*** 给定一个二叉树,返回其节点值的锯齿形层序遍历(即:先从左往右,再从右往左进行下一层遍历。以此类推,层与层之间交替执行)* @param root 树的根节点* @return 交替执行的遍历结果*/public List<List<Integer>> levelOrderBetweenLeftAndRight(TreeNode root){LinkedList<List<Integer>> res = new LinkedList<List<Integer>>();if (root == null){return res;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);boolean isOrderLeft = true;while (!queue.isEmpty()){Deque<Integer> levelLeft = new LinkedList<Integer>();int size = queue.size();for (int i = 0; i < size; i++) {TreeNode curNode = queue.remove();if (isOrderLeft){levelLeft.offerLast(curNode.data);}else {levelLeft.offerFirst(curNode.data);}if (curNode.left != null){queue.add(curNode.left);}if (curNode.right != null) {queue.add(curNode.right);}}res.add(new LinkedList<>(levelLeft));isOrderLeft = !isOrderLeft;}return res;}

层次遍历(N叉树) 

 题目

        给定一个 N 又树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)树的序列化输入是用层序遍历,每组子节点都由 null 值分隔 (参见示例)。

 示例

输入: root = [1,null,3,2,4,nu11,5,6](表述树的元素是这个序列)

输出: [[1],[3,2,4],[5,6]]

代码实现

N叉树代码

class MutilTreeNode {public int data ;public List<MutilTreeNode> children;
}

实现 

public List<List<Integer>> nLevelOrder(MutilTreeNode root){ArrayList<List<Integer>> res = new ArrayList<>();Deque<MutilTreeNode> deque = new LinkedList<>();if (root != null){//Java中的java.util.LinkedList.addLast()方法用于在LinkedList的末尾插入特定元素。deque.addLast(root);}while (! deque.isEmpty()){ArrayList<Integer> nd = new ArrayList<>();ArrayDeque<MutilTreeNode> next = new ArrayDeque<>();while (! deque.isEmpty()){MutilTreeNode cur = deque.pollFirst();nd.add(cur.data);for (MutilTreeNode child : cur.children) {if (child != null){next.add(child);}}deque = next;res.add(nd);}}return res;}

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

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

相关文章

Python 开发工具 Pycharm —— 使用技巧Lv.3

单步执行调试 1&#xff1a; 鼠标左键单击红点是断点行 2&#xff1a;甲虫样式是进行调试方式运行&#xff0c;鼠标左键单击点击 3&#xff1a; 单步运行图标&#xff0c;点击让程序运行一行 4&#xff1a; 步入步出&#xff0c;可以进入当前代码行函数内 5&#xff1a;重新运行…

C语言笔试训练【第三天】

大家好&#xff0c;我是纪宁。 今天是C语言笔试训练的第三天&#xff0c;大家加油&#xff01; 第一题 1、已知函数的原型是&#xff1a; int fun(char b[10], int *a) &#xff0c;设定义&#xff1a; char c[10];int d; &#xff0c;正确的调用语句是&#xff08; &#xf…

Java分布式微服务1——注册中心(Eureka/Nacos)

文章目录 基础知识注册中心Eureka注册中心与Ribbon负载均衡1、Eureka注册中心2、Eureka的搭建3、Eureka服务注册4、复制服务实例5、拉取服务6、Ribbon负载均衡的流程及Eureka规则调整&#xff1a;7、Ribbon负载均衡饥饿加载 Nacos注册中心1、服务端Nacos安装与启动2、客户端Nac…

iOS 搭建组件化私有库

一、创建私有库索引 步骤1是在没有索引库的情况下或者是新增索引的时候才需要用到&#xff08;创建基础组件库&#xff09; 首先在码云上建立一个私有库索引&#xff0c;起名为SYComponentSpec 二、本地添加私有库索引 添加私有库索引 pod repo add SYComponentSpec https:/…

Vol的学习

nen 首先学习基础用法 1.查看系统基本信息 imageinfo vol.py -f 路径 imageinfo 2.查看进程命令行 cmdline cmdline vol.py -f 路径 --profile系统版本 cmdline vol.py -f 路径 --profile版本 cmdscan 3.查看进程信息 pslist vol.py -f 路径 --profile系统 pslist 通过…

maven开发利器:idea安装maven依赖分析插件 Maven Helper,谁用谁知道!

大家好&#xff0c;我是三叔&#xff0c;很高兴这期又和大家见面了&#xff0c;一个奋斗在互联网的打工人。 这篇博客给大家介绍一款博主实战开发中一直在使用的pom开发分析利器&#xff0c;教大家玩转maven&#xff1a;使用idea安装 Maven Helper 插件&#xff0c;可以分析依…

Dubbo+Zookeeper使用

说明&#xff1a;Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题&#xff0c;官方提供了 Java、Golang 等多语言 SDK 实现。 本文介绍Dubbo的简单使用及一些Dubbo功能特性&#xff0c;注册中心使用的是ZooKeeper&#xff0c;可在…

Mybatis 实体类属性名和表中字段名不一致怎么处理

一. 前言 最近耀哥有学生出去面试&#xff0c;被问到 “Mybatis实体类的属性名和表中的字段名不一致该怎么处理&#xff1f;”&#xff0c;这其实是一个很经典的面试题&#xff0c;接下来耀哥就为大家详细解析一下这道面试题。 二. 分析 2.1 实体类和字段名不一致所带来的后果…

在 aosp 中启用 Material You design

作者&#xff1a;Mr_万能胶 近期研究了一下如何在 aosp 中启用 Material You design&#xff0c;在把踩过的坑记录一下&#xff0c;方便后续有厂商可以快速集成。 本文基于 aosp 最新代码&#xff0c;版本号为 Android 13&#xff0c;并使用 Cuttlefish 快速验证。 Material …

flutter开发实战-flutter_spinkit实现多种风格进度指示器

flutter开发实战-flutter_spinkit实现多种风格进度指示器 最近开发过程中flutter_spinkit&#xff0c;这个拥有多种种风格加载指示器 一、flutter_spinkit 引入flutter_spinkit # 多种风格的模糊进度指示器flutter_spinkit: ^5.1.0效果示例 const spinkit SpinKitRotatingC…

安卓4G核心板开发板_MTK6785/MT6785(Helio G95)安卓手机主板方案

联发科MTK6785&#xff08;Helio G95&#xff09;安卓核心板采用八核 CPU 具有两个强大的 Arm Cortex-A76 处理器内核&#xff0c;主频高达 2.05GHz&#xff0c;外加六个 Cortex-A55 高效处理器。其强大的图形性能由 Arm Mali-G76 MC4 提供&#xff0c;速度可提升至 900MHz 。 …

Matlab的信号频谱分析——FFT变换

Matlab的信号频谱分析——FFT变换 Matlab的信号频谱分析 FFT是离散傅立叶变换的快速算法&#xff0c;可以将一个时域信号变换到频域。 有些信号在时域上是很难看出什么特征的。但是如果变换到频域之后&#xff0c;就很容易看出特征了。 这就是很多信号分析采用FFT变换的原因…