0x01 基础知识
树 VS 子树
之前我们一直在谈的是一对一的线性结构,可现实中,还有很多一对多的情况需要处理,所以我们需要研究这种一对多的数据结构——“树”
树的定义
树(Tree)是n(n≥0)个结点的有限集。n=0时称为空树。
在任意一棵非空树中:
a. 有且仅有一个特定的称为根(Root)的结点;
b. 当 n>1时,其余结点可分为 m(m>0)个互不相交的有限集 T1、T2、……、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)
需要强调的是:
-
n>0时根结点是唯一的,不可能存在多个根结点
-
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 算法,主要包括以下几个步骤:
-
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中 github.com/vuejs/vue/b…
-
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异 - diff
-
把步骤2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了 - patch