一、红黑树(Red Black Tree)
1、初识红黑树
image
- 红黑树也是一种自平衡的二叉搜索树,也曾叫做平衡二叉B树。
- 红黑树必须满足以下5条性质:
-
节点
是RED
或者BLACK
-
根节点
是BLACK
-
叶子节点
(外部节点,空节点)都是BLACK
-
RED
节点的子节点都是BLACK
-
RED
节点的parent
都是BLACK
- 从
根节点
到叶子节点
的所有路径上不能有2
个连续的RED
节点
- 从任意节点到
叶子节点
的所有路径都包含相同数目的BLACK
节点。
- 在
添加
和删除
节点之后,让树依然满足以上5
条性质,就可以保证平衡。
2、红黑树的等价变化
image
image
-
红黑树
和4阶B树
具有等价性。
-
BLACK
节点与它的RED
子节点融合在一起,形成1
个B树节点。
- 红黑树的
BLACK
节点数与4阶B树
的节点总个数相等
。
3、红黑树 vs 2-3-4树
image
- 如果上图最底层的
BLACK
节点不存在,那么整颗B树只有一个
节点,而且是超级节点
。
4、红黑树节点关系
image
- 父节点(parent)
-
55
是38
和80
的父节点
,38
是25
和46
的父节点
。
- 兄弟节点(sibling)
- 叔父节点(parent的兄弟节点)
- 祖父节点(parent的父节点)
二、红黑树的实现
1、构造方法
// 红黑树继承于二叉平衡搜索树,这里只列出红黑树特有的属性。
public class RBTree<E> extends BBST<E> {
private static final boolean RED = false;
private static final boolean BLACK = true;
@Override
protected Node<E> createNode(E element, Node<E> parent) {
return new RBNode<>(element, parent);
}
// 构造一个红黑节点,默认为红色
private static class RBNode<E> extends Node<E> {
boolean color = RED; //
public RBNode(E element, Node<E> parent) {
super(element, 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 boolean isLeftChild() {
return parent != null && this == parent.left;
}
public boolean isRightChild() {
return parent != null && this == parent.right;
}
// 获取兄弟节点
public Node<E> sibling() {
if (isLeftChild()) {
return parent.right;
}
if (isRightChild()) {
return parent.left;
}
return null;
}
}
复制代码
2、添加
- 通过之前的学习,我们知道:
- B树中,
新元素
必定是添加到叶子节点
中。
-
4阶B树
所有节点的元素个数x
,都符合1 <= x <= 3
。
- 建议新添加的节点默认为
RED
,这样能够让红黑树的性质尽快满足(初识红黑树中的5条性质,除了性质4不一定满足)。
- 如果添加的是根节点,染成BLACK即可。
image
- 因为
新元素
必定是添加到叶子节点
中,所以红黑树
的添加一共有12
种情况,分为17
的左右
,33
的左右
,46
的左
,50
的左右
,72
的左右
,76
的右
,88
的左右
。
- 其中
4
种是parent为BLACK
的情况,有8
种是parent为RED
的情况。
2.1 parent为BLACK
- 有
4
种情况满足红黑树的性质4:parent为BLACK
。
- 并且同样也满足
4阶B树
的性质,因此不用做任何额外处理。
image
2.2 parent为RED(Double Red)
- 这
8
种情况需要在添加之后修复红黑树。
- 其中
前4种
属于B树节点上溢
的情况。
image
2.2.1 添加-修复性质4-LL\RR
image
- 首先看
52
和60
,这两种情况分别属于RR
和LL
。
- 判定条件:
uncle
不是RED
。
- 操作步骤:
-
parent
染成BLACK
,grand
染成RED
。
-
grand
进行单旋
操作。
image
2.2.2 添加-修复性质4-LR\RL
image
- 看
48
和74
,这两种情况属于LR
和RL
。
- 判定条件:
uncle
不是RED。
- 操作步骤:
-
自己
染成BLACK
,grand
染成RED
。
- 进行
双旋
操作。
- 如果是
LR
,parent
左旋转,grand
右旋转。
- 如果是
RL
,parent
右旋转,grand
左旋转。
image
2.2.3 添加-修复性质4-上溢-LL
image
- 现在我们来添加
10
,10
添加之后会导致上溢,这种情况属于LL
。
- 判定条件:
uncle
是RED
。
- 操作步骤:
-
parent
,uncle
染成BLACK
,成为独立节点。
-
grand
向上合并,染成RED
,当作是新添加的节点进行处理。
-
grand
向上合并时,可能继续发生上溢
,若上溢
持续到根节点
,只需将根节点
染成BLACK
。
-
LL
的情况不需要旋转
,只需要染色
。
image
2.2.4 添加-修复性质4-上溢-RR
image
- 判定条件:
uncle
是RED
。
- 操作步骤:
-
parent
,uncle
染成BLACK
,成为独立节点。
-
grand
向上合并,染成RED
,当作是新添加的节点进行处理。
image
2.2.5 添加-修复性质4-上溢-LR
image
- 判定条件:
uncle
是RED
。
- 操作步骤:
-
parent
,uncle
染成BLACK
,成为独立节点。
-
grand
向上合并,染成RED
,当作是新添加的节点进行处理。
image
2.2.6 添加-修复性质4-上溢-RL
image
- 判定条件:
uncle
是RED
。
- 操作步骤:
-
parent
,uncle
染成BLACK
,成为独立节点。
-
grand
向上合并,染成RED
,当作是新添加的节点进行处理。
image
2.3 添加总结
- 添加一共有
12
种情况。
- 其中有
4
种情况,添加之后父节点
为黑色
,添加之后不用做处理。依然满足性质4
。
- 另外
8
种情况,添加之后父节点
为红色
,不满足性质4
,需要进行双红修复处理。
- 修复分为两种情况:
-
uncle
不是RED
:
-
LL\RR
,让祖父节点
进行单旋转,染成红色
,让父节点
成为中心,并染成黑色
。
-
LR\RL
,让祖父节点
和父节点
进行旋转,让新添加成员成为中心节点,染成黑色
,祖父节点染成红色
。
-
uncle
是RED
:
-
父节点
,叔父节点
染成黑色
。
-
祖父节
点染成红色
,并上溢
。
2.4 添加实现
protected void afterAdd(Node<E> node) {
Node<E> parent = node.parent;
// 添加的是根节点 或者 上溢到达了根节点
if (parent == null) {
black(node);
return;
}
// 如果父节点是黑色,直接返回
if (isBlack(parent)) return;
// 叔父节点
Node<E> uncle = parent.sibling();
// 祖父节点
Node<E> grand = red(parent.parent);
if (isRed(uncle)) { // 叔父节点是红色【B树节点上溢】
black(parent);
black(uncle);
// 把祖父节点当做是新添加的节点
afterAdd(grand);
return;
}
// 叔父节点不是红色
if (parent.isLeftChild()) { // L
if (node.isLeftChild()) { // LL
black(parent);
} else { // LR
black(node);
rotateLeft(parent);
}
rotateRight(grand);
} else { // R
if (node.isLeftChild()) { // RL
black(node);
rotateRight(parent);
} else { // RR
black(parent);
}
rotateLeft(grand);
}
}
复制代码
3、删除
image
- 删除分为
删除RED节点
和删除BLACK节点
两种情况。
- 删除
RED节点
,直接删除即可,不用做任何调整。
image
- 删除
BLACK节点
分为3
种情况。
- 拥有
2
个RED子节点
的BLACK节点
,例如25
。
- 不可能被直接删除,因为会找它的
前驱
或后继
子节点替代删除,因此不用考虑这种情况。
- 拥有
1
个RED子节点
的BLACK节点
,例如46
,76
。
-
BLACK
叶子节点,例如88
。
image
3.1 删除拥有1个RED子节点的BLACK节点
- 判定条件:删除指定节点后,用以代替的子节点是RED。
- 将替代的子节点染成BLACK即可保持红黑树的性质。
- 例如删除
50
和72
。
image
3.2 删除 - BLACK叶子节点 - sibling为BLACK
-
BLACK叶子节
点被删除后,会导致B树节点下溢
(比如删除88
)。
3.2.1 sibling至少有1个RED子节点
- 如果
sibling
至少有1
个RED子节点
:
- 进行旋转操作。
- 旋转之后的中心节点继承
parent
的颜色。
- 旋转之后的左右节点染为
BLACK
。
- 如果
sibling
有2
个RED子节点
,那么可以选择删除其左子节点
或右子节点
,删除左子节点少做一次旋转。
image
- 举例:
- 删除
88
。
-
76
左旋转,80
右旋转。
- 中心节点
(78)
继承parent
的颜色(80)
。
-
80
旋转下去之后,染成BLACK
。
image
3.2.2 sibling没有RED子节点
- 如果
sibling
没有1
个RED子节点
:
- 将sibling染成RED,parent染成BLACK即可修复红黑树性质。
image
- 如果
parent
是BLACK
- 会导致
parent
也下溢。
- 这时只需要把
parent
当作被删除的节点处理即可,相当于递归调用afterremove
。
image
3.2.3 sibling为RED
- 如果
sibling
是RED
:
-
sibling
染成BLACK
,parent染成RED
,进行旋转。
- 于是又回到
sibling
是BLACK
的情况。
image
网友评论