From 0c26714e032ed4b331f382ea14cfa3a63054af2d Mon Sep 17 00:00:00 2001 From: Shine wOng <1551885@tongji.edu.cn> Date: Mon, 16 Dec 2019 13:51:53 +0800 Subject: [PATCH] update notes on splay, avl and btree. --- thu_dsa/chp7/notes.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/thu_dsa/chp7/notes.md b/thu_dsa/chp7/notes.md index 9a68635..0d7440f 100644 --- a/thu_dsa/chp7/notes.md +++ b/thu_dsa/chp7/notes.md @@ -5,6 +5,22 @@ > Splay树基本操作的分摊时间复杂度为`O(logn)`,如何证明这个结论? +对于任意一棵伸展树`S`,引入势能函数$\Phi(S) = \Sigma_{v \in S} rank(v)$,其中$rank(v) = log(size(v))$,即以`v`为树根的子树的规模的对数。通过简单的分析可以看到,`S`越平衡,则它的势能越小,`S`越失衡,则它的势能越大。特殊地,当`S`是一条单链的时候,$\Phi(S) = O(nlogn)$;而`S`是一棵满二叉树的时候,$\Phi(S) = O(n)$。 + +将第`i`次操作的分摊时间复杂度$A_i$,写作实际的时间复杂度与势能变化量之和,即 + +$$ +A_i = T_i + \Delta\Phi +$$ + +然后就伸展树当中存在的三种元操作,即`zig`,`zigzig`以及`zigzag`(其余的操作无非是这三个操作的对称情况),分别证明满足 + +$$ +A_i \le 3\cdot [rank_i(v) - rank_{i - 1}(v)] +$$ + +这样,对于伸展树中一次基本操作(如`search`),其作用无非是将节点`v`逐层伸展到了根节点,因此分摊时间复杂度为$A \le 1 + 3\cdot[rank(r) - rank(v)] = O(logn)$。 + ## AVL树 > 高度为h的AVL树,至少拥有`fib(h + 3) - 1`个节点,此时对应了一棵`fib-AVL`数。 @@ -43,4 +59,26 @@ ## B树 +关于B树,首先需要明确高度的定义,乃是加上了外部节点后的树的高度。其次是说这里有两种统计口径,即节点数量和关键码数量。节点数量是指超级节点的数量,而关键码则是指一个数据的关键码,两者的关系是:一个超级节点中含有多个关键码。 +> 对于含有`N`个关键码的B树,其高度`h`的上下限。 + +$$ +log_m^{N + 1} \le h \le log_{\lceil{\frac{m}{2}}\rceil}^{\lfloor\frac{N + 1}{2}\rfloor} + 1 +$$ + +这个结论的推导要会。需要注意的是,推导的过程,是通过高度为`h`的B树,所含有最多节点数量和最少节点数量,来得到`h`应该满足的关系的。为了将推导过程中使用的节点数量,转化为关键码数量,利用了B树外部节点的性质——外部节点的数量等于关键码的数量加一。实际上,这个性质的本质是二叉树二度节点数量与零度节点数量的关系。 + +> 关于B树插入和删除过程中的分裂和合并次数 + +考虑一次分裂操作,有两种情况,即在根节点分裂以及在非根节点分裂。如果是在非根节点分裂,一次分裂后B树的高度`h`不变,但是节点数量加一;如果是在根节点分裂,分裂后B树的高度`h`加一,节点数量加二。无论如何,一次分裂以后,`n - h`的值都会增加一,故可以之作为B树分裂次数的计数器。 + +同样地可以证明,一次B树的合并操作后,`n - h`的值将会减少一。 + +因此,如果对一棵初始状态`h = 1`的B树连续插入`N`个关键码,最终得到一棵高度为`h`,节点数量为`n`的B树,则累计的的分裂次数为`n - h`,平均每次插入的分裂次数为$\frac{n - h}{N} \le \frac{n - h}{n(\lceil \frac{m}{2} \rceil - 1)} = \frac{1}{\lceil \frac{m}{2} \rceil - 1}$。因此,平均$\lceil \frac{m}{2} \rceil - 1$次插入操作才会导致一次分裂。对于删除与合并操作,也有同样的结论。 + +尽管如此,单次的插入和删除操作,至多仍需要`O(logn)`次分裂或合并。在最坏情况下,对于任意的`m`为奇数,都可以构造一棵B树,交替地对它进行插入和删除操作,每次操作都需要`O(logn)`次分裂或者合并调整。 + +> 为什么B树的插入不采用旋转策略,而是统一采用分裂策略? + +分裂策略总是可行,程序逻辑简单。根据上面的分析,分裂操作通常不会频繁发生。空间利用率也不至于显著降低,因为至少有50%。树高也不至于明显增加,根据上面分析,树高主要取决于关键码的总数,而与节点数量几乎没有关系。