一、概述
1. 历史
红黑树是一种自平衡二叉查找树,最早由一名叫Rudolf Bayer的德国计算机科学家于1972年发明。然而,最初的树形结构不是现在的红黑树,而是一种称为B树的结构,它是一种多叉树,可以用于在磁盘上存储大量数据。
在1980年代早期,计算机科学家Leonard Adleman和Daniel Sleator推广了红黑树,并证明了它的自平衡性和高效性。从那时起,红黑树成为了最流行的自平衡二叉查找树之一,并被广泛应用于许多领域,如编译器、操作系统、数据库等。
红黑树的名字来源于红色节点和黑色节点的交替出现,它们的颜色是用来维护树的平衡性的关键。它们的颜色具有特殊的意义,黑色节点代表普通节点,而红色节点代表一个新添加的节点,它们必须满足一些特定的规则才能维持树的平衡性。
红黑树也是一种自平衡的二叉搜索树,较之AVL,插入和删除时旋转次数更少。
2. 红黑树特性
- 所有节点都有两种颜色:红🔴、黑⚫️
- 所有null视为黑色⚫️
- 红色🔴节点不能相邻
- 根节点是黑色⚫️
- 从根到任意一个叶子节点,路径中的黑色⚫️节点数一样
二、实现
1. 插入情况
插入节点均视为红色🔴
case 1:插入节点为根节点,将根节点变黑⚫️
case 2:插入节点的父节点若为黑色⚫️,树的红黑性质不变,无需调整
插入节点的父节点为红色🔴,触发红红相邻
case3:叔叔为红色🔴
- 父亲变为黑色⚫️,为了保证黑色平衡,连带的叔叔也变为黑色⚫️
- 祖父如果是黑色不变,就会造成这棵子树黑色过多,因此祖父节点变为红色🔴
- 祖父如果变成红色,可能会接着触发红红相邻,因此对将祖父进行递归调整
case 4:叔叔为黑色⚫️
1. 父亲为左孩子,插入节点也是左孩子,此时即LL不平衡
- 让父亲变黑⚫️,为了保证这棵子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色
- 祖父右旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻
2. 父亲为左孩子,插入节点是右孩子,此时即LR不平衡
- 父亲左旋,变为LL情况,按1.来后续处理
3. 父亲为右孩子,插入节点也是右孩子,此时即RR不平衡
- 让父亲变黑⚫️,为了保证这棵子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色
- 祖父左旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻
4. 父亲为右孩子,插入节点是左孩子,此时即RL不平衡
- 父亲右旋,变成RR情况,按3.后续处理
节点类Node
private static class Node {
int key;
Object value;
Node left;
Node right;
Node parent;
Color color = Color.RED; // 颜色
/**
* 判断是否是左孩子
* @return
*/
public boolean isLeftChild() {
return parent != null && parent.left == this;
}
/**
* 找叔叔节点
* @return
*/
public Node uncle() {
if(parent == null || parent.parent == null) {
return null;
}
if(parent.isLeftChild()) {
return parent.parent.right;
} else {
return parent.parent.left;
}
}
/**
* 找兄弟节点
* @return
*/
public Node sibling() {
if(parent == null) { // 根节点
return null;
}
if(this.isLeftChild()) {
return parent.right;
} else {
return parent.left;
}
}
}
判断节点的颜色
/**
* 判断节点是否是红色
* @param node
* @return
*/
public boolean isRed(Node node) {
return node != null && node.color == Color.RED;
}
/**
* 判断节点是否为黑色
* @param node
* @return
*/
public boolean isBlack(Node node) {
return node == null || node.color == Color.BLACK;
}
右旋
右旋前
右旋后
代码:
private Node root;
/**
* 右旋
* 1. parent的处理
* 2. 旋转后新根的父子关系
* @param pink
*/
private void rightRotate(Node pink) {
Node parent = pink.parent;
Node yellow = pink.left;
Node green = yellow.right;
// 被旋转节点的右子树不为空
if(green != null) {
green.parent = pink;
}
yellow.right = pink;
yellow.parent = parent;
pink.left = green;
pink.parent = yellow;
if(parent == null) {
// 旋转后yellow为根节点
root = yellow;
}else if(parent.left == pink) {
// 左子树
parent.left = yellow;
} else {
// 右子树
parent.right = yellow;
}
}
左旋
/**
* 左旋
* @param pink
*/
private void leftRotate(Node pink) {
Node parent = pink.parent;
Node yellow = pink.right;
Node green = yellow.left;
if(green != null) {
green.parent = pink;
}
yellow.left = pink;
yellow.parent = parent;
pink.right = green;
pink.parent = yellow;
if(parent == null) {
root = yellow;
} else if(parent.left == pink) {
parent.left = yellow;
} else {
parent.right = yellow;
}
}
新增节点
/**
* 新增或更新
* 正常增、遇到红红不平衡进行调整
* @param key
* @param value
*/
public void put(int key, Object value) {
// 1. 找空位
Node p = root;
Node parent = null;
while(p != null) {
parent = p;
if(key < p.key) {
p = p.left;
} else if(p.key < key) {
p = p.right;
} else {
p.value = value; // 更新
return;
}
}
Node inserted = new Node(key, value);
if(parent == null) {
root = inserted;
} else if(key < parent.key) {
// 左孩子
parent.left = inserted;
inserted.parent = parent;
} else {
// 右孩子
parent.right = inserted;
inserted.parent = parent;
}
// 红红相邻调整
fixRedRed(inserted);
}
/**
* 红红相邻
* @param x
*/
private void fixRedRed(Node x) {
if(x == root) {
// 情况1 插入节点为根节点,将根节点变黑
x.color = Color.BLACK;
return;
}
if(isBlack(x.parent)) {
// 情况2 插入节点的父节点为黑色,树的红黑特性不变,无需调整
return;
}
Node parent = x.parent;
Node uncle = x.uncle();
Node grandparent = parent.parent;
if(isRed(uncle)) {
// 情况3 插入节点的父节点与叔叔节点为红色
// 父亲变为黑色,为了保证黑色平衡,连带的叔叔也变为黑色
parent.color = Color.BLACK;
uncle.color = Color.BLACK;
// 祖父如果是黑色不变,会造成这棵子树黑色过多,因此祖父节点变为红色
grandparent.color = Color.RED;
// 如果祖父变成红色,可能会触发红红相邻,因此对祖父进行递归调整
fixRedRed(grandparent);
return;
}
// 情况4 插入节点的父节点为红色,叔叔为黑色
// 1. 父亲为左孩子,插入节点也是左孩子,此时即LL不平衡 -> 右旋
if(parent.isLeftChild() && x.isLeftChild()) {
// 父亲变黑
parent.color = Color.BLACK;
// 祖父变红
grandparent.color = Color.RED;
// 右旋
rightRotate(grandparent);
}
// 2. 父亲为左孩子,插入节点是右孩子,此时即LR不平衡 -> 左右旋
else if(parent.isLeftChild()) {
// 以父节点为支点进行左旋
leftRotate(parent);
// 插入节点变黑
x.color = Color.BLACK;
// 祖父节点变红
grandparent.color = Color.RED;
// 以祖父节点为支点进行右旋
rightRotate(grandparent);
}
// 3. 父亲为右孩子,插入节点也是右孩子,此时即RR不平衡 -> 左旋
else if(!x.isLeftChild()) {
// 父节点变黑
parent.color = Color.BLACK;
// 祖父节点变红
grandparent.color = Color.RED;
// 以祖父节点为支点进行左旋
leftRotate(grandparent);
}
// 4. 父亲节点为右孩子,插入节点是左孩子,此时即RL不平衡 -> 右左旋
else {
// 以父节点为支点进行右旋
rightRotate(parent);
// 插入节点变黑
x.color = Color.BLACK;
// 祖父节点变红
grandparent.color = Color.RED;
// 以祖父节点为支点进行左旋
leftRotate(grandparent);
}
}
CASE 4-1:LL
调整前:
调整后:
- 父亲变黑
- 祖父变红
- 右旋
CASE 4-2:LR
调整前:
调整后:
- 左旋
- 父节点变黑色,祖父节点变红色
- 右旋
2. 删除情况
case 0:如果删除节点有两个孩子
- 交换删除节点和后继节点的key,value,递归删除后继节点,直到该节点没有孩子或只剩下一个孩子
如果删除节点没有孩子或只剩一个孩子
case 1:删的是根节点
- 删完了,直接将root = null
- 用剩余节点替换根节点的key,value,根节点孩子null,颜色保持黑色⚫️不变
删黑色⚫️会失衡,删红色不会失衡,但删黑色有一种简单情况
case 2:删的是黑⚫️,剩下的是红🔴,剩下这个红节点变黑⚫️
删除节点和剩余节点都是⚫️黑,触发双黑,双黑的意思是,少了一个黑
case 3:被调整节点的兄弟为红🔴,此时两个侄子定为黑⚫️
- 删除节点是左孩子,父亲左旋
- 删除节点是右孩子,父亲右旋
- 父亲和兄弟要变色,保证旋转后颜色平衡
- 旋转的目的是让黑侄子变为删除节点的黑兄弟,对删除节点再次递归,进入case 4 或 case 5
case 4:被调整节点的兄弟为黑⚫️,两个侄子都为黑⚫️
- 将兄弟变红🔴,目的是将删除节点和兄弟那边的黑色高度同时减少1
- 如果父亲是红🔴,则需将父亲变为黑⚫️,避免红红,此时路径黑节点数目不变
- 如果父亲是黑⚫️,说明这条路径还是少黑,再次让父节点触发双黑
case 5:被调整节点的兄弟为黑,至少一个红🔴侄子
①如果兄弟是左孩子,左侄子是红,LL不平衡
- 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,左侄子也是黑⚫️
- 原来兄弟要成为父亲,需要保留父亲颜色
例如,删除节点4
右旋
变色
②如果兄弟是左孩子,右侄子是红🔴,LR不平衡
- 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️
- 右侄子会取代原来父亲,因此它保留父亲颜色
- 兄弟已经是黑了⚫️,无需改变
例如,要删除节点4
左旋(以兄弟为支点)
右旋(以父节点为支点)
变色
③如果兄弟是右孩子,右侄子是红🔴,RR不平衡
- 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,右侄子也是黑⚫️
- 原来兄弟要成为父亲,需要保留父亲颜色
死如果兄弟是右孩子,左侄子是红🔴,RL不平衡
- 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️
- 左侄子会取代原来父亲,因此它保留父亲颜色
- 兄弟已经是黑了⚫️,无需改变
/**
* 解决双黑问题 -> case 3、 case 4、 case 5
* @param x
*/
private void fixDoubleBlack(Node x) {
if(x == root) {
return;
}
Node parent = x.parent;
Node sibling = x.sibling();
// case 3:被调整节点的兄弟为红色,此时两个侄子定为黑色
if(isRed(sibling)) {
if(x.isLeftChild()) {
// 删除节点是左孩子 -> 父亲左旋
leftRotate(parent);
} else {
// 删除节点是右孩子 -> 父亲右旋
rightRotate(parent);
}
// 父亲和兄弟要变色,保证旋转后颜色平衡
parent.color = Color.RED;
sibling.color = Color.BLACK;
// 旋转的目的是让黑侄子变为删除节点的黑兄弟,对删除节点再次递归,进入case 4或 case 5
fixDoubleBlack(x);
return;
}
if (sibling != null) {
// case 4:被调整节点的兄弟为黑,两个侄子都为黑
if(isBlack(sibling.left) && isBlack(sibling.right)) {
// 1. 将兄弟变红,目的是将删除节点和兄弟那边的黑色高度同时减少1
sibling.color = Color.RED;
if(isRed(parent)) {
// 2. 如果父亲是红,则需将父亲变为黑,避免红红,此时路径黑节点数目不变
parent.color = Color.BLACK;
} else {
// 3. 如果父亲是黑,说明这条路径还是少黑,再次让父节点触发双黑
fixDoubleBlack(parent);
}
}
// case 5:被调整节点的兄弟是黑色,至少一个侄子是红色
else {
// 5.1 如果兄弟是左孩子,左侄子是红色,LL不平衡
if(sibling.isLeftChild() && isRed(sibling.left)) {
// 右旋
rightRotate(parent);
// 左侄子变黑色
sibling.left.color = Color.BLACK;
// 原来兄弟要成为父亲,需要保留父亲颜色
sibling.color = parent.color;
}
// 5.2 如果兄弟是左孩子,右侄子是红色,LR不平衡
else if(sibling.isLeftChild() && isRed(sibling.right)) {
// 右侄子会取代原来的父亲,保留父亲的颜色
sibling.right.color = parent.color;
// 左旋(以兄弟为支点)
leftRotate(sibling);
// 右旋(以父节点为支点)
rightRotate(parent);
}
// 5.3 如果兄弟是右孩子,右侄子是红色,RR不平衡
else if(!sibling.isLeftChild() && isRed(sibling.right)) {
// 左旋
leftRotate(parent);
// 右侄子变成黑色
sibling.right.color = Color.BLACK;
// 原来兄弟要成为父亲,需要保留父亲颜色
sibling.color = parent.color;
}
// 5.4 如果兄弟是右孩子,左侄子是红色,RL不平衡
else {
// 左侄子会取代原来父亲,因此它保留父亲颜色
sibling.left.color = parent.color;
// 右旋(以兄弟为支点)
rightRotate(sibling);
// 左旋(以父亲为支点)
leftRotate(parent);
}
// 旋转过来的父亲要变成黑色
parent.color = Color.BLACK;
}
} else {
// @TODO 实际也不会出现,触发双黑后,兄弟节点不会为 null
fixDoubleBlack(parent);
}
}
private void doRemove(Node deleted) {
Node replaced = findReplaced(deleted);
Node parent = deleted.parent;
// 1. 没有孩子
if(replaced == null) {
// case 1:删除的是根节点,且删完了,直接将root = null
if(deleted == root) {
root = null;
} else {
// 删除的是叶子节点
if(isBlack(deleted)) {
// 删除节点和剩余节点(空节点)都是黑色,复杂调整
fixDoubleBlack(deleted);
} else {
// 删除节点是红色,无需任何处理
}
if(deleted.isLeftChild()) {
// 被删除节点是父亲的左孩子
parent.left = null;
} else {
// 被删除节点是父亲的右孩子
parent.right = null;
}
deleted.parent = null; // help GC
}
return;
}
// 2. 有一个孩子
if(deleted.left == null || deleted.right == null) {
// case 1: 删除的是根节点,用剩余节点替换根节点key、value,根节点孩子=null,颜色保持黑色不变
if(deleted == root) {
// 孩子节点不会再有孩子,否则树高不平衡
root.key = replaced.key;
root.value = replaced.value;
root.left = root.right = null;
} else {
// 删除的是非叶子节点 -> 先删除,再调整
if(deleted.isLeftChild()) {
// 删除的是父节点的左孩子
parent.left = replaced;
} else {
// 删除的是父节点的右孩子
parent.right = replaced;
}
replaced.parent = parent;
deleted.left = deleted.right = deleted.parent = null; // help GC
// 删除节点和剩下节点都是黑,触发双黑 -> 少了一个黑
if(isBlack(deleted) && isBlack(replaced)) {
// 复杂处理
fixDoubleBlack(replaced);
} else {
// case 2:删的是黑色,剩下的是红色,剩下这个红节点变黑
replaced.color = Color.BLACK;
}
}
return;
}
// 3. case 0:被删除节点有两个孩子 => 有一个孩子 或 没有孩子
// 3.1 交换被删节点和后继节点的key,value值
int k = deleted.key;
deleted.key = replaced.key;
replaced.key = k;
Object v = deleted.value;
deleted.value = replaced.value;
replaced.value = v;
// 3.2 递归删除后继节点,直到该节点没有孩子或只剩一个孩子
doRemove(replaced);
}
3. 完整代码
package com.itheima.datastructure.RedBlackTree;
import com.sun.org.apache.regexp.internal.RE;
/**
* 红黑树
*/
public class RedBlackTree {
enum Color {
RED, BLACK;
}
private static class Node {
int key;
Object value;
Node left;
Node right;
Node parent;
Color color = Color.RED; // 颜色
public Node(int key) {
this.key = key;
}
public Node(int key, Object value) {
this.key = key;
this.value = value;
}
public Node(int key, Color color) {
this.key = key;
this.color = color;
}
public Node(int key, Node left, Node right, Color color) {
this.key = key;
this.left = left;
this.right = right;
this.color = color;
if(left != null) {
left.parent = this;
}
if(right != null) {
right.parent = this;
}
}
/**
* 判断是否是左孩子
* @return
*/
public boolean isLeftChild() {
return parent != null && parent.left == this;
}
/**
* 找叔叔节点
* @return
*/
public Node uncle() {
if(parent == null || parent.parent == null) {
return null;
}
if(parent.isLeftChild()) {
return parent.parent.right;
} else {
return parent.parent.left;
}
}
/**
* 找兄弟节点
* @return
*/
public Node sibling() {
if(parent == null) { // 根节点
return null;
}
if(this.isLeftChild()) {
return parent.right;
} else {
return parent.left;
}
}
}
private Node root;
/**
* 判断节点是否是红色
* @param node
* @return
*/
public boolean isRed(Node node) {
return node != null && node.color == Color.RED;
}
/**
* 判断节点是否为黑色
* @param node
* @return
*/
public boolean isBlack(Node node) {
return node == null || node.color == Color.BLACK;
}
/**
* 右旋
* 1. parent的处理
* 2. 旋转后新根的父子关系
* @param pink
*/
private void rightRotate(Node pink) {
Node parent = pink.parent;
Node yellow = pink.left;
Node green = yellow.right;
// 被旋转节点的右子树不为空
if(green != null) {
green.parent = pink;
}
yellow.right = pink;
yellow.parent = parent;
pink.left = green;
pink.parent = yellow;
if(parent == null) {
// 旋转后yellow为根节点
root = yellow;
}else if(parent.left == pink) {
// 左子树
parent.left = yellow;
} else {
// 右子树
parent.right = yellow;
}
}
/**
* 左旋
* @param pink
*/
private void leftRotate(Node pink) {
Node parent = pink.parent;
Node yellow = pink.right;
Node green = yellow.left;
if(green != null) {
green.parent = pink;
}
yellow.left = pink;
yellow.parent = parent;
pink.right = green;
pink.parent = yellow;
if(parent == null) {
root = yellow;
} else if(parent.left == pink) {
parent.left = yellow;
} else {
parent.right = yellow;
}
}
/**
* 新增或更新
* 正常增、遇到红红不平衡进行调整
* @param key
* @param value
*/
public void put(int key, Object value) {
// 1. 找空位
Node p = root;
Node parent = null;
while(p != null) {
parent = p;
if(key < p.key) {
p = p.left;
} else if(p.key < key) {
p = p.right;
} else {
p.value = value; // 更新
return;
}
}
Node inserted = new Node(key, value);
if(parent == null) {
root = inserted;
} else if(key < parent.key) {
// 左孩子
parent.left = inserted;
inserted.parent = parent;
} else {
// 右孩子
parent.right = inserted;
inserted.parent = parent;
}
// 红红相邻调整
fixRedRed(inserted);
}
/**
* 红红相邻
* @param x
*/
private void fixRedRed(Node x) {
if(x == root) {
// 情况1 插入节点为根节点,将根节点变黑
x.color = Color.BLACK;
return;
}
if(isBlack(x.parent)) {
// 情况2 插入节点的父节点为黑色,树的红黑特性不变,无需调整
return;
}
Node parent = x.parent;
Node uncle = x.uncle();
Node grandparent = parent.parent;
if(isRed(uncle)) {
// 情况3 插入节点的父节点与叔叔节点为红色
// 父亲变为黑色,为了保证黑色平衡,连带的叔叔也变为黑色
parent.color = Color.BLACK;
uncle.color = Color.BLACK;
// 祖父如果是黑色不变,会造成这棵子树黑色过多,因此祖父节点变为红色
grandparent.color = Color.RED;
// 如果祖父变成红色,可能会触发红红相邻,因此对祖父进行递归调整
fixRedRed(grandparent);
return;
}
// 情况4 插入节点的父节点为红色,叔叔为黑色
// 1. 父亲为左孩子,插入节点也是左孩子,此时即LL不平衡 -> 右旋
if(parent.isLeftChild() && x.isLeftChild()) {
// 父亲变黑
parent.color = Color.BLACK;
// 祖父变红
grandparent.color = Color.RED;
// 右旋
rightRotate(grandparent);
}
// 2. 父亲为左孩子,插入节点是右孩子,此时即LR不平衡 -> 左右旋
else if(parent.isLeftChild()) {
// 以父节点为支点进行左旋
leftRotate(parent);
// 插入节点变黑
x.color = Color.BLACK;
// 祖父节点变红
grandparent.color = Color.RED;
// 以祖父节点为支点进行右旋
rightRotate(grandparent);
}
// 3. 父亲为右孩子,插入节点也是右孩子,此时即RR不平衡 -> 左旋
else if(!x.isLeftChild()) {
// 父节点变黑
parent.color = Color.BLACK;
// 祖父节点变红
grandparent.color = Color.RED;
// 以祖父节点为支点进行左旋
leftRotate(grandparent);
}
// 4. 父亲节点为右孩子,插入节点是左孩子,此时即RL不平衡 -> 右左旋
else {
// 以父节点为支点进行右旋
rightRotate(parent);
// 插入节点变黑
x.color = Color.BLACK;
// 祖父节点变红
grandparent.color = Color.RED;
// 以祖父节点为支点进行左旋
leftRotate(grandparent);
}
}
/**
* 查找删除节点
* @param key
* @return
*/
private Node find(int key) {
Node p = root;
while(p != null) {
if(key < p.key) {
p = p.left;
} else if(p.key < key) {
p = p.right;
} else {
return p;
}
}
return null;
}
/**
* 查找被删除节点的剩余节点
* @param deleted
* @return
*/
private Node findReplaced(Node deleted) {
// 1. 被删除节点是叶子节点
if(deleted.left == null && deleted.right == null) {
return null;
}
// 2. 只有右孩子
if(deleted.left == null) {
return deleted.right;
}
// 3. 只有左孩子
if(deleted.right == null) {
return deleted.left;
}
// 4. 有两个孩子 -> 找后继
Node s = deleted.right;
while(s.left != null) {
s = s.left;
}
return s;
}
/**
* 删除
* 正常删,会用到李代桃僵技巧,遇到黑黑不平衡进行调整
* @param key
*/
public void remove(int key) {
Node deleted = find(key);
// 被删除节点不存在
if(deleted == null) {
return;
}
doRemove(deleted);
}
/**
* 解决双黑问题 -> case 3、 case 4、 case 5
* @param x
*/
private void fixDoubleBlack(Node x) {
if(x == root) {
return;
}
Node parent = x.parent;
Node sibling = x.sibling();
// case 3:被调整节点的兄弟为红色,此时两个侄子定为黑色
if(isRed(sibling)) {
if(x.isLeftChild()) {
// 删除节点是左孩子 -> 父亲左旋
leftRotate(parent);
} else {
// 删除节点是右孩子 -> 父亲右旋
rightRotate(parent);
}
// 父亲和兄弟要变色,保证旋转后颜色平衡
parent.color = Color.RED;
sibling.color = Color.BLACK;
// 旋转的目的是让黑侄子变为删除节点的黑兄弟,对删除节点再次递归,进入case 4或 case 5
fixDoubleBlack(x);
return;
}
if (sibling != null) {
// case 4:被调整节点的兄弟为黑,两个侄子都为黑
if(isBlack(sibling.left) && isBlack(sibling.right)) {
// 1. 将兄弟变红,目的是将删除节点和兄弟那边的黑色高度同时减少1
sibling.color = Color.RED;
if(isRed(parent)) {
// 2. 如果父亲是红,则需将父亲变为黑,避免红红,此时路径黑节点数目不变
parent.color = Color.BLACK;
} else {
// 3. 如果父亲是黑,说明这条路径还是少黑,再次让父节点触发双黑
fixDoubleBlack(parent);
}
}
// case 5:被调整节点的兄弟是黑色,至少一个侄子是红色
else {
// 5.1 如果兄弟是左孩子,左侄子是红色,LL不平衡
if(sibling.isLeftChild() && isRed(sibling.left)) {
// 右旋
rightRotate(parent);
// 左侄子变黑色
sibling.left.color = Color.BLACK;
// 原来兄弟要成为父亲,需要保留父亲颜色
sibling.color = parent.color;
}
// 5.2 如果兄弟是左孩子,右侄子是红色,LR不平衡
else if(sibling.isLeftChild() && isRed(sibling.right)) {
// 右侄子会取代原来的父亲,保留父亲的颜色
sibling.right.color = parent.color;
// 左旋(以兄弟为支点)
leftRotate(sibling);
// 右旋(以父节点为支点)
rightRotate(parent);
}
// 5.3 如果兄弟是右孩子,右侄子是红色,RR不平衡
else if(!sibling.isLeftChild() && isRed(sibling.right)) {
// 左旋
leftRotate(parent);
// 右侄子变成黑色
sibling.right.color = Color.BLACK;
// 原来兄弟要成为父亲,需要保留父亲颜色
sibling.color = parent.color;
}
// 5.4 如果兄弟是右孩子,左侄子是红色,RL不平衡
else {
// 左侄子会取代原来父亲,因此它保留父亲颜色
sibling.left.color = parent.color;
// 右旋(以兄弟为支点)
rightRotate(sibling);
// 左旋(以父亲为支点)
leftRotate(parent);
}
// 旋转过来的父亲要变成黑色
parent.color = Color.BLACK;
}
} else {
// @TODO 实际也不会出现,触发双黑后,兄弟节点不会为 null
fixDoubleBlack(parent);
}
}
private void doRemove(Node deleted) {
Node replaced = findReplaced(deleted);
Node parent = deleted.parent;
// 1. 没有孩子
if(replaced == null) {
// case 1:删除的是根节点,且删完了,直接将root = null
if(deleted == root) {
root = null;
} else {
// 删除的是叶子节点
if(isBlack(deleted)) {
// 删除节点和剩余节点(空节点)都是黑色,复杂调整
fixDoubleBlack(deleted);
} else {
// 删除节点是红色,无需任何处理
}
if(deleted.isLeftChild()) {
// 被删除节点是父亲的左孩子
parent.left = null;
} else {
// 被删除节点是父亲的右孩子
parent.right = null;
}
deleted.parent = null; // help GC
}
return;
}
// 2. 有一个孩子
if(deleted.left == null || deleted.right == null) {
// case 1: 删除的是根节点,用剩余节点替换根节点key、value,根节点孩子=null,颜色保持黑色不变
if(deleted == root) {
// 孩子节点不会再有孩子,否则树高不平衡
root.key = replaced.key;
root.value = replaced.value;
root.left = root.right = null;
} else {
// 删除的是非叶子节点 -> 先删除,再调整
if(deleted.isLeftChild()) {
// 删除的是父节点的左孩子
parent.left = replaced;
} else {
// 删除的是父节点的右孩子
parent.right = replaced;
}
replaced.parent = parent;
deleted.left = deleted.right = deleted.parent = null; // help GC
// 删除节点和剩下节点都是黑,触发双黑 -> 少了一个黑
if(isBlack(deleted) && isBlack(replaced)) {
// 复杂处理
fixDoubleBlack(replaced);
} else {
// case 2:删的是黑色,剩下的是红色,剩下这个红节点变黑
replaced.color = Color.BLACK;
}
}
return;
}
// 3. case 0:被删除节点有两个孩子 => 有一个孩子 或 没有孩子
// 3.1 交换被删节点和后继节点的key,value值
int k = deleted.key;
deleted.key = replaced.key;
replaced.key = k;
Object v = deleted.value;
deleted.value = replaced.value;
replaced.value = v;
// 3.2 递归删除后继节点,直到该节点没有孩子或只剩一个孩子
doRemove(replaced);
}
}
4. 小结
维度 | 普通二叉搜索树 | AVL树 | 红黑树 |
查询 | 平均O(log n),最坏O(n) | O(logn) | O(logn) |
插入 | 平均O(logn),最坏O(n) | O(logn) | O(logn) |
删除 | 平均O(logn),最坏O(n) | O(logn) | O(logn) |
平衡性 | 不平衡 | 严格平衡 | 近似平衡 |
结构 | 二叉树 | 自平衡的二叉树 | 具有红黑性质的自平衡二叉树 |
查找效率 | 低 | 高 | 高 |
插入删除效率 | 低 | 中等 | 高 |
普通二叉树插入、删除、查询的时间复杂度与树的高度有关,因此在最坏情况下,时间复杂度为O(n),而且容易退化为链表,查找效率低。
AVL树是一种高度平衡的二叉搜索树,其左右子树的高度差不超过1。因此,它能够在logn的平均时间内完成插入、删除、查询操作,但是在维护平衡的过程中,需要频繁地进行旋转操作,导致插入删除效率较低。
红黑树是一种近似平衡的二叉搜索树,它在保持高度平衡的同时,又能够保持较高的插入、删除效率。红黑树通过节点着色和旋转操作来维护平衡。红黑树在维护平衡的过程中,能够进行较少的节点旋转操作,因此插入删除效率较高,并且查询效率也较高。
综上所述,红黑树具有较高的综合性能,是一种广泛应用的数据结构。