update notes on splay, avl and btree.

This commit is contained in:
Shine wOng
2019-12-16 13:51:53 +08:00
parent 8b184cd95f
commit 0c26714e03

View File

@@ -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%。树高也不至于明显增加,根据上面分析,树高主要取决于关键码的总数,而与节点数量几乎没有关系。