二叉树的遍历

160 阅读4分钟

二叉树

二叉树是一种非线性数据结构,其每个节点可以包含1个左孩子和一个右孩子,表示的是“孩子”与“祖先”的派生关系。二叉树可以像链表一样存储,也可以使用数组来存储,因为在二叉树中我们可以通过当前节点的索引来计算得到左右孩子以及父节点的索引。

二叉树的遍历

如图所示是一棵二叉树:

image.png

二叉树的遍历包含以下四种方式:

  • 深度优先遍历:逐步向最深层遍历的方式
    • 先序遍历:对于每个节点都是按照:中间节点、左子节点、右子节点的顺序遍历
    • 中序遍历:对于每个节点都是按照:左子节点、中间节点、右子节点的顺序遍历
    • 后序遍历:对于每个节点都是按照:左子节点、右子节点、中间节点的顺序遍历
  • 广度优先遍历:一般称为层序遍历,即逐层进行遍历。

对于上图二叉树而言,不同的遍历方式得到的结果如下:

  • 先序:1,2,3,4,5
  • 中序:3,2,4,1,5
  • 后续:3,4,2,5,1
  • 层序:1,2,5,3,4

深度优先遍历中的先序、中序、后序实际上就是指的中间节点在三个节点中的遍历顺序。

知道了这些,那代码如何实现呢?

对于深度优先遍历中的三种遍历方法,主要介绍递归方法,并给出一种具有通用性的迭代方法。

深度优先遍历

对应LeetCode题目:

递归

在我们使用递归时,需要注意递归的三大要素:

  1. 方法返回值和参数
  2. 终止条件
  3. 单次遍历的逻辑

一起分析一下先序遍历中递归的三大要素:

  1. 每次递归实际上向结果集中添加当前遍历节点的值,因此参数需要当前节点以及一个用于收集结果的数组。
  2. 当节点为空时,我们不需要再进行递归了。
  3. 每次按照先序遍历的顺序,将当前节点值添加到结果中,并继续递归遍历左子节点和右子节点。

至此得到如下代码:

function preorder(node, res) {
  //---终止条件
  if (!node) return;
  //---单次遍历逻辑
  //添加中间节点值--中
  res.push(node.val);
  //递归遍历左子节点--左
  if (node.left) preorder(node.left, res);
  //递归遍历右子节点--右
  if (node.right) preorder(node.right, res);
}
var preorderTraversal = function (root) {
  const res = [];
  preorder(root, res);
  return res;
};

同理,中序和后续遍历只需要改变单次遍历的逻辑即可。

//中序
function inorder(node, res) {
  if (node === null) return;
  inorder(node.left, res);
  res.push(node.val);
  inorder(node.right, res);
}
var inorderTraversal = function (root) {
  const res = [];
  inorder(root, res);
  return res;
};
//后序
function postorder(node, res) {
  if (node === null) return;
  postorder(node.left, res);
  postorder(node.right, res);
  res.push(node.val);
}
var postorderTraversal = function (root) {
  const res = [];
  postorder(root, res);
  return res;
};

迭代

除了使用递归,迭代也可以完成对二叉树的深度优先遍历,不过我们需要借助来完成二叉树的迭代。试想一下,实际递归就借助了调用栈。

这里介绍的迭代方法是,标记法:使用null来标记栈中的节点,代码如下:

var preorderTraversal = function (root) {
  const res = [];
  const stack = [];
  if (!root) return res;
  stack.push(root);
  while (stack.length) {
    const node = stack.pop();//取出栈顶结点
    if (!node) {//如果为空结点则将下一个结点加入结果集
      const resNode = stack.pop();
      res.push(resNode.val);
      continue;
    }
    //中左右,因此入栈顺序为右左中,中间结点后跟随null
    node.right && stack.push(node.right);
    node.left && stack.push(node.left);
    stack.push(node);
    stack.push(null);
  }
  return res;
};

同理,只需要根据不同的遍历顺序来调整如下代码的顺序即可。

    //右
    node.right && stack.push(node.right);
    //左
    node.left && stack.push(node.left);
    //中
    stack.push(node);
    stack.push(null);

广度优先遍历(层序遍历)

LeetCode对应题目:二叉树的层序遍历

在层序遍历时,我们需要借助到队列来帮助我们,其思想是:每次循环中,都将本层的结点出队,下一层的所有结点添加到队列中。

具体代码如下:

var levelOrder = function (root) {
  const quene = [];
  const res = [];
  if (!root) return res;
  quene.push(root);
  while (quene.length) {
    //用于收集本层结点
    const layer = [];
    //本层结点个数
    const size = quene.length;
    //将本层结点中逐个出队并将每个结点的左右子结点入堆
    for (let i = 0; i < size; i++) {
      const node = quene.shift();
      layer.push(node.val);
      node.left && quene.push(node.left);
      node.right && quene.push(node.right);
    }
    //将本层结点添加到结果集
    res.push(layer);
  }
  return res;
};