红黑树是一种常用的数据结构,例如:
- Java 的TreeMap,TreeSet,HashMap,HashSet
- Linux 的进程调度
- Nginx 的timer管理
红黑树可以类比 4 阶 B 树去分析,下面简述一下 B 树的性质
一、B 树
1、性质
m 阶 B 树有如下性质:
- 若根结点不是终端节点,则至少有两棵子树,最多有m棵子树。
- 除根结点外的所有非叶结点至少有⌈m/2⌉棵子树,最多有m棵子树。
- 所有的非叶结点都出现在同一层次上,并且不带信息(可视为失败结点)
下图就是一颗 4 阶 B 树
B树 和 二叉搜索树,在逻辑上是等价的。
如上图的 4 阶 B 树可以看成是二叉搜索树的一个父节点跟左右子节点合并成一个新节点。
2、添加
新添加的元素必定是添加到叶子节点,如下所示的添加过程,添加 98,56 之后导致中间节点向上合并,也称 上溢
最极端的情况是一直分裂到根节点,此时根节点再次上溢,这是仅有的导致B树长高的情况。
3、删除
同二叉搜索树,删除非叶子节点需要先找到前驱或后继元素,覆盖所需要删除元素的值,再把前驱或后继元素删除。
而前驱或后继元素都在叶子节点中, 所以删除元素都是发生在叶子节点上。
如下图所示,删除 100,80之后,节点个数为0,低于最低限制1,会导致B树节点下溢。
下溢解决的解决方案:
- 兄弟节点可以借(节点个数大于或等于 [m/2] ),旋转解决
- 兄弟节点不可以借,父节点(当前分支和兄弟节点分支之间的父节点)向下合并,这个操作可能会导致父节点继续下溢,一直传播到根节点,最终导致树的高度降低
二、红黑树的基本操作
1、性质
红黑树是一种自平衡的二叉搜索树,必须满足以下 5 条性质
- 节点是 RED 节点或者 BLACK 节点
- 根节点是 BLACK
- 叶子节点(外部节点,空节点)都是 BLACK
- RED 节点的子节点都是 BLACK,可以推导出 RED 节点的 parent 都是 BLACK
- 从任一节点到叶子节点的所有路径都包含相同数目的 BLACK 节点
如下图所示的红黑树满足上面 5 条性质 ,其中 null 节点是假想的,实际运用中可以不用关注
2、添加
BLACK 节点 与他的 RED 子节点融合在一起,形成一个 4 阶B树节点,上图中的红黑树可以看成下图所示的B树
红黑树的 BLACK 节点个数与4阶B树的节点总个数相等
添加的红黑树节点默认为红色,这样能尽快满足红黑树的性质(1,2,3,4都满足,性质4不一定)
下图是红黑树添加节点的所有情况:
添加的节点都是红色的,分别以绿,蓝,紫三种颜色表示三种不同的处理情况
(1)绿色节点(父节点是黑色的),满足性质4,不需要处理
(2)蓝色节点(父节点的兄弟节点不是红色的,空节点是黑色),不满足性质4此时需要做相应的旋转和染色操作
- 节点48:自己染成黑色,祖父节点46染成红色,父节点50先右旋转,然后祖父节点46左旋转
- 节点52:父节点50染成黑色,祖父节点46染成红色,祖父节点46左旋转
- 节点60:父节点72染成黑色,祖父节点76染成红色,祖父节点76右旋转
- 节点74:自己染成黑色,祖父节点76染成红色,父节点72左旋转,然后祖父节点76右旋转
(3)紫色节点(父节点的兄弟节点是红色)
- 父节点,父节点的兄弟节点染成黑色
- 祖父节点向上合并,可以看成 4阶B树节点上溢
例如添加节点10,父节点17,父节点的兄弟节点33染成黑色,祖父节点25 染成红色向上合并,此时当成新节点看成上述添加新节点的情况分析。向上合并时,可能继续发生上溢,如果上溢到根节点,只需要将根节点染成黑色即可,类似B树长高的唯一情况。
如下图所示添加节点10:
- 添加10之后,父节点17,父节点兄弟节点33染成黑色,祖父节点25染成红色,当成新节点向上合并
- 节点25向上合并时,情况和17一样,父节点38,父节点兄弟节点80染成黑色,祖父节点55上溢到根节点染成黑色(当成B树考虑,之前,38,55,80是一个B树节点,添加10之后,55上溢变成根节点,38,80变成55的子节点)
3、删除
B树中,最后真正被删除的元素都在叶子节点,还是以下面的红黑树为例,它包含了红黑树所有的情况
红色节点,如 17,33,50,72,直接删除,不做任何调整
删除黑色节点,有如下三种情况:
(1)拥有2个红色子节点的黑节点,如节点25
先找到它的前驱或后继节点,如 17 , 33 替代,17 , 33是红色节点可以直接删除,因此不用考虑这种情况
(2) 拥有一个红色节点的黑色节点(用以替代的子节点是红色节点),图节点46,76,下面讨论删除46
删除只有一个子节点的黑色节点46之后,55的右子树是50,为了维护红黑树的性质,只需要将子节点50染成黑色即可
(3)删除没有叶子节点的黑色节点(相当于替代的子节点是空节点,黑色),例如88,这种情况比较复杂,分成以下3种情况
1、兄弟节点至少有一个红色子节点,可以推导出兄弟节点是黑色,例如上图80的兄弟节点76,有一个红色节点72。先进行旋转,76成为父节点,72,80成为子节点,然后父节点76继承原来的父节点80的颜色(红色),旋转之后的72,80子节点染成黑色。类似于B树中兄弟节点可以借的情况(B树通过旋转解决)。如下图所示:
2、兄弟节点不可以借(兄弟节点没有一个红色节点),将兄弟节点染成红色,父节点染成黑色即可,这种情况类似于B树节点删除节点时,兄弟节点不可以借。此时父节点向下合并(下溢)。
如果父节点是黑色,会导致父节点也下溢,这时只需要把父节点当成被删除的节点处理即可。
3、兄弟节点是红色,将兄弟节点染成黑色,父节点染成红色,进行旋转,于是又回到兄弟节点是黑色的情况
三、红黑树的平衡
- 相比 AVL 树,红黑树的平衡标准比较宽松:没有一条路径会大于其他路径的 2 倍
- 红黑树是一种弱平衡,黑高度平衡
- 红黑树的最大高度是 2 * log2(n+1),依然是O(logn)级别
- 搜索:O(logn)
- 添加:O(logn),O(1)次旋转操作
- 删除:O(logn),O(1)次旋转操作
红黑树 VS AVL树
- AVL 树的左右子树高度差不超过1,搜索的次数比红黑树少,当搜索的次数远远大于插入和删除时,选择 AVL树
- 相对于AVL树,红黑树牺牲了部分平衡性以换取插入,删除操作时少量的旋转操作。整体来说,性能要优于AVL树
- 红黑树的平均统计性能优于AVL树,实际应用中更多选择使用红黑树