「算法与数据结构」二叉树之美

10,539 阅读10分钟

前言

这次梳理的内容是数据结构专题中的,如果你看到这类数据结构时,满脑子头疼,觉得它很难理解,如果是这样子的话,那么本文可能对你或许有点帮助。

俗话说得好,要想掌握理解的话,我们得先了解它的概念,性质等内容。

围绕以下几个点来展开介绍树👇

  • 树的基本概念
  • 基本术语
  • 树的种类
  • 二叉树概念
  • 二叉树的遍历
  • 二叉树题目汇总

公众号前端UpUp,回复二叉树,即可获取脑图。

联系👉TianTianUp,遇到问题的话,可以联系作者噢,愿意陪你一起学习一起探讨问题。

脑图👇


树的基本概念

树是用来模拟具有树状结构性质的数据集合。或者你可以把它认为是一种抽象数据结构或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。

那么根据维基百科给出的定义,我们似乎可以这么理解:

它是由n(n>0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

  • 每个节点都只有有限个子节点或无子节点;
  • 没有父节点的节点称为根节点;
  • 每一个非根节点有且只有一个父节点;
  • 除了根节点外,每个子节点可以分为多个不相交的子树;
  • 树里面没有环路(cycle)

这个时候,我们就需要拿出一张图来看👇

树的概念

从图中来看,以上的五个特点都可以很好的总结出来

  • A节点作为根节点,没有父节点,所以是根节点。
  • 除根节点(A)外,其他的节点都有父节点,并且每个节点只有有限个子节点或无子节点。
  • 从某个节点开始,可以分为很多个子树,举个例子,从B节点开始,即是如此。

既然对树有一定认识后,我们需要了解它的一些术语。

基本术语

树的基本术语

为了更加规范的总结,这里给出的描述来自于维基百科:

  1. 节点的度:一个节点含有的子树的个数称为该节点的度;
  2. 树的度:一棵树中,最大的节点度称为树的度;
  3. 叶节点终端节点:度为零的节点;
  4. 非终端节点分支节点:度不为零的节点;
  5. 父亲节点父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
  6. 孩子节点子节点:一个节点含有的子树的根节点称为该节点的子节点;
  7. 兄弟节点:具有相同父节点的节点互称为兄弟节点;
  8. 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  9. 深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
  10. 高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
  11. 堂兄弟节点:父节点在同一层的节点互为堂兄弟;
  12. 节点的祖先:从根到该节点所经分支上的所有节点;
  13. 子孙:以某节点为根的子树中任一节点都称为该节点的子孙;
  14. 森林:由m(m>=0)棵互不相交的树的集合称为森林。

可以结合上述的图来理解这些概念,通过两者的结合,你一定会对树有进一步的了解的。

有以上基本概念,以及一些专业术语的掌握,接下来我们需要对树进行一个分类,看看树有哪些种类。


树的种类

理解了树的概念以及基本术语,接下来,我们需要拓展的内容就是树的种类。

我们可以根据维基百科的依据来作为分类的标准👇

  • 无序树:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树
  • 有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树;
    • 二叉树:每个节点最多含有两个子树的树称为二叉树;
      • 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树;
        • 满二叉树:所有叶节点都在最底层的完全二叉树;
      • 平衡二叉树AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树;
      • 排序二叉树(英语:Binary Search Tree)):也称二叉搜索树、有序二叉树;
    • 霍夫曼树带权路径最短的二叉树称为哈夫曼树或最优二叉树;
    • B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。

既然树的分类有这么多的话,那么我们是不是都需要一一掌握呢,我个人觉得,掌握二叉树这种结构就足够了,它也是树最简单、应用最广泛的种类。

那么接下来,我们就来介绍一下二叉树吧。


二叉树的概念

二叉树是一种典型的树树状结构。如它名字所描述的那样,二叉树是每个节点最多有两个子树的树结构,通常子树被称作“左子树”和“右子树”。

二叉树

图片来自网络,具体出处不明。

从这个图片的内容来看,应该很清楚的展示了二叉树的结构。

至于二叉树的性质的话,可以参考下图,作为补充知识吧,个人觉得这个不是重点。

二叉树的性质

重点的话,我们需要掌握的应该是它的遍历方式。

二叉树的遍历

我们知道对于二叉树的遍历而言,有常见得三种遍历方式,分别是以下三种:

  • 前序遍历
  • 中序遍历
  • 后序遍历

对于任何一种遍历方式而言,我们不仅需要掌握它的非递归版本,同时对于它的递归版本来说,更是考察一个人的算法基本功,那么接下来,我们来看看吧。

前序遍历

点击这里,练习二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

假设我们mock一下假数据👇

输入: [1,null,2,3]
   1
    \
     2
    /
   3
输出: [1,3,2]

那么根据我们对前序遍历的理解,我们可以写出解题伪代码👇

//   TianTianUp
//   * function TreeNode(val, left, right) {
//   *     this.val = (val===undefined ? 0 : val)
//   *     this.left = (left===undefined ? null : left)
//   *     this.right = (right===undefined ? null : right)
//   * }
let preorderTraversal  = (root, arr = []) => {
    if(root) {
      arr.push(root.val)
      preorderTraversal(root.left, arr)
      preorderTraversal(root.right, arr)
    }
    return arr
  }

非递归版本👇

对于非递归的话,我们需要借助一个数据结构去存储它的节点,需要使用的就是栈,它的思路可以借鉴👇

  • 根节点为目标节点,开始向它子节点遍历
  • 1.访问目标节点
  • 2.左孩子入栈 -> 直至左孩子为空的节点
  • 3.节点出栈,以右孩子为目标节点,再依次执行1、2、3
  let preorderTraversal = (root, arr = []) => {
    const stack = [], res = []
    let current = root
    while(current || stack.length > 0) {
      while (current) {
        res.push(current.val)
        stack.push(current)
        current = current.left
      }
      current = stack.pop()
      current = current.right
    }
    return res
  }

中序遍历

给定一个二叉树,返回它的中序 遍历。

示例:

输入: [1,null,2,3] 1
2 / 3

输出: [1,3,2]

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/bi… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


递归版本👇

const inorderTraversal  = (root, arr = []) => {
  if(root) {
    inorderTraversal(root.left, arr)
    arr.push(root.val)
    inorderTraversal(root.right, arr)
  }
  return arr
}

非递归版本,这里就不解释了,跟前序遍历一样,思路一样,用栈维护节点信息。

const inorderTraversal = (root, arr = []) => {
  const stack = [], res = []
  let current = root
  while(current || stack.length > 0) {
    while (current) {
      stack.push(current)
      current = current.left
    }
    current = stack.pop()
    res.push(current.val)
    current = current.right
  }
  return res
}

后续遍历

给定一个二叉树,返回它的 后序 遍历。

示例:

输入: [1,null,2,3]
1
2 / 3

输出: [3,2,1]

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/bi… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


递归版本👇

const postorderTraversal  = (root, arr = []) => {
  if(root) {
    postorderTraversal(root.left, arr)
    postorderTraversal(root.right, arr)
    arr.push(root.val)
  }
  return arr
}

非递归版本👇

其实,嗯,做完前面两个后,会发现都是有套路滴~

const postorderTraversal = (root, arr = []) => {
  const stack = [], res = []
  let current = root, last = null  // last指针记录上一个节点
  while(current || stack.length > 0) {
    while (current) {
      stack.push(current)
      current = current.left
    }
    current = stack[stack.length - 1]
    if (!current.right || current.right == last) {
      current = stack.pop()
      res.push(current.val)
      last = current
      current = null              // 继续弹栈
    } else {
      current = current.right
    }
  }
  return res
}

二叉树的层次遍历 ⭐⭐

链接:二叉树的层序遍历

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

示例: 二叉树:[3,9,20,null,null,15,7],

3

/
9 20 /
15 7

返回其层次遍历结果:

[ [3], [9,20], [15,7] ]

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/bi… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


递归版本👇

const levelOrder = function(root) {
  if(!root) return []
  let res = []
  dfs(root, 0, res)
  return res
}

function dfs(root, step, res){
  if(root){
      if(!res[step]) res[step] = []
      res[step].push(root.val)
      dfs(root.left, step + 1, res)
      dfs(root.right, step + 1, res)
    }
}

非递归版本👇

这里借助的就是队列这个数据结构,先进先出的机制。

const levelOrder = (root) => {
  let queue = [], res = []
  if (root) queue.push(root);
  while (queue.length) {
      let next_queue = [],
          now_res = []
      while (queue.length) {
          root = queue.shift()
          now_res.push(root.val)
          root.left && next_queue.push(root.left)
          root.right && next_queue.push(root.right)
      }
      queue = next_queue
      res.push(now_res)
  }
  return res
}

题目汇总

还是那句话,题目做不完的,剩下的就靠刷leetcode了,我还准备了一些常见的二叉树题集,题目的质量还是不错的👇

❤️ 感谢大家

如果你觉得这篇内容对你挺有有帮助的话:

  1. 点赞支持下吧,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)

  2. 关注公众号前端UpUp,联系作者👉 DayDay2021 ,我们一起学习一起进步。

  3. 觉得不错的话,也可以阅读TianTian近期梳理的文章(感谢掘友的鼓励与支持🌹🌹🌹):