红黑树解读
解读红黑树
1、红黑树(Red Black Tree)
-
红黑树也是一种自平衡的二叉搜索树
-
红黑树必须满足一下5条性质
-
节点是RED或者BLACK
-
根节点是BLACK
-
叶子节点(外部节点,空节点)都是BLACK
-
RED节点的子节点都是BLACK
RED节点的parent都是BLACK
从根节点到叶子节点的所有路径上不能有2个连续的RED节点
-
从任一节点到叶子节点的所有路径都包含相同数目的BLACK节点
-
以下即为一颗红黑树:
![Snipaste_2021-03-27_15-58-43](https://gitee.com/xue--dong/blog_images/raw/master/images/20210402183150.png)
2、红黑树的等价变换
![Snipaste_2021-04-01_08-56-33](https://gitee.com/xue--dong/blog_images/raw/master/images/20210402183208.png)
![](https://gitee.com/xue--dong/blog_images/raw/master/images/20210402183215.png)
![Snipaste_2021-04-01_08-57-06](https://gitee.com/xue--dong/blog_images/raw/master/images/20210402183226.png)
如上图所示:
- 红黑树和4阶B树(2-3-4树)具有等价性
- BLACK节点与它的RED子节点融合在一起,形成1个B树节点
- 红黑树的BLACK节点个数与4阶B树的节点总个数相等
2.1、红黑树 VS 2-3-4树
上面的是红黑树,下面是与之等价的2-3-4树
3、几个单词和一些辅助函数
- parent:父节点
- sibling:兄弟节点
- uncle:叔父节点(parent的兄弟节点)
- grand:祖父节点(parent的父节点)
// 染色
private Node<E> color(Node<E> node, boolean color) {
if (node == null) return node;
((RBNode<E>)node).color = color;
return node;
}
// 将该节点染为红色
private Node<E> red(Node<E> node) {
return color(node, RED);
}
// 将该节点染为黑色
private Node<E> black(Node<E> node) {
return color(node, BLACK);
}
// 返回该节点的颜色
private boolean colorOf(Node<E> node) {
return node == null ? BLACK : ((RBNode<E>)node).color;
}
// 该节点是否为黑色
private boolean isBlack(Node<E> node) {
return colorOf(node) == BLACK;
}
// 该节点是否为红色
private boolean isRed(Node<E> node) {
return colorOf(node) == RED;
}
public Node<E> sibling(){
if(isLeftChild()){
return parent.right;
}
if(isRightChild()){
return parent.left;
}
return null;
}
4、添加
-
已知
- B树中,新元素必定是添加到叶子节点中的
- 4阶B树所有节点的元素个数x都符合1<=x<=3
-
建议新添加的节点默认为RED,这样能让红黑树的性质尽快满足
-
如果添加的是根节点,染成BLACK即可
4.1、添加的所有情况
-
有4中情况满足红黑树的性质4:parent为BLACK
-
同样也满足4阶B树的性质
-
因此不用做任何处理
如下图所示,如果我们要添加的是红色节点的话,又因为任何添加其实都是在叶子节点处添加,所以我们不需要对这种情况进行任何的处理,如图中的40,78,82,90都是这种情况
-
-
有 8 种情况不满足红黑树的性质 4 :parent 为 RED( Double Red )
- 其中前 4 种属于B树节点上溢的情况
- 剩下的这8中情况就是我们需要进行处理的
接下来我们对其进行一一解析
4.1.1、添加——修复性质4——LL\RR
我们先来看这种情况,我们要在节点50的左边添加一个新的节点,或者我们要在72的右边添加一个新的节点。那么我们要做的事情很简单。我们知道新添加的这俩个节点,他们的父节点都是红色,这样的话,就会有连续的俩个节点都为红色了,那么就违背了红黑树的性质,所以我们要进行一定的调整。
判定条件:uncle(叔父节点)不是RED
其实调整的过程非常的简单:
- 我们只需要将新添加的节点的父节点染成黑色
- 然后将新添加的祖父节点染成红色
- 然后我们对新添加节点的祖父节点进行旋转即可
也就是:
-
parent染成BLACK,grand染成RED
-
grand进行单选操作
LL:右旋转
RR:左旋转
旋转后如图所示:
4.1.2、添加——修复性质4——LR\RL
我们再看这种情况,这种情况下,新添加的节点处于grand.right.left或者是grand.left.right,也就是我们下图所要添加的节点48或者节点74。对于这种情况,其实处理的方法也很简单,那就是我们要将新添加的节点染黑,然后将它的祖父节点染成红色,并且我们要进行旋转操作。对于下图添加节点48的情况,我们要将它的父节点进行右旋,然后将祖父节点进行左旋;对于下图中添加节点74的情况,我们只需要将它的父节点左旋,然后将它的祖父节点进行右旋就可以了。
判定条件:uncle不是RED
- 自己染成BLACK,grand染成RED
- 进行双旋操作
- LR:parent左旋转,grand右旋转
- RL:parent右旋转,grand左旋转
旋转之后的样子:
4.1.3、添加——修复性质4——上溢——LL
对于这种情况,我们添加元素的时候,肯定会发出上溢的情况,这个时候我们就要将节点进行向上合并的操作。如下图我们要添加新的节点10的时候,我们就需要进行一系列的操作。
首先,我们要将取出中间位置的节点向上合并,我们这里取节点25向上合并,做完这个操作之后,我们将25这个节点染成红色,作为是新添加的节点一样进行处理。然后因为25已经向上合并了,所以原先25节点的左子树和右子树分裂,所以我们要将新添加的节点的父节点,也就是下图的17节点染成黑色,然后我们将下图的33节点也染成黑色,因为现在的33节点也是一个叶子节点,我们知道红黑树里的叶子节点都是黑色的。
判定条件:uncle是RED
-
parent、uncle染成BLACK
-
grand向上合并
- 染成RED,当作是新添加的节点进行处理
-
grand向上合并时,可能继续发生上溢
-
若上溢持续到根节点,只需将根节点染成BLACK
添加节点处理后如图所示:
4.1.4、添加——修复性质4——上溢——RR
对于这种情况,其实是和我们上面说的是相似的。对于我们要添加的新节点36来说,如果我们要添加新节点,那么必然会发生上溢的现象。所以我们同样的要选取中间位置的节点进行向上合并,这里我们取节点25进行向上合并,同样的,我们将节点25染成红色,当成新添加的节点进行处理。然后原来25节点的左右子树进行分裂。这个时候,我们应该将parent、uncle染成黑色。
判定条件:uncle是RED
- parent、uncle染成BLACK
- grand向上合并
- 染成RED,当作是新添加的节点进行处理
4.1.5、添加——修复性质4——上溢——LR
在这里我就不一一进行赘述了,下面的情况大家可以自己进行理解
判定条件:uncle是RED
- parent、uncle染成BLACK
- grand向上合并
- 染成RED,当做是新添加的节点进行处理
4.1.6、添加——修复性质4——上溢——RL
判定条件:uncle是RED
- parent、uncle染成BLACK
- grand向上合并
- 染成RED,当做是新添加的节点进行处理
5、删除
- B树中,最后真正被删除的元素都在叶子节点中
5.1、删除——RED节点
- 对于这种情况,我们可以直接进行删除,不用做任何的调整
5.2、删除——BLACK节点
-
有3种情况
- 拥有2个RED子节点的BLACK节点
- 不可能被直接删除,因为会找它的子节点代替删除
- 不用考虑这种情况
- 拥有1个RED子节点的BLACK节点
- BLACK叶子节点
我们来说一说,为什么拥有2个RED子节点的BLACK节点的这种情况。如下图,假如我们要删除的是节点55,我们要处理的情况就是找到节点55的前驱结点或者后继节点进行替换节点55,然后将它的前驱结点或者后继节点删除。也就是说,如果我们要删除的节点是55的话,其实我们删除的就是它的前驱结点或者后继节点。
- 拥有2个RED子节点的BLACK节点
5.2.1、删除——拥有1个RED子节点的BLACK节点
- 判定条件:用以替代的子节点是RED
- 将替代的子节点染成BLACK即可保持红黑树性质
我们来简单的叙述一下这种操作,如下图,我们要删除节点46和节点76,因为要替代他们俩位置的都是红色的子节点50和72,所以进行删除的时候我们只需要将俩个节点删除,然后将节点50和72染成黑色就好了
操作之后的样子为:
5.2.2、删除——BLACK叶子节点——sibling为BLACK
- BLACK 叶子节点被删除后,会导致B树节点下溢(比如删除88)
- 如果 sibling 至少有 1 个 RED 子节点
- 进行旋转操作
- 旋转之后的中心节点继承parent的颜色
- 旋转之后的左右节点染为BLACK
5.2.3、删除——BLACK叶子节点——sibling为BLACK
- 判定条件:sibling没有1个RED子节点
- 将sibling染成RED、parent染成BLACK即可修复红黑树性质
- 如果parent是BLACK
- 会导致parent也下溢
- 这时只需要将parent当作被删除的节点处理即可
5.2.4、删除——BLACK叶子节点——sibling为RED
- 如果sibling是RED
- sibling 染成 BLACK,parent 染成 RED,进行旋转
- 于是又回到 sibling 是 BLACK 的情况
6、红黑树的平衡
- 相比AVL树,红黑树的平衡标准比较宽松:没有一条路径会大于其他路径的2倍
- 是一种弱平衡、黑高度平衡
- 红黑树的最大高度是 2 ∗ log2(n + 1) ,依然是 O(logn) 级别
7、平均时间复杂度
- 搜索:O(logn)
- 添加:O(logn),O(1) 次的旋转操作
- 删除:O(logn),O(1) 次的旋转操作
8、AVL树 VS 红黑树
- AVL树
- 平衡标准比较严格:每个左右子树的高度差不超过1
- 最大高度是 1.44 ∗ log2 n + 2 − 1.328(100W个节点,AVL树最大树高28)
- 搜索、添加、删除都是 O(logn) 复杂度,其中添加仅需 O(1) 次旋转调整、删除最多需要 O(logn) 次旋转调整
- 红黑树
- 平衡标准比较宽松:没有一条路径会大于其他路径的2倍
- 最大高度是 2 ∗ log2(n + 1)( 100W个节点,红黑树最大树高40)
- 搜索、添加、删除都是 O(logn) 复杂度,其中添加、删除都仅需 O(1) 次旋转调整
- 搜索的次数远远大于插入和删除,选择AVL树;搜索、插入、删除次数几乎差不多,选择红黑树
- 相对于AVL树来说,红黑树牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树
- 红黑树的平均统计性能优于AVL树,实际应用中更多选择使用红黑树