手撕红黑树
1. 红黑树的性值
1.1 平衡条件
- 每个节点非黑即红
- 根节点是黑色
- 叶节点(NIL)是黑色
- 如果一个节点是红色,则它的两个子节点都是黑色
- 从任意节点出发到所有叶节点路径上,黑色节点数量相同
1.2最长路径和最短路径长度关系?
根据平衡条件第4、5点,最短路径都是黑色;最长路径红黑相间;最长是最短的两倍;
1.3平衡调整的终极法门
插入调整站在祖父节点看
删除调整站在父节点看
插入和删除的情况处理一共五种
1.4新插入的节点的颜色
红色,因为插入黑色会引发失衡;插入红色不一定会失衡。
2. 红黑树的插入
2.1 调整原则
为了不影响整颗树的平衡, 调整前路径上的黑节点数量等于调整后路径上黑色节点数量
2.2 调整目的
消除双红
2.3 调整方法
情况一:uncle为红色
站在15节点向下看,发现右子树为红色,并且右子树有一个红色的子节点 ==》 失衡
调整方法:
-
15节点 ==》 变红
-
1节点 ==》 变黑
-
20节点 ==》 变黑
情况二:uncle为黑色
站在20节点向下看,左侧节点为红色,并且左侧节点的孩子有红色节点 ==》失衡
类似于AVL树的LL失衡调整 ==> 右旋
调整后的变色方案可以选择红色上浮或下沉
红色上浮:
红色下沉:
3. 红黑树的删除
3.1 删除场景
-
场景1: 删除度为0的红色节点: 直接删除即可,不会引发失衡
-
场景2:删除度为1的红色节点:不存在这样的节点
若一个红色节点下面有子节点,一定同时有两个黑色子节点
原因:红黑树的第4条和第5条性值
性值4: 如果一个节点是红色,则它的两个子节点都是黑色
性值5: 从任一节点出发到所有叶节点路径上,黑色节点数量相同
- 场景3(需要调整): 删除度为0的黑色节点
删除X后,因X度为0,其下只有NIL节点;
红色根节点左子树一共两个黑色节点(包含NIL节点);所以该红色根节点右子树也需要有两个黑色节点 ==>
即,将该度为0 的黑色节点的颜色加到NIL节点上,至此,这个NIL节点变为双重黑
删除调整的目的就是为了消除双重黑节点。
- 场景4: 删除度为1的黑色节点
度为1的黑色节点的唯一子孩子一定是红色的,如下图所示:
删除X,将唯一子节点挂到父节点,将当前节点X的黑色加到唯一子孩子上。
3.2 调整场景
场景3中,删除度为0的黑色节点会引发红黑树的失衡,消除的双重黑。
- 情况一 (双重黑的兄弟节点为黑色)
双重黑X的兄弟为黑,且兄弟节点的子节点均为黑色节点:
调整方法:
-
双重黑 及 其兄弟节点 --> 去掉一重黑
-
双重黑的父节点 --> 加一重黑
-
情况二 RR失衡:双重黑的兄弟节点为黑色, 右子树的右子树为红色:
双重黑为左子树,双重黑的兄弟节点的右子树为确定性红色(左子树颜色不确定)
若38为红色,则调整前左子树一共有2个黑,则调整后,仍需要为两个黑: 将51改为黑,38 & 72改为红;
若38为黑色,则调整前左子树一共3个黑,调整后,仍需要为3个黑,将72改为黑。
总结RR失衡调整策略:大左旋 => 2个子节点改为黑色,一个新根改为原根的颜色。 -
情况三RL失衡:双重黑的兄弟节点为黑色,右子树的左子树为红色,右子树为黑色
双重黑为左子树,双重黑的兄弟节点的左子树为红色,右子树为确定性黑色(一旦右子树为红,则为RR失衡)
调整方法:
step1: 小右旋 => 将原根节点的颜色和新根节点的颜色对调(原根72改为红色,新根51改为黑色),变为情况二,图示 =》
step2: 大左旋,即,调整为 =》
将新根(51)变为原根(38)的颜色
将新根(51)左右子树变为黑色
双重黑变为黑色
- 情况四: (双重黑的兄弟节点为红色):
step1: 根节点改为红色
step2: 情况(1)若 => 双重黑为右子树,根节点的左子树为红色:将该根节点右旋
情况(2)若 => 双重黑为左子树,根节点的右子树为红色:将该根节点左旋
step3: 新根(原红色的兄弟节点)改为黑色
step4: 情况(1)=> 递归到右子树
情况(2)=> 递归到左子树
下面图演示了 ==> 若双重黑为左子树,根节点的右子树为红色的情况
左旋 =》新根节点改为黑色,原根节点(双重黑的父节点)改为红色
至此,双重黑X节点的兄弟节点变为黑色,应递归到当前根节点的左子树中继续调整,该调整已经转换为上面的双重黑的兄弟节点为黑色的情况处理
代码:
public class RbTree {
static Node NIL = new Node(0, 1, null, null);
static class Node {
int key;
int color;//0:red; 1:black; 2:double black
Node left;
Node right;
Node(int key, int color, Node left, Node right) {
this.key = key;
this.color = color;
this.left = left;
this.right = right;
}
Node(int key, int color) {
this.key = key;
this.color = color;
this.left = NIL;
this.right = NIL;
}
}
Node getNewNode(int key) {
return new Node(key, 0);
}
private Node _insert(Node root, int key) {
if (root == NIL) return getNewNode(key);
if (root.key == key) return root;
if (key < root.key) {
root.left = _insert(root.left, key);
} else {
root.right = _insert(root.right, key);
}
return insert_maintain(root);
}
private boolean has_red_child(Node root) {
return root.left.color == 0 || root.right.color == 0;
}
private Node insert_maintain(Node root) {
int flag = 0;
if (root.left.color == 0 && has_red_child(root.left)) {
flag = 1;
}
if (root.right.color == 0 && has_red_child(root.right)) {
flag = 2;
}
if (flag == 0) {
return root;
}
if (root.left.color == 0 && root.right.color == 0) {
root.color = 0;
root.left.color = 1;
root.right.color = 1;
return root;
}
if (flag == 1) {
if (root.left.right.color == 0) {
root.left = left_rotate(root.left);
}
root = right_rotate(root);
} else {
if (root.right.left.color == 0) {
root.right = right_rotate(root.right);
}
root = left_rotate(root);
}
root.color = 0;
root.left.color = 1;
root.right.color = 1;
return root;
}
private Node right_rotate(Node root) {
Node temp = root.left;
root.left = temp.right;
temp.right = root;
return temp;
}
private Node left_rotate(Node root) {
Node temp = root.right;
root.right = temp.left;
temp.left = root;
return temp;
}
Node insert(Node root, int key) {
root = _insert(root, key);
root.color = 1;
return root;
}
Node _erase(Node root, int key) {
if (root == null) return root;
if (key < root.key) {
root.left = _erase(root.left, key);
} else if (key > root.key) {
root.right = _erase(root.right, key);
} else {
if (root.left == NIL || root.right == NIL) {
Node temp = root.left == NIL ? root.right : root.left;
temp.color += root.color;
return temp;
} else {
Node temp = predecessor(root);
root.key = temp.key;
root.left = _erase(root.left, temp.key);
}
}
return erase_maintain(root);
}
private Node erase_maintain(Node root) {
if (root.right.color != 2 && root.left.color != 2) return root;
//情况四: 双重黑的兄弟节点为红色
if (has_red_child(root)) {
int flag = 0;
root.color = 0;
if (root.right.color == 0) {
root = left_rotate(root);
flag = 1;
} else {
root = right_rotate(root);
flag = 2;
}
root.color = 1;
if (flag == 1) {
root.left = erase_maintain(root.left);
} else {
root.right = erase_maintain(root.right);
}
return root;
}
//情况一:双重黑的兄弟节点为黑色,且该兄弟节点下无红色节点
if (root.left.color == 1 && !has_red_child(root.right) || root.right.color == 1 && !has_red_child(root.left)) {
root.left.color -= 1;
root.right.color -= 1;
root.color += 1;
return root;
}
//情况二 & 三:双重黑的兄弟节点为黑色,且该兄弟节点下存在红色节点
if (root.left.color == 1) {//双重黑为右子树
root.right.color = 1;//将双重黑节点变为黑色
//若左子树的左子树为红色 ==> LL失衡
//否则(若左子树的右子树为黑色,即,红色节点位于左子树的左子树) ==> LR失衡
if (root.left.left.color != 0) {
root.left.color = 0;//将左子树原根变红
root.left = left_rotate(root.left);//将左子树左旋
root.left.color = 1;//将左子树新根变黑
}
root.left.color = root.color;//将左子树小右旋后,将左子树的新根变为原根颜色
root = right_rotate(root);//将根大右旋
} else {//双重黑为左子树
root.left.color = 1;//将双重黑节点变为黑色
//若右子树的右子树为红色 ==> RR失衡
//否则(右子树的右子树为黑色,即,红色节点为右子树的左子树) ==> RL失衡
if (root.right.right.color != 0) {
root.right.color = 0;//将右子树原根变红
root.right = right_rotate(root.right);
root.right.color = 1;//将右子树新根变黑
}
root.right.color = root.color;//将右子树小左旋后,将右子树中新的根节点的颜色变为原根节点的颜色
root = left_rotate(root);//将根大左旋
}
//将新根节点的左右子树变为黑色
root.right.color = 1;
root.left.color = 1;
return root;
}
private Node predecessor(Node root) {
Node temp = root.left;
while (temp.right != NIL) {
temp = temp.right;
}
return temp;
}
Node erase(Node root, int key) {
root = _erase(root, key);
root.color = 1;
return root;
}
static void outPut(Node root) {
if (root == NIL) return;
System.out.println(root.color + ", " + root.key + ", " + root.left.key + ", " + root.right.key);
outPut(root.left);
outPut(root.right);
}
public static void main(String[] args) {
RbTree rbTree = new RbTree();
Node root = NIL;
Scanner scanner = new Scanner(System.in);
System.out.println("Enter array: ");
while (true) {
String[] strings = scanner.nextLine().split(", ");
for (String string : strings) {
int key = Integer.parseInt(string);
root = rbTree.insert(root, key);
System.out.println("--------AVL tree print-------");
outPut(root);
System.out.println();
System.out.println("--------AVL tree print done -------");
}
}
}
}
测试结果:
Enter array:
1, 2, 3, 4, 5, 6, 7, 8, 9
--------AVL tree print-------
1, 1, 0, 0
--------AVL tree print done -------
--------AVL tree print-------
1, 1, 0, 2
0, 2, 0, 0
--------AVL tree print done -------
--------AVL tree print-------
1, 2, 1, 3
1, 1, 0, 0
1, 3, 0, 0
--------AVL tree print done -------
--------AVL tree print-------
1, 2, 1, 3
1, 1, 0, 0
1, 3, 0, 4
0, 4, 0, 0
--------AVL tree print done -------
--------AVL tree print-------
1, 2, 1, 4
1, 1, 0, 0
0, 4, 3, 5
1, 3, 0, 0
1, 5, 0, 0
--------AVL tree print done -------
--------AVL tree print-------
1, 2, 1, 4
1, 1, 0, 0
0, 4, 3, 5
1, 3, 0, 0
1, 5, 0, 6
0, 6, 0, 0
--------AVL tree print done -------
--------AVL tree print-------
1, 4, 2, 6
1, 2, 1, 3
1, 1, 0, 0
1, 3, 0, 0
1, 6, 5, 7
1, 5, 0, 0
1, 7, 0, 0
--------AVL tree print done -------
--------AVL tree print-------
1, 4, 2, 6
1, 2, 1, 3
1, 1, 0, 0
1, 3, 0, 0
1, 6, 5, 7
1, 5, 0, 0
1, 7, 0, 8
0, 8, 0, 0
--------AVL tree print done -------
--------AVL tree print-------
1, 4, 2, 6
1, 2, 1, 3
1, 1, 0, 0
1, 3, 0, 0
1, 6, 5, 8
1, 5, 0, 0
0, 8, 7, 9
1, 7, 0, 0
1, 9, 0, 0
--------AVL tree print done -------