图解二叉树的层序遍历BFS(广度优先)模板,附面试题题解

科技资讯 投稿 53600 0 评论

图解二叉树的层序遍历BFS(广度优先)模板,附面试题题解

壹 ❀ 引

从JS执行栈角度图解递归以及二叉树的前、中、后遍历的底层差异一文中,从一个最基本的数组遍历引出递归,在掌握递归的书写规则后,又从JS执行栈角度解释了二叉树三种深度优先(前序、中序后序)的底层差异,帮助大家站在模板的角度上深入理解模板。而二叉树还剩一种广度优先(也称层序遍历)也使用广泛,但考虑到篇幅问题,所以还是打算另开一篇文章讲解。

其实相对深度优先,广度优先的模板要好理解的一些,毕竟它没有让人头疼的递归,且我们只用维护好一个队列(先进先出)即可,但考虑到刚接触算法人基础不同,我们还是用图解以及更直白的话术来讲解层序遍历的模板,那么本文开始。

贰 ❀ 壹 从队列与栈说起

先进后出的特性,这是JS底层帮我们做的事。那我们自己能不能模拟栈呢?显然是可以的,通过数组的与方法,比如数字我可以先全部到一个空数组中变成,然后再利用依次弹出就能倒序访问变成。

const stack = [];
// 先进
stack.push(...[1, 2, 3];

while (stack.length > 0 {
  // 后出
  console.log(stack.pop(;// 3 2 1
}

用图来表示这个过程:

而栈的特性是先进后出,我们是不是一样可以用数组模拟这个过程,别忘了数组还有方法:

const stack = [];
stack.push(...[1, 2, 3];

while (stack.length > 0 {
  console.log(stack.shift(; // 1 2 3
};

理解了用数组模拟队列的过程,那么层序遍历就理解了一半了。

叁 ❀ 图解层序遍历

我们来看一个最基本的二叉树层序遍历例子,如下,如果要层序遍历输出自然是,那么怎么做?

检查的长度,只要证明还有元素,那就标明可以继续做我们要做的事。

很明显面对上图的二叉树,第一次层自然是根节点,那我们就可以将根节点先塞到中:

先进先出的特性,我们需要将从数组头部弹出。那么问题来了,要是弹出长度直接为遍历结束,但我们希望继续检查第二层,我们是不是得将的子节点依次也到中,于是就有了下面这一步,将入队列,并将弹出:

同理,因为当前长度为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.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;
}

我就跟他说,你这考的也太简单了,不如再做个变体,我希望层序遍历输出的同时,同层的元素能装在同一个数组里:

大家可以先自己尝试下怎么写,其实思路还是一样,只是在遍历某一层时,我们需要额外再定一个来装这一层的数组,代码如下:

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;
}

入队列----判断队列长度----出队列----记录值----将子加入队列。但是这个过程很明显不能让每一层的元素在同一个数组,因此我们每次处理某一层之前,先创建一个,之后我们肯定得知道当前层有几个数组,因此额外需要遍历一次,依次将当前层元素的值加入到,直到这一层遍历完成,我们值记录也完成了,同时这一层节点的子节点也被加入到了。

简单点来理解就是,我们通过控制还能不能遍历,通过内部的来控制每一层具体的遍历操作,思维转换也比较简单,将原先的处理逻辑丢到里面即可,而需要做几次呢?我们可以通过的长度来决定,你要相信,当进入到下一轮时,一定只包含了某一层的元素。

虽然这里不是递归,但是的迭代本质上跟递归没什么差异,上篇文章我教大家将改成写递归也是这个目的,你只用关注当前需要做什么,后续的迭代(递归)会帮你做相同的事,大概就是这么个意思了。

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(广度优先)模板,附面试题题解

赞同 (46) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽