为什么要用平衡二叉树?
因为二叉树没有平衡约束,当输入数据比较有序时,将导致树的高度极端不平衡,最严重的时候退化为一个类似链表的单路径树(比如输入完全有序数组)。
如:输入1,2,3,4。
1
\
2
\
3
\
4
当然,更平衡的代价是插入和删除变的更复杂。
AVL 树
AVL 树本质还是一种二叉树,只不过带有更严格的平衡条件:每个结点的左右子树的高度只差的绝对值(平衡因子)最多为1。
平衡因子 = 左子树高度 – 右子树高度
5
/ \
3 6
/ \ \
2 4 7
/
1
红黑树
红黑树本质上也是一种二叉树,但在每一个结点上增加了一个存储位来表示结点的颜色。一般而言,满足下面性质的树可以称为红黑树:
- 每个结点要么是红的,要么是黑的。
- 根结点是黑的。
- 每个叶结点,即空结点(NIL)是黑的。
- 如果一个结点是红的,那么它的俩个儿子都是黑的。
- 对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。
如下图所示,既是一颗红黑树:
AVL 树高度
AVL 树:最小高度 h=lg(n+1) ,满树,显然,不再证明
AVL 树:最大高度 h=1.44lg(n+2)−0.328 。
证明:
假设 Ni 表示高度为i的 AVL 树的最小结点个数。
- N0=0 (AVL 树高度为0,空树)
- N1=1 (AVL 树高度为1,只有一个根结点)
N2=2 (AVL 树高度为2,只有一个根结点和一个叶子结点)
2 / 1
N3=4 (AVL 树高度为3,根节点的一个子树(不妨设为左子树)为 N2 树,另一个子树(不妨设为右子树)为 N1 树)
3 / \ 2 4 / 1
N4=7 (AVL 树高度为3,根节点的一个子树(不妨设为左子树)为 N3 树,另一个子树(不妨设为右子树)为 N2 树)
5 / \ 3 6 / \ \ 2 4 7 / 1
综上, Nn=Nn−1+Nn−2+1 。
令 Nn=Fn+2−1 ,
所以: Fn+2=Fn+1+Fn
其中, F2=1 , F3=2 ,如果令 F0=0 , F1=1 ,并不会改变数列中的其他值,通过这种转换,此数列变为典型的 Fibonacci 数列,可求其通项公式:
Fn=ϕn5√−15√ϕn
Nn=ϕn+25√−15√ϕn+2−1
其中 ϕ=5√+12 。
事实上,当n较大时,上述式子的第二项相对第一项较小,可以忽略,简写为:
Fn=ϕn5√
Nn=ϕn+25√−1
但考虑到求的是 Nn 固定时,n 的最大值,也就是求 Fn 的下限,而 −1<15√ϕn+2<1 ,所以上述式子又可写为:
Fn=ϕn5√−1
Nn=ϕn+25√−2
所以,有 Nn 个结点的 AVL 树的最大高度为:
n=logϕ(5√(Nn+2))−2=log2(5√(Nn+2))log2(ϕ)−2=logϕ2∗log2(5√(Nn+2))−2=1.44∗log2(Nn+2)−0.328
结论参考《计算机程序设计》第3卷 6.2.3 以及《维基百科:AVT Tree》
Fibonacci 数列通项的推导过程可参考《编程之美》2.9节,或自行百度。
红黑树高度
红黑树:最小高度 h=lg(n+1) ,满树,显然,不再证明。 红黑树:最大高度 h=2lg(n+1) 。
证明:
可参考《算法导论》第13章-红黑树的证明方法 或者参考《麻省理工-算法导论》公开课中的证明,将红黑树转换为2-3-4树,通过2-3-4树的特性间接证明。
一颗大小为N的红黑树中,根节点到任意结点的平均路径长度约为:
1.001lg(N)
参考《Algorithms》第4版3.3.2
AVL 树和红黑树高度
AVL 树的高度范围为:
[lg(n+1),1.44∗lg(n+2)−0.328]
红黑树的高度范围为:
[lg(n+1),2∗lg(n+1)]
如果但从此看,AVL 树更适合查找,效率更高。
AVL 树插入
- 查找插入位置,复杂度为: O(lg(n))
- 追踪不平衡位置,最坏情况从叶子结点到根结点,复杂度为: O(lg(n))
- 最后经过一到两次旋转完成插入操作,复杂度为: O(lg(1))
但经验测试表明,除非 n 非常小,否则插入第 n 项所需要的查找次数平均为: 1.01∗lg(n)+0.1
参考《计算机程序设计》第3卷 6.2.3 以及《维基百科:AVT Tree》
AVL 树删除
- 查找删除位置,复杂度为: O(lg(n))
- 追踪不平衡位置,最坏情况从叶子结点到根结点,复杂度为: O(lg(n))
- 最后至多需要 O(lg(n)) 次旋转,因为要沿父节点一路调整,直到根结点。
参考《计算机程序设计》第3卷 6.2.3
红黑树插入
- 查找插入位置,复杂度为: O(lg(n))
- 经过一到两次旋转完成插入操作,复杂度为: O(lg(1))
- 插入操作影响了树的颜色,可以对其重新着色,做少量 O(lg(n)) 调整(重新着色的效率非常高)。
红黑树删除
- 查找删除位置,复杂度为: O(lg(n))
- 经过不超过三次旋转完成删除操作,复杂度为: O(lg(1))
- 删除和旋转操作影响了树的颜色,可以对其重新着色,做少量 O(lg(n)) 调整。
从此也可以看出,在删除操作中,AVL 树可能需要比红黑树更多的旋转操作。
AVL 树与红黑树区别
AVL 树是严格平衡二叉树,红黑树是非严格平衡二叉树,平衡条件比 AVL 树弱,它追求的是局部的平衡,而 AVL 树追求的是全局的平衡。
AVL 树用平衡因子来标识结点的高度,红黑树用颜色来标识。 空间占用方面:红黑树只需多余的1bit额外空间,而 AVL 树至少需要2bits。
但是考虑到内存的对齐问题,很多时候都额外分配4Bytes空间。
应用
在 C++ STL中,set、multiset和map、multiset都是用红黑树实现的。另外,linux的内核中也使用了红黑树。
结论
当输入数据完全随机时,不平衡二叉树有更大的优势;当输入数据比较随机(偶尔有序)时,红黑树更有优势,因为需要更少的平衡保持操作;当输入数据比较有序时,普通二叉树速度最慢,因为需要较多的查询操作(树的高度值最大),而 AVL 树性能最好,因为树的高度最低,需要更少的查询,其次是红黑树。
另外,使用父结点能带来更好的效率,因为可以更快的查询父节点,而不需遍历。当然,每个结点也需要额外的一个指针空间。
可参考《Performance Analysis of BSTs in System Software》中的实验。
AVL 树和红黑树的查找,插入和删除的复杂度都是 O(lg(n)) ,但是这个 O(lg(n)) 也是有差距的。网上很多说法是: AVL 树的删除操作复杂度为 O(lg(n)) ,而红黑树是 O(1) ,主要考虑的就是肯能进行的旋转操作的次数,从这方面考虑是对的,最也是红黑树可能性能更好的一个主要方面,但是不能忽略删除之前所需要的查找的复杂度 O(lg(n)) 。
一个不完全准确的说法:红黑树的插入删除更快,但是查询可能会稍慢。所以,如果经常插入删除的化还是使用红黑树,这样的情况也比较多;而偶尔进行插入删除,经常进行查询操作,可能 AVL 树性能更优。
补充
对于 AVL 树和红黑树的插入删除操作没有做更多介绍,因为太复杂,外加能力有限(其实很多我也不懂)。另外,很多涉及统计特性的内容也未详细探讨,有兴趣可参考下列文献。
对于 AVL 树的更多数学内容可参考《计算机程序设计》第3卷 6.2.3
对于红黑树的更多详细内容可参考《Algorithms》第4版3.3.2
对于 AVL 树和红黑树的对比可参考《Performance Analysis of BSTs in System Software》中的大量实验
另外,对于红黑树也可参考《Left-Leaning Red-Black Trees》
文中可能存在诸多错误,望多多指正。