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

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

[[349809]]

 前言

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

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

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

树的基本概念

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

脑图👇

 

 

 

 

树的基本概念

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

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

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

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

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

 

 

 

 

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

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

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

基本术语

 


树的基本术语

 

 

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

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

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

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

树的种类

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

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

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

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

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

二叉树的概念

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

 

 


二叉树

 

 

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

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

 

 


二叉树的性质

 

 

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

二叉树的遍历

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

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

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

前序遍历

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

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

假设我们mock一下假数据👇

  1. 输入: [1,null,2,3] 
  2.    1 
  3.     \ 
  4.      2 
  5.     / 
  6.    3 
  7. 输出: [1,3,2] 

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

  1. //   TianTianUp 
  2. //   * function TreeNode(val, leftright) { 
  3. //   *     this.val = (val===undefined ? 0 : val) 
  4. //   *     this.left = (left===undefined ? null : left
  5. //   *     this.right = (right===undefined ? null : right
  6. //   * } 
  7. let inorderTraversal  = (root, arr = []) => { 
  8.   if(root) { 
  9.     inorderTraversal(root.left, arr) 
  10.     arr.push(root.value) 
  11.     inorderTraversal(root.right, arr) 
  12.   } 
  13.   return arr 

非递归版本👇

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

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

中序遍历

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

示例:

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

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

递归版本👇

  1. const inorderTraversal  = (root, arr = []) => { 
  2.   if(root) { 
  3.     inorderTraversal(root.left, arr) 
  4.     arr.push(root.val) 
  5.     inorderTraversal(root.right, arr) 
  6.   } 
  7.   return arr 

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

  1. const inorderTraversal = (root, arr = []) => { 
  2.   const stack = [], res = [] 
  3.   let current = root 
  4.   while(current || stack.length > 0) { 
  5.     while (current) { 
  6.       stack.push(current
  7.       current = current.left 
  8.     } 
  9.     current = stack.pop() 
  10.     res.push(current.val) 
  11.     current = current.right 
  12.   } 
  13.   return res 

后续遍历

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

示例:

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

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

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

递归版本👇

  1. const postorderTraversal  = (root, arr = []) => { 
  2.   if(root) { 
  3.     postorderTraversal(root.left, arr) 
  4.     postorderTraversal(root.right, arr) 
  5.     arr.push(root.val) 
  6.   } 
  7.   return arr 

非递归版本👇

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

  1. const postorderTraversal = (root, arr = []) => { 
  2.   const stack = [], res = [] 
  3.   let current = root, last = null  // last指针记录上一个节点 
  4.   while(current || stack.length > 0) { 
  5.     while (current) { 
  6.       stack.push(current
  7.       current = current.left 
  8.     } 
  9.     current = stack[stack.length - 1] 
  10.     if (!current.right || current.right == last) { 
  11.       current = stack.pop() 
  12.       res.push(current.val) 
  13.       last = current 
  14.       current = null              // 继续弹栈 
  15.     } else { 
  16.       current = current.right 
  17.     } 
  18.   } 
  19.   return res 

二叉树的层次遍历 ⭐⭐

链接:二叉树的层序遍历

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

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

  • 3
  • /
  • 9 20 /
  • 15 7

返回其层次遍历结果:

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

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

递归版本👇

  1. const levelOrder = function(root) { 
  2.   if(!root) return [] 
  3.   let res = [] 
  4.   dfs(root, 0, res) 
  5.   return res 
  6.  
  7. function dfs(root, step, res){ 
  8.   if(root){ 
  9.       if(!res[step]) res[step] = [] 
  10.       res[step].push(root.val) 
  11.       dfs(root.left, step + 1, res) 
  12.       dfs(root.right, step + 1, res) 
  13.     } 

非递归版本👇

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

  1. const levelOrder = (root) => { 
  2.   let queue = [], res = [] 
  3.   if (root) queue.push(root); 
  4.   while (queue.length) { 
  5.       let next_queue = [], 
  6.           now_res = [] 
  7.       while (queue.length) { 
  8.           root = queue.shift() 
  9.           now_res.push(root.val) 
  10.           root.left && next_queue.push(root.left
  11.           root.right && next_queue.push(root.right
  12.       } 
  13.       queue = next_queue 
  14.       res.push(now_res) 
  15.   } 
  16.   return res 

题目汇总

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

  • 二叉树的最小深度⭐
  • 二叉树的最大深度⭐
  • 相同的树⭐
  • 二叉搜索树的范围和⭐
  • 对称二叉树⭐
  • 将有序数组转换为二叉搜索树⭐
  • 二叉树的层次遍历 II⭐⭐
  • 二叉树的最近公共祖先⭐⭐
  • 验证二叉搜索树⭐⭐
  • 路径总和 III⭐⭐
  • 存在重复元素 III⭐⭐
  • 计算右侧小于当前元素的个数⭐⭐⭐

 

 

责任编辑:姜华 来源: 前端UpUp
相关推荐

2020-09-23 18:25:40

算法二叉树多叉树

2021-04-01 10:34:18

Java编程数据结构算法

2021-04-28 20:12:27

数据结构创建

2021-03-19 10:25:12

Java数据结构算法

2021-04-19 07:47:42

数据结构二叉树Tree

2021-04-20 08:37:14

数据结构二叉树

2021-03-22 09:00:22

Java数据结构算法

2013-01-30 10:34:02

数据结构

2023-04-06 07:39:48

2020-10-30 09:56:59

Trie树之美

2021-01-07 08:12:47

数据结构二叉树

2021-03-29 10:13:47

Java编程数据结构算法

2013-07-15 16:35:55

二叉树迭代器

2021-09-29 10:19:00

算法平衡二叉树

2020-04-27 07:05:58

二叉树左子树右子树

2021-03-22 08:23:29

LeetCode二叉树节点

2018-03-15 08:31:57

二叉树存储结构

2021-09-15 07:56:32

二叉树层次遍历

2020-12-30 08:35:34

贪心算法监控

2021-09-28 06:28:51

二叉树公共祖先
点赞
收藏

51CTO技术栈公众号