二叉树的遍历【先序,中序,后序,层级】(详解)

摘要

关于二叉树的遍历也是很常见的问题,而最常用的遍历也是标题中的说的四种方式。
先序,中序和后序可以采用递归和迭代的方式来完成,也是深度优先的思想,后面会写出递归和迭代的方法。
层级遍历主要是借用队列这种数据结构来进行对二叉树逐层遍历,是广度优先的思想。
现在我们来写一下每一种的遍历方法。

1.中序遍历

先说一下中序遍历的方式是什么。对于二叉树的每个节点,从根节点开始,都要先遍历当前节点的左子节点,再遍历当前节点,然后是当前节点的右子节点。

简单来说就是左子树 -> 根节点 -> 右子树

递归的方法很简单,这里面我们不详细的去说,直接上代码。

const logSort1 = function (arr, node) {
    
    
  if (node != null) {
    
    
    logSort1(arr, node.left)
    arr.push(node.val)
    logSort1(arr, node.right)
  }
}

如果我们想用迭代的方法来做,就必须要有一个栈,因为递归的本质上其实维护了一个内部的栈,所以我们想要用循环的方式也需要自己创建一个栈。

第一步:

由于遍历的方式是先左子树,所以我们可以从根节点到下面的每个左子节点,一直入栈。
在这里插入图片描述

第二步:

出栈,出栈的时候,我们可以将出栈的节点放到结果集中了,然后拿到出栈节点的右子节点,以这个右子节点为根节点重复第一步。下图是2这个节点出栈后的情况,后序的情况可以自己继续推。
在这里插入图片描述

第三步:

直到栈已经空了并且出栈的最后一个节点已经没有右子节点了,我们可以说遍历已经结束了。

2.先序遍历

先序遍历的方式,对于二叉树的所有节点,是先遍历当前节点,再遍历当前节点的左子节点,最后再遍历当前节点的右子节点。

总结来说:根节点 -> 左子树 -> 右子树

同样,递归的方式我们不多说,直接上代码。

const logSort3 = function (arr, node) {
    
    
  if (node != null) {
    
    
    arr.push(node.val);
    logSort3(arr, node.left);
    logSort3(arr, node.right);
  }
}

我们主要来说一下迭代的方式,同样的,只要我们用循环不用递归,那么我们必须要先维护一个自己的栈。

第一步:

由于是先根节点,再左子节点,我们依旧从根节点,依次到每个左子节点进行入栈,不过,记住刚才中序是在出栈的时候入结果集,但是这次我们根节点的优先级大于左子节点,所以我们入栈的时候就要把节点入结果集。
在这里插入图片描述

第二步:

出栈,这次我们出栈的节点我们不入结果集里面,但是我门出栈节点的右子节点,要重复第一步,下图是2节点出栈的情况。
在这里插入图片描述
第三步:

直到栈为空并且最后出栈的节点不包含右子节点,我们才能认为遍历已经结束。

const logSort4 = function (root) {
    
    
  let stack = [], res = [];
  while (stack.length > 0 || root) {
    
    
    while (root != null) {
    
    
      res.push(root.val)
      stack.push(root)
      root = root.left;
    }
    let node = stack.pop();
    root = node.right
  }
  return res;
}

3.后序遍历

后序遍历的情况就是,对于二叉树的所有节点,先遍历当前节点的左子节点,然后是当前节点,最后是当前节点的右子节点。
总结来说:左子树 -> 根节点 -> 右子树

递归的方法直接上代码,重点还是在迭代的方法:

const logSort5 = function (arr, node) {
    
    
  if (node != null) {
    
    
    logSort5(arr, node.left);
    logSort5(arr, node.right);
    arr.push(node.val);
  }
}

有了前两种遍历的讲解,我们一定知道我们首先要维护一个栈了,那么后序遍历的情况我们要怎么用循环来解决呢?

第一步:

我们一定要记住优先级,这种遍历方式的优先级就是left > right > root。
既然左子树的优先级最高,我们依旧从根节点,到每个左子节点进行入栈,由于根节点的优先级最低,所以我们不能在入栈的时候进行入结果集的操作。
在这里插入图片描述

第二步:

出栈的情况有点复杂,我们想一下:

如果出栈节点没有右子节点(不考虑左子节点的情况,因为是以左子节点入栈),或者右子节点已经入了结果集。那么是不是就能说明出栈节点已经变成了优先级最高的情况(或者说是一个不存在左右节点的根节点),这个时候我们就可以将该节点入结果集。

如果出栈节点有右子节点,那么说明出栈节点的优先级一定没有右子节点高,那么我们只能禁止这个出栈节点出栈并且将优先级比他高的右子节点入栈,并且要以这个右子节点去重复第一步

这里有一点难以理解,但只要记住优先级的顺序,并且记住我是以左子节点的顺序入栈的,就是可以想通的。

下图是2节点要出栈的情况,没出去憋回去了哈。
在这里插入图片描述

第三步:

直到这个栈已经为空并且出栈的节点没有右子节点了,我们可以说遍历已经结束了。

const logSort6 = function (root) {
    
    
  let stack = [], res = [], pre = null
  while (stack.length > 0 || root) {
    
    
    while (root != null) {
    
    
      stack.push(root)
      root = root.left;
    }
    root = stack.pop();
    if (root.right === null || pre === root.right) {
    
    
      res.push(root.val)
      pre = root;
      root = null
    } else {
    
    
      stack.push(root)
      root = root.right
    }

  }
  return res
}

4.层级遍历

关于层级遍历,也是通过队列这种数据结构,先进先出,通过广度优先的方式来实现的。

第一步: 我们首先将根节点放入队列中。
在这里插入图片描述

第二步:我们将队列的节点全部出队列,入结果集,并且每个节点出队列的时候,都将该节点的左子节点和右子节点入队列。

在这里插入图片描述
第三步:重复第二步,直到队列已经为空,我们可以说遍历已经结束了。

这里面注意一下,我们层级遍历可以记录层数的奥。

const logSort7 = function (root) {
    
    
  let queue = [root], res = [];
  while (queue.length > 0) {
    
    
    let len = queue.length;
    let arr = [];
    for (var i = 0; i < len; i++) {
    
    
      let node = queue.shift();
      arr.push(node.val)
      if (node.left) {
    
    
        queue.push(node.left)
      }
      if (node.right) {
    
    
        queue.push(node.right)
      }
    }
    res.push(arr)
  }
  return res
}

猜你喜欢

转载自blog.csdn.net/weixin_46726346/article/details/120324098