红黑树(RB-Tree)比AVL强在哪?

前言

以前本科同学在找工作的时候,就被面试官问到过关于红黑树的问题。因为当时我的知识面不广,所以也不知道红黑树是个什么东西,也没放在心上。在看过了STL源码后才知道原来有很多底层实现都用的红黑树。简单的查阅了相关概念后,我觉得它和AVL没啥差别,而且AVL更好理解,平衡性也更优,那为什么还要使用红黑树?

于是我开始查阅相关资料,百度红黑树的概念及性质,在网上看到了一些回复,感觉讲的很有道理。但我没有简单的接受他们的答案,而是在Github上找代码,以验证他们所说的红黑树的优势。本篇博文不向各位介绍红黑树原理及旋转操作的过程,只介绍一些基本的概念,然后通过使用AVL和RB-Tree处理数据的时间验证红黑树的优势。

 

基本概念

AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。查找、插入和删除在平均和最坏情况下都是O(logn)。

红黑树(Red Black Tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的:它可以在O(logn)时间内做查找,插入和删除。

红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树有如下的额外要求:

性质1. 节点是红色或黑色。

性质2. 根节点是黑色。

性质3 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

性质4. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

从以上概念和性质可知红黑树不是高度平衡的二叉树,用非严格的平衡结构来换取增删节点时旋转次数的降低虽然它放弃了严格的高度平衡,但完成查询、插入、删除操作的时间复杂性度依然为O(logn)。此外,算法导论里的分析我们可以得到这么一个结论:任何不平衡都会在三次旋转之内解决。

 

CSDN、知乎上的回答

1. 如果插入一个node引起了树的不平衡,AVL和RB-Tree都是最多只需要2次旋转操作,即两者都是O(1);但是在删除node引起树的不平衡时,最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,因此需要旋转的量级O(logn),而RB-Tree最多只需3次旋转,只需要O(1)的复杂度。

2. 其次,AVL的结构相较RB-Tree来说更为平衡,在插入和删除node更容易引起Tree的unbalance,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。因此,RB-Tree在需要大量插入和删除node的场景下,效率更高。自然,由于AVL高度平衡,因此AVL的search效率更高。

3. map的实现只是折衷了两者在search、insert以及delete下的效率。总体来说,RB-tree的统计性能是高于AVL的。

4.红黑树的查询性能略微逊色于AVL树,因为他比avl树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的avl树最多多一次比较,但是,红黑树在插入和删除上完爆avl树,avl树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于avl树为了维持平衡的开销要小得多

注意:因为每次插入和删除都要进行查找元素的操作,所以插入和删除的时间复杂性的都是O(logn),其实插入和删除操作本身的时间复杂度是O(1)。

 

实验验证

以上代码是我在Github上找到的,作者的目的是实现和Linux下的红黑树一样快的AVL。其中分析了AVL和红黑树在处理数据时的性能,也是验证上述回答的非常好的一个实验。实验分为静态内存评估和动态内存评估,而我只关注于两者在插入、查找和删除元素所耗费的时间上。网址在文章结尾。

静态内存情况下运行结果:

RB-Tree和AVL在处理10000000个结点时的数据对比:
insert time: 3400ms, height=32
insert time: 3618ms, height=27

search time: 2790ms error=0
search time: 2912ms error=0

delete time: 510ms
delete time: 612ms

total: 6700ms
total: 7142ms

RB-Tree和AVL在处理1000000个结点时的数据对比:
insert time: 292ms, height=26
insert time: 315ms, height=24

search time: 167ms error=0
search time: 170ms error=0

delete time: 56ms
delete time: 62ms

total: 515ms
total: 547ms

 

动态内存情况下运行结果:

RB-Tree和AVL在处理10000000个结点时的数据对比:
insert time: 3832ms, height=32
insert time: 4138ms, height=27

search time: 2901ms error=0
search time: 2945ms error=0

delete time: 484ms
delete time: 574ms

total: 7217ms
total: 7657ms

RB-Tree和AVL在处理1000000个结点时的数据对比:
insert time: 342ms, height=26
insert time: 373ms, height=24

search time: 187ms error=0
search time: 171ms error=0

delete time: 44ms
delete time: 53ms

total: 573ms
total: 597ms

 

分析

以下是程序运行时间的柱状图,这里只做了静态内存的图表,因为无论是静态还是动态,程序处理数据的优劣情况是一样的。

从上面的两张图片可以看出,随着数据量的增加,RB-Tree的优势更为明显。当这两个树型结构在处理百万元素的时候,在删除方面,RB-Tree比AVL快6ms,但两者的删除操作总共花费时间为60ms左右。相比插入和搜索时间占比而言,在删除操作方面,RB-Tree的表现更好。当数据量增加了一个量级后,RB-Tree的删除操作比AVL快将近100ms(节约的时间占比变高)。所以当处理大量数据,且删除操作较为频繁的情况下,RB-Tree的性能要优于AVL。综上所述,如果你的应用中,查询的次数远远大于插入和删除,那么应该选择AVL如果搜索,插入删除次数几乎差不多,应该选择RB-Tree

 

结论

结合实验和网友的回答,可知AVL在查询上是优于RB-Tree,但在删除元素方面,RB-Tree所花费的时间要小于AVL的用时。这里我不能对插入效率做出评判,因为插入一个结点引起的不平衡,AVL和RB-Tree都是最多只需要2次旋转操作,所以从理论上讲,两者的时间应该是差不多的(或者多次实验的好坏是交替出现的)。但实验数据往往是RB-Tree优于AVL,这点和代码有关。在寻找AVL和RB-Tree做对比实验的代码时,我就发现了网上所给的代码效率参差不齐。原本我使用的是严蔚敏数据结构的AVL代码做实验,但是严奶奶的书上没有给出删除操作的代码,所以我就在网上找完整的代码。在找代码的过程中发现,有很多代码能用,但效率太低了(而且我想找的是AVL和RB-Tree运行时间差距不是很大的代码)。我甚至还发了一条Blink求助网友,但是没有得到有效的回应,最后终于在Github上找到了可以用于验证以上回答的代码。可能各位也会对代码本身有疑问,但这是我目前找到的最符合需求的代码了。

 

参考:

https://github.com/skywind3000/avlmini

https://blog.csdn.net/mmshixing/article/details/51692892

http://www.zhihu.com/question/20545708/answer/58717264

http://www.zhihu.com/question/43744788/answer/9825888

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tyler_Zx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值