带你从零开始手撕二叉树

185 阅读4分钟

什么是树

基础概念

在数据结构中,树(Tree)是一种非线性的数据结构,它是由n(n>=0)个有限节点组成一个具有层次关系的集合。对于n>0时,这个集合满足以下条件:

  • 有且仅有一个特定的称为根(root)的节点。
  • 当n>1时,其余节点可分为m(m>0)个互不相交的有限集T1, T2, ..., Tm,其中每一个集合本身又是一棵树,并且称为根的子树(subtree)。

常见术语

  • 节点(Node):树的基本组成部分,包含一个数据元素和若干指向其他节点的链接。

  • 根节点(Root):树中的最顶端节点,没有父节点。

  • 子节点(Child):除了根节点外,任何节点都是另一个节点的子节点。

  • 父节点(Parent):给定节点的上一级节点,每个节点(除根节点外)有且仅有一个父节点。

  • 兄弟节点(Sibling):具有同一父节点的节点。

  • 叶节点/终端节点(Leaf):没有任何子节点的节点。

  • 内部节点(Internal Node):非叶节点,至少有一个子节点。

  • 路径(Path):从一个节点到另一个节点的序列,由边连接。

  • 深度(Depth):从根节点到某节点的唯一路径上的边数。

  • 高度(Height):从节点到其最远叶节点的最长路径上的边数。对于整个树来说,树的高度是从根节点到最远叶节点的最长路径上的边数。

  • 祖先(Ancestor):从根节点到该节点路径上的所有节点。

  • 子孙(Descendant):以某节点为根的子树中的所有节点。

  • 森林(Forest):零个或多个不相交的树的集合。删除一棵非空树的根会得到一个森林。

  • 度(Degree):一个节点的子节点数量

二叉树概念

二叉树是一种每个节点最多有两个子节点的树形数据结构,这两个子节点分别称为左子节点和右子节点。

由于二叉树的定义是递归的,所以二叉树可以为空(没有节点),或者是由一个根节点加上两棵互不相交的、分别称为左子树和右子树的二叉树组成。

二叉树结构

为了方便下面的遍历,笔者先在这里定义一个二叉树

const root = {
  val:'A',
  left:{
    val:'B',
    left:{
      val:'D'
    },
    right:{
      val:'E'
    }
  },
  right:{
    val:'C',
    right:{
      val:'F'
    }
  }
}
eca493155012ab9e22cde33d56e76837.png

为了方便大家能看懂,笔者在这就不构造过于繁琐的二叉树,下面二叉树的图会稍微复杂点让大家更好的理解二叉树的遍历。

遍历二叉树

深度优先搜索

深度优先搜索(DFS)是一种遍历或搜索树或图的算法,它尽可能深地探索每个分支,直到达到搜索结构的末端然后回溯。

前序遍历
概念

前序遍历:先访问根节点,如何遍历左子树,最后遍历右子树,简单来说就是访问完根节点后递归的对左子树进行前序遍历,再递归的对右子树进行前序遍历。

图解
image.png

如上图所示,这个二叉树前序遍历的结果为ABDGHCEIF

代码
function preOrder(root){
  // 退出条件
  // 空树
  if(!root){
    return
  }
  // 递归式
  console.log(root.val);
  preOrder(root.left)
  preOrder(root.right)
}
eca493155012ab9e22cde33d56e76837.png

image.png

中序遍历
概念

中序遍历:先遍历左子树,然后访问根节点,最后再遍历右子树,简单来说,就是先递归的对左子树进行中序遍历,再访问根节点,最后再递归的对右子树进行中序遍历

图解
image.png

如上图所示,中序遍历的结果为GDHBAEICF

代码
function inOrder(root){
  // 退出条件
  // 空树
  if(!root){
    return
  }

  // 递归式
  inOrder(root.left)
  console.log(root.val);
  inOrder(root.right)
}
eca493155012ab9e22cde33d56e76837.png

image.png

后序遍历
概念

后序遍历:先遍历左子树,再遍历右子树,最后访问根节点,简单来说就是先递归的对左子树进行后序遍历,再递归的对右子树进行后序遍历,最后再访问根节点

图解
image.png

如上图所示,后序遍历的结果为GHDBIEFCA。

代码
function postOrder(root){
  // 退出条件
  // 空树
  if(!root){
    return
  }

  // 递归式
  postOrder(root.left)
  postOrder(root.right)
  console.log(root.val);
}
eca493155012ab9e22cde33d56e76837.png

image.png

广度优先搜索

广度优先搜索(BFS)是一种逐层扩展的算法,它从根节点开始,首先访问其所有邻接节点,然后再按同样方式访问下一层级的所有节点。

层序遍历
概念

层序遍历(Level-order Traversal)是按树的层级从上到下、同一层级从左到右依次访问节点的过程。使用队列实现,先访问根节点,然后逐层访问每个节点的左右子节点。

图解
image.png

如上图所示,层序遍历的结果为ABCDEFGHI

代码
class TreeNode{
  constructor(val){
    this.val=val;
    this.left=this.right=null;
  }
}

const root = new TreeNode("A");
root.left = new TreeNode("B");
root.right = new TreeNode("C");
root.left.left = new TreeNode("D");
root.left.right = new TreeNode("E");
root.right.right = new TreeNode("F");

console.log(root)
function levelOrderTraversal(root){
  // 
  if(root ===null)return []
  const result = []
  // 根节点入队
  // 借助队列 先进先出 FIFO 栈 LIFO
  const queue = [root]  
  while(queue.length){ // 不为空
    const currentNode = queue.shift() // 队头出队
    result.push(currentNode.val)
    if(currentNode.left!==null){
      queue.push(currentNode.left)
    }
    if(currentNode.right!==null)
      queue.push(currentNode.right)
  }
  return result
}
console.log(levelOrderTraversal(root))
eca493155012ab9e22cde33d56e76837.png image.png c224676594aae6804009e3fe521b122.jpg