五三想休息,今天还学习,图解二叉树的层序遍历BFS(广度优先)模板,附面试题题解

壹 ❀ 引
我在从JS执行栈角度图解递归以及二叉树的前、中、后遍历的底层差异一文中,从一个最基本的数组遍历引出递归,在掌握递归的书写规则后,又从JS执行栈角度解释了二叉树三种深度优先(前序、中序后序)的底层差异,帮助大家站在模板的角度上深入理解模板。而二叉树还剩一种广度优先(也称层序遍历)也使用广泛,但考虑到篇幅问题,所以还是打算另开一篇文章讲解。
其实相对深度优先,广度优先的模板要好理解的一些,毕竟它没有让人头疼的递归,且我们只用维护好一个队列queue(先进先出)即可,但考虑到刚接触算法人基础不同,我们还是用图解以及更直白的话术来讲解层序遍历的模板,那么本文开始。
贰 ❀ 壹 从队列与栈说起
其实通过前文二叉树深度遍历的讲解,所谓前序、中序与后序遍历的差异,本质上是将做一件事的动作放在了递归前后不同位置,而这个原理其实依赖了JS执行栈先进后出的特性,这是JS底层帮我们做的事。那我们自己能不能模拟栈呢?显然是可以的,通过数组的push与pop方法,比如数字1 2 3我可以先全部push到一个空数组中变成[1, 2, 3],然后再利用pop依次弹出就能倒序访问变成3 2 1。
const stack = [];
// 先进
stack.push(...[1, 2, 3]);
while (stack.length > 0) {
// 后出
console.log(stack.pop());// 3 2 1
}
用图来表示这个过程:

而栈的特性是先进后出,我们是不是一样可以用数组模拟这个过程,别忘了数组还有shift方法:
const stack = [];
stack.push(...[1, 2, 3]);
while (stack.length > 0) {
console.log(stack.shift()); // 1 2 3
};

理解了用数组模拟队列的过程,那么层序遍历就理解了一半了。
叁 ❀ 图解层序遍历
我们来看一个最基本的二叉树层序遍历例子,如下,如果要层序遍历输出自然是[1,2,3,4,5,6,7],那么怎么做?

固定套路,我们可以先定义一个承装结果的数组ans,以及一个模拟队列的数组queue,整个遍历的过程都会检查queue的长度,只要length > 0证明还有元素,那就标明可以继续做我们要做的事。
很明显面对上图的二叉树,第一次层自然是根节点,那我们就可以将根节点先塞到queue中:

然后开始遍历queue做我们要做的操作,做什么呢?当然是将当前节点的值存起来,之后因为节点1已经访问过了,遵循先进先出的特性,我们需要将1从数组头部弹出。那么问题来了,1要是弹出queue长度直接为0遍历结束,但我们希望继续检查第二层,我们是不是得将1的子节点依次也push到queue中,于是就有了下面这一步,将2 3入队列,并将1弹出:

同理,因为当前queue长度为2,很明显我们能继续遍历,依次将2,3存入ans数组,之后再将2的子节点4,5与3的子节点6,7存入queue,同时2,3出队列。

之后我们再将4,5,6,7的值存入ans,但由于这四个节点没有自己的子节点,所以我们只用让它们四个出队列,也由于没有新的节点进来,queue长度为空,终于整个查找过程结束,我们顺序得到ans [1,2,3,4,5,6,7]。
在通过图解解释了这个过程后,我们就能很清晰定义出层序遍历的模板:
const levelTraverse = (root) => {
if (root === null) return;
const queue = [];
const ans = [];
queue.push(root);
// 当队列为空时跳出遍历
while (queue.length > 0) {
// 当前节点出队列
const node = queue.shift();
// 做你要做的操作
ans.push(node.val);
// 别忘了让node的左右子节点进入队列
// 因为不是递归了,加入前还是有必要检查有没有子节点
if (node.left !== null) {
queue.push(node.left);
}
if (node.right !== null) {
queue.push(node.right);
}
};
return ans;
}
肆 ❀ 一道基础的层序遍历面试题
那么图解完层序遍历模板,我们趁热打铁来做道题。我现在公司有一个同事一直负责他们部门的前端招聘工作,有一次他跟我说今天一个来面试候选人有点厉害,算法10分钟就写出来了,我出于兴趣就问他考的什么算法题?于是他就把题目发给我了,题目如下:
// 树结构的广度优先遍历(BFS)-(建议做题时长:20分钟)
// 从后端拿到了一个文档节点树结构,请按广度优先遍历(BFS)的顺序,返回该树结构的所有节点名字。
// 树节点列表的数据说明:
// 节点定义
const NodeType = {
name: '<Title>' // 节点名字
children: [], // 子节点列表
}
// 样例数据
const ExampleTreeRoot = {
name: "Top",
children: [{
name: "Level 1",
children: [{
name: "Level 1-1",
children: [],
},
{
name: "Level 1-2",
children: []
}
],
},
{
name: "Level 2",
children: [{
name: "Level 2-1",
children: [],
},
{
name: "Level 2-2",
children: []
}
]
}
]
}
// 按广度优先遍历返回的结果:
[
"Top",
"Level 1",
"Level 2",
"Level 1-1",
"Level 1-2",
"Level 2-1",
"Level 2-2"
]
我一看,这不就是个最基础的层序遍历吗,你就拿这个考验干部,大家可以结合上面的讲解思路自己做做此题,答案如下:
const levelTraverse = (root) => {
if (root === null) return;
const queue = [];
const ans = [];
queue.push(root);
while (queue.length > 0) {
const node = queue.shift();
ans.push(node.name);
queue.push(...node.children);
};
return ans;
}
我就跟他说,你这考的也太简单了,不如再做个变体,我希望层序遍历输出的同时,同层的元素能装在同一个数组里:

大家可以先自己尝试下怎么写,其实思路还是一样,只是在遍历某一层时,我们需要额外再定一个levelArr来装这一层的数组,代码如下:
const levelTraverse = (root) => {
if (root === null) return;
const queue = [];
const ans = [];
queue.push(root);
while (queue.length > 0) {
// 创建每一层的数组
const levelArr = [];
// 获取每一层的长度
const len = queue.length;
// 每一层的操作单独再交给一个for来处理
for (let i = 0; i < len; i++) {
const node = queue.shift();
levelArr.push(node.name);
queue.push(...node.children);
}
ans.push(levelArr);
};
return ans;
}
这种做法跟之前有什么区别呢?之前我们始终维护一个queue,然后不断的入队列----判断队列长度----出队列----记录值----将子加入队列。但是这个过程很明显不能让每一层的元素在同一个数组,因此我们每次处理某一层之前,先创建一个levelArr,之后我们肯定得知道当前层有几个数组,因此额外需要遍历一次,依次将当前层元素的值加入到levelArr,直到这一层遍历完成,我们值记录也完成了,同时这一层节点的子节点也被加入到queue了。
简单点来理解就是,我们通过queue控制还能不能遍历,通过内部的for来控制每一层具体的遍历操作,思维转换也比较简单,将原先while的处理逻辑丢到for里面即可,而需要做几次呢?我们可以通过queue的长度来决定,你要相信,当进入到下一轮queue时,queue一定只包含了某一层的元素。
虽然这里不是递归,但是while的迭代本质上跟递归没什么差异,上篇文章我教大家将while改成写递归也是这个目的,你只用关注当前需要做什么,后续的迭代(递归)会帮你做相同的事,大概就是这么个意思了。
那么在讲解完这个之后,我相信大家也能轻易拿下 leetcode 102. 二叉树的层序遍历,这个题难度还是中等,但是我相信对于现在的你而言非常简单,当然,我还是直接套模板给大家提供一个思路:
var levelOrder = function (root) {
// 这里是题目特性,它希望空时返回空数组
if (root === null) return [];
const ans = [];
const queue = [];
queue.push(root);
while (queue.length > 0) {
const len = queue.length;
const levelArr = [];
for (let i = 0; i < len; i++) {
const node = queue.shift();
levelArr.push(node.val);
if (node.left) {
queue.push(node.left);
};
if (node.right) {
queue.push(node.right);
};
}
ans.push(levelArr);
}
return ans;
};
伍 ❀ 总
OK,那么到这里,我们花了两个篇幅分别解释了二叉树的深度优先以及广度优先的模板含义,我相信在理解二叉树不同遍历方式到底是如何运转之后,对于模板的理解也会更上一个层次。也希望这两篇文章,在之后的二叉树算法中能为大家带来一定帮助,那么到这里本文结束。
五三想休息,今天还学习,图解二叉树的层序遍历BFS(广度优先)模板,附面试题题解的更多相关文章
- 二叉树的层序遍历 BFS
二叉树的层序遍历,或者说是宽度优先便利,是经常考察的内容. 问题一:层序遍历二叉树并输出,直接输出结果即可,输出格式为一行. #include <iostream> #include &l ...
- Leetcode 102. Binary Tree Level Order Traversal(二叉树的层序遍历)
Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, ...
- 剑指offer 二叉树的层序遍历
剑指offer 牛客网 二叉树的层序遍历 # -*- coding: utf-8 -*- """ Created on Tue Apr 9 09:33:16 2019 @ ...
- leetcode之二叉树的层序遍历
1.题目描述 2.题目分析 二叉树的层序遍历主要算法思想是使用 队列这一数据结构实现,这个数据结构多应用在和 图相关的算法.例如图的广度优先遍历就可以使用队列的方法实现.本题的关键在于如何识别出一层已 ...
- LeetCode 102. 二叉树的层序遍历 | Python
102. 二叉树的层序遍历 题目来源:https://leetcode-cn.com/problems/binary-tree-level-order-traversal 题目 给你一个二叉树,请你返 ...
- 刷题-力扣-107. 二叉树的层序遍历 II
107. 二叉树的层序遍历 II 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/binary-tree-level-order-tr ...
- 二叉树的深度优先遍历与广度优先遍历 [ C++ 实现 ]
深度优先搜索算法(Depth First Search),是搜索算法的一种.是沿着树的深度遍历树的节点,尽可能深的搜索树的分支. 当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点 ...
- 【LeetCode】102. Binary Tree Level Order Traversal 二叉树的层序遍历 (Python&C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 BFS DFS 日期 题目地址:https://lee ...
- leetcode题解:Tree Level Order Traversal II (二叉树的层序遍历 2)
题目: Given a binary tree, return the bottom-up level order traversal of its nodes' values. (ie, from ...
随机推荐
- IDEA 创建javaWeb以及Servlet
1.新建项目 2.Web工程设置:点击项目名称,按F4 (1)配置sources:在WEB-INF下新建两个文件夹classes和lib (2)配置path:刚刚创建的classes文件夹路径 (3) ...
- 学习openstack(一)
一.云计算 云计算特点:必须通过网络使用:弹性计算(按需付费):对用户是透明的(用户不考虑后端的具体实现): 云计算分类:私有云.公有云(amazon是老大.aliyun.tingyun.tencen ...
- 学习MFS(三)
1.MooseFS是什么 一个类MooseFS是一个具备冗余容错功能的分布式网络文件系统,它将数据分别存放在多个物理服务器或单独磁盘或分区上,确保一份数据有多个备份副本,然而对于访问MFS的客户端或者 ...
- 单总线协议DS1820
一. DS18B20简介 DS18B20数字温度传感器接线方便,封装后可应用于多种场合,如管道式,螺纹式,磁铁吸附式,不锈钢封装式.主要根据应用场合的不同而改变其外观.封装后的DS18B20可用于电缆 ...
- 「腾讯视频」微信小程序插件介绍
上期,我们在<从原理到应用,一文带你了解小程序插件能力>一文中介绍了小程序插件的意义.作用以及应用.今天开始,我们会每期与大家分享一款优秀的小程序插件,从使用场景到使用方法,都将作出详细的 ...
- 五款轻量型bug管理工具横向测评
五款轻量型bug管理工具横向测评 最近正在使用的本地bug管理软件又出问题了,已经记不清这是第几次了,每次出现问题都要耗费大量的时间精力去网上寻找解决方案,劳心劳力.为了避免再次出现这样的情况,我决定 ...
- 微信小程序版博客——开发汇总总结(附源码)
花了点时间陆陆续续,拼拼凑凑将我的小程序版博客搭建完了,这里做个简单的分享和总结. 整体效果 对于博客来说功能页面不是很多,且有些限制于后端服务(基于ghost博客提供的服务),相关样式可以参考截图或 ...
- LazyCaptcha自定义随机验证码和字体
介绍 LazyCaptcha是仿EasyCaptcha和SimpleCaptcha,基于.Net Standard 2.1的图形验证码模块. 目前Gitee 52star, 如果对您有帮助,请不吝啬点 ...
- Docker入门(windows版),利用Docker创建一个Hello World的web项目
Docker 当大家点开这篇博客的时候,相信大家对docker多多少少也有些认识了,最近学习docker这门技术,略微有些心得,写篇文章记录一下学习过程并帮大家跳过一些坑. docker的核心有两个, ...
- Python入门-多进程
1.获取本机CPU # 早期的CPU是单核:实现多个程序并行,在某一时间点,其实只有一个进程 # 后来硬件多核CPU:多个进程是并行执行. from multiprocessing import cp ...