avl-tree-vs-rb-tree
最近看了几篇关于二叉搜索树的文字,其中就包括了几个简单的平衡树的内容,包括avltree
和rbtree
。很多文章和资料都说avltree
综合性能没有rbtree
高,但是有些说法是没什么依据的,可能是被早前的比较差的avltree
实现给误导了。
在一些好的avltree
的实现和rbtree
做性能对比的时候,avltree
的性能并比不rbtree
差。
就从下面几个方面来分析下。
查询性能
因为avltree
的平衡性要要求严格,所以在节点相同的情况下,高度必定不会比rbtree
高,所以在查询性能上,avltree
会更好,虽然都是O(logN)
插入性能
avltree
插入节点之前,先找到需要插入的位置,找这个位置根据二叉搜索树的逻辑很容易实现。
之后就是新建一个节点连上找到的这个节点即可,连上后就需要检查树是否平衡了并且向根部回溯的检查:
- 如果父节点的高度没有变化,那么可以直接停止回溯,因为插入节点没有破坏平衡
- 如果不平衡,就需要旋转操作,最多需要两步旋转,旋转后树高会-1,因为加入节点导致树高+1被抵消了,所以旋转后树一定是平衡的,可以停止回溯。所以回溯也是可以在常数时间内完成,并不需要很高的代价。
- 其他的情况只要重新计算树高即可
rbtree
红黑树的插入可以简单分成两种方式:
- 自底向上(
Bottom-Top
): 简单说就是先找到插入位置,之后添加节点,之后回溯调整,过程略复杂,需要讨论的情况很多,这里就不讲了 - 自顶向下(
Top-Down
): 从根节点开始通过旋转和颜色变化,保证插入的节点位置是黑色的,插入后不需要回溯调整颜色,所以,可以省略指向父节点指针
自顶向下
- 从根节点开始遍历
- 当前节点
X
为黑色,如果左右两个子节点L
,R
都是红色,这时候需要根据X
的父节点P
的颜色分别处理 - 如果
P
也是黑色的,直接变颜色即可,X
变红色,L
,R
变为黑色 - 如果
P
是红色的,这时候p
的父节点G
一定是黑色的(因为两个红节点不能相连,P
兄弟节点U
一定是黑色的,因为如果U
是红色的,那上一步一定会调整)。这时候都要根据X
位置使用单旋转还是双旋转 - 如果
X
,P
,G
三个节点在一条线,那么只需要单旋转即可,再修改下颜色
1 | | | |
- 如果
X
,P
,G
不在一条线,那就需要做两次旋转,再修改节点颜色
小结
通过两中数据结构的插入过程来看,在数量级上是没有区别的,唯一的差别是在avltree
更平衡,所以他的树高会更低,尤其是在节点数量很多的时候,高度会更小,所以查找时间要小一点,插入操作会更有一点优势,尤其是rbtree
在使用自顶向下的插入过程,对比更加明显
删除性能
avltree
avltree
的删除要比插入麻烦点,首先找到要删除的节点
- 如果是叶子节点,直接删除
- 如果有一个子节点,用子节点替代删除节点连上父节点即可
- 如果有两个子节点,则需要在两个子节点中的比较高的子树中找一个值替代。如果左树高则找最大值,如果是右树高则找最小值。找到后交换删除节点和这个节点交换内容,之后按照
1
步骤删除即可
删除节点后需要向根节点回溯,判断平衡和节点高度,直到树高和平衡性没有变化的时候停止即可。
rbtree
红黑树的删除也是比较麻烦的,首先在删除节点后需要判断是否破坏平衡。如果删除的是一个红色叶子节点,那么直接删除即可。如果一个黑节点,那就会破坏平衡,需要回溯调整。所以目标就是保证删除的节点X
一定是一个红色节点。过程如下:
- 如果根节点两个儿子都是黑色的,将根节点染成红色,
X
移动到子节点 - 如果根节点两个儿子有一个红色的,
X
移到根节点
甚至在因为树高比较低,查询性能更好。删除的性能因为要旋转和往根部回溯平衡高度而有点影响,但是rbtree
同样需要回溯修改节点颜色,所以并没什么区别。