平衡二叉树和红黑树最差情况分析
1.经典平衡二叉树
平衡二叉树(又称AVL树)是带有平衡条件的二叉查找树,使用最多的定理为:一棵平衡二叉树是其每个节点的左子树和右子树的高度最多差为1的二叉查找树。因为他是二叉树的一种具体应用,所以他同样具有二叉树的性质。例如,一棵满二叉树在第k层最多可拥有个节点(性质1)。一棵树的高度为其从根节点到最底层节点经过的路径数(例如只含一个节点的树的高度为0)(性质2)。并且已被证明,一棵含有N个节点的平衡二叉树的高度最多(粗略来说)为。下面我们来尝试总结如何得到一个高度差最大的平衡二叉树(查找性能最差)。
由平衡二叉树的定义可知,左子树和右子树最多可以相差1层高度,那么多个在同一层的子树就可以依次以相差1层的方式来递减子树的高度,如下图所示是一个拥有4棵子树的树的层高最大差情形(图1):
图1 拥有4颗子树的平衡二叉树最大高度差
该图虚线框中的子树,最左端的节点树高度为0,最右端的节点树的高度为2,因此该平衡二叉树的内部子树最大高度差为2。
利用这样的性质,我们就可以依次递推,1棵子树最大高度差为0层,2、3棵子树最大高度差为1层,4、5、6、7棵子树最大高度差为2层,8至15棵子树最大高度差为3层,16至31棵子树最大高度差为4层……
假设n为子树的数量,m为最大高度差,可得:
进一步分析,假设一棵高为h的平衡二叉树的最大高度差为m(假设最小的子树高度为0),且m的高度差由n(此处只考虑分界点情形,即n为2的幂级数)棵子树达成。由平衡二叉树的性质(性质1和性质2),可得如下式:
化简后最终可以得到一个简单的结论(似乎这个结论在本文之前没人关注过,此处仅仅考虑了最差情况,不过对于实际应用和性能分析已经完全够用,详细完整的数学证明感兴趣的读者可以尝试证明):
也就是说,一棵高为h的平衡二叉树,其内部子树的最大高度差可以达到(结果取整,不舍入)。举例来说,一棵高度为8的平衡二叉树,其内部子树的最大高度差为4,同理,一棵高度为9的平衡二叉树,其内部子树的最大高度差为5,如下示(图2):
图2 高度为
9
的平衡二叉树的最大高度差
2.红黑树
历史上AVL树流行的一种变种是红黑树(red black tree)。红黑树也是许多编程语言底层实现采纳较多的数据结构(例如Java的TreeSet和TreeMap实现)。红黑树是具有下列着色性质的二叉查找树:
1.每一个节点或为黑色或为红色。
2.根节点时黑色的。
3.一个红色节点的儿子节点必须全部是黑色。
4.从任意一个节点到一个null的每一条路径必须包含相同数目的黑色节点。
以上着色法则的一个结论是:红黑树的高度最多为,这个结论似乎还不如经典平衡二叉树。下面我们来分析由以上着色法则规定的红黑树的最差情形。
由规则4可知,只要在一个节点的一侧子树尽可能多的使用红色节点,而另一侧尽可能少甚至不使用红色节点,就可以拉开左右子树的高度差,如下图所示(图3):
图3 红黑树高度差形成原因
则我们可以显而易见的得到一个结论:一棵含有k个红色节点的红黑树,理论上其内部子树最大高度差就可以达到k。
既然红黑树理论上缺陷如此大,那为什么实际应用中反而采纳较多?深入研究红黑树的具体实现方式,可以发现,红黑树在实际应用中的实现形式已经超出其原本定义的规则。红黑树的理论阐述不够完善,也是其难于理解和新手难以自己动手实现的原因之一。(注:本文采纳Mark Allen Weiss的《Data Structures and Algorithm Analysis in Java》一书当中对于红黑树的实现方式,这也是实际中使用最多的实现方式)。
经过我的归纳总结,现实中的红黑树在实现过程中增加了以下限制条件:
5.新插入的节点必须为红色。
6.任意节点其左右子树最多相差2层红节点。
7.插入过程(仅限于插入点那条路径上)中不允许任一节点有2个红色儿子节点。
增加了以上三条限制条件的红黑树甚至都不需要再多加分析了。规则6和7建立在规则5和红黑树复杂的插入调整的基础之上,规则6恰巧直接阻止了经典平衡二叉树出现最差情形的可能性,规则7甚至是对红黑树到AA树(AA树实现尚不完善,目前实际性能较差,本文不做深入讨论)的一种过渡。以下图展示了连续向红黑树中插入右节点(依次插入30,40,50,60,70,80,90,100)的变化过程(图4,图5):
图4 依次向红黑树插入
30,40,50,60
情形
图5 继续向红黑树插入
70,80,90,100
情形
3.性能测试
本文的测试环境为Window7操作系统,代码全部使用Java编写,jdk版本为1.8.0,测试均采用随机数生成器产生的九位数数字数据。为尽可能排除编译器优化以及操作系统调度造成的影响,每一项测试均运行100遍取平均时间,例如对于万级测试:生成100次不同的1000个随机九位数,将每次生成的结果分别对平衡二叉树、红黑树、AA树进行树的构造,构造完成后重新循环这10000个数进行平均查找时间的统计,在查找期间还会生成节点数据量十分之一(此处即1000)个假数据来保证查找不到的情况也被统计进查找时间,也就是说万级测试是在操作1100000次后得出的结果。最终测试结果如下:
注:AA树的各项性能均介于红黑树和平衡二叉树之间。但是AA树的深度受随机生成的数字影像波动较大(最差情况出现次数多,并且最差情况树高最高)。
由此总结如下(如果前文都没看懂没有关系,记住下面的结论):
性能:红黑树>平衡二叉树>AA树;
编程实现难度:红黑树>平衡二叉树>AA树。
虽然各种树的实现以及具体应用千差万别,但是没有最好的数据结构只有最合适的数据结构,此处比较的三种查找树在实际应用中性能上只有细微的差别(最差情况出现的概率毕竟非常小),在生产实践中完全不会带来性能的明显缺陷,因此选择合理的实现方式,保证程序的功能健全性,才是选择数据结构的最重要选择因素。
相关代码下载地址(该内容提供了三中树的Java代码实现):https://github.com/bigbird231/Tree