数据结构与算法之 —— 树🌲

565 阅读4分钟

0x01 基础知识

树 VS 子树

之前我们一直在谈的是一对一的线性结构,可现实中,还有很多一对多的情况需要处理,所以我们需要研究这种一对多的数据结构——“树”

树的定义
树(Tree)是n(n≥0)个结点的有限集。n=0时称为空树。
在任意一棵非空树中:
a. 有且仅有一个特定的称为根(Root)的结点;
b. 当 n>1时,其余结点可分为 m(m>0)个互不相交的有限集 T1、T2、……、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)

需要强调的是:

  1. n>0时根结点是唯一的,不可能存在多个根结点

  2. m>0时,子树的个数没有限制,但它们一定是互不相交的。

结点分类

树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树数称为结点的度(Degree)。

1. 叶结点: 度为0的结点, 也叫终端结点
2. 分支结点: 度不为0的结点, 也叫非终端结点
a. 根结点
b. 内部结点
3. 树的度: 树内各结点的度的最大值

结点间的关系

孩子 VS 双亲 VS 兄弟 VS 祖先VS 子孙

1. 结点的子树的根结点, 相应地,该结点称为孩子的双亲
2. 同一个双亲的孩子之间互称兄弟
3. 结点的祖先是从根到该结点所经分支上的所有结点
4. 以某结点为根的子树中的任一结点都称为该结点的子孙

结点的层次 VS 树的高度(也叫深度)

结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。

树中结点的最大层次称为树的深度(Depth)或高度

0x02 树的存储结构

双亲表示法(顺序存储结构)

用一组连续的存储空间来存储树的结点,同时在每个结点中附加一个指示器(整数域) ,用以指示双亲结点的位置(下标值) 。

“其中data是数据域,存储结点的数据信息。而parent是指针域,存储该结点的双亲在数组中的下标。”

这样的存储结构,我们可以根据结点的 parent 指针很容易找到它的双亲结点,所用的时间复杂度为O(1),直到parent为-1时,表示找到了树结点的根。但是,求结点的子结点时需要扫描整个数组。这也是这种存储结构的优缺点。

孩子表示法

树中每个结点有多个指针域,每个指针指向其一棵子树的根结点。

方案一

指针域的个数就等于树的度

这种方法对于树中各结点的度相差很大时,显然是很浪费空间的,因为有很多的结点,它的指针域都是空的。

方案二

第二种方案每个结点指针域的个数等于该结点的度,我们专门取一个位置来存储结点指针域的个数

这种方法克服了浪费空间的缺点,对空间利用率是很高了,但是由于各个结点的链表是不相同的结构,加上要维护结点的度的数值,在运算上就会带来时间上的损耗。

方案三

对于树中的每个结点,其孩子结点用带头结点的单链表表示

表结点和头结点的结构如下图所示:

这样的结构对于我们要查找某个结点的某个孩子,或者找某个结点的兄弟,只需要查找这个结点的孩子单链表即可。对于遍历整棵树也是很方便的,对头结点的数组循环即可。

孩子兄弟表示法(二叉树表示法)

两个指针域:分别指向结点的第一个子结点和下一个兄弟结点,如下图所示:

0x03 树的应用

虚拟DOM树

我们知道,所谓的 Virtual DOM 算法,主要包括以下几个步骤:

  1. 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中 github.com/vuejs/vue/b…

  2. 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异 - diff

  3. 把步骤2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了 - patch