前言
本文主要聚焦在红黑树的添加删除的所有情况的归纳以及给出完整可运行的java实现代码,代码的正确性我已经验证,可以放心使用。
其实红黑树并不难懂,难懂的原因是没有理解红黑树产生的历史,以及红黑树与2-3-4树之间的关系。导致很多文章一上来就讨论红黑树的五条性质,然后让人云里雾里。看了网上很多中文博客文章,没有遇到一篇能完全讲明白的,很多文章都没有实现代码,有的给出了源码,但是代码并没能真正修复红黑树,更有甚者,画的图都不符合红黑树的性质。本文通过与2-3-4树对比的形式来给读者描述红黑树添加和删除所有可能的情况,以及在这些情况下如何修复红黑树的性质。并给出完整的Java源码实现。
注意,这里说的是普通红黑树,而不是算法4里面的左倾红黑树。左倾红黑树对应2-3树,是红黑树的一个变种,实现会相对普通红黑树简单,因为不需要考虑右倾情况。网上有些文章把普通红黑树对应到2-3树上,这是完全没有理解红黑树表现,不建议看。普通红黑树对应的应该是2-3-4树也就是4阶B树,
其实要把红黑树完全讲透,一篇文章是不够的。但是由于时间和精力有限。无法将涉及的知识都讲一遍,这篇文章默认读者有二叉搜索树(添加,删除),AVL树(旋转),B树和2-3-4树(理解红黑树的基础)的知识储备。如果没有这些知识储备,请务必先学习这些知识后再看本文。
红黑树的性质
- 根节点必须是黑色
- 节点的颜色只有红色和黑色
- NIL叶子节点都是黑色
- 红色节点的子节点必须是黑色(也就是不能出现两个连续的红色节点)
- 从任一节点到其每个NIL叶子(NIL节点)的所有简单路径都包含相同数目的黑色节点(也就是说黑色节点对应2-3-4树的高度)
红黑树的本质
红黑树其实就是通过五条性质约束二叉搜索树,使得这棵树可以转换成对应的2-3-4树。也就是说每一棵红黑树都有唯一一棵2-3-4树与之对应。
后面我们的添加删除后的调整都围绕着红黑树对应的2-3-4树进行的。所以一定要先理解2-3-4树的添加和删除,否则会看得云里雾里
2-3-4树(4阶B树)的添加和删除都是发生在B树最下面一层。红黑树中的黑色节点就是用来标记B树中的一个节点,所以这也是为什么要满足性质5的原因,如果不满足性质5,红黑树就不能对应到4阶B树上。然后性质4的约束,才能保证B树的阶是4阶
红黑树的插入以及其对应的2-3-4树的插入
MrDrBob, CC BY-SA 3.0, via Wikimedia Commons
通过上面这幅图片可以知道,红黑树的添加中对性质的维护完全是符合B树的添加。
例如:添加70。对应的2-3-4树节点上溢,红黑树,则是通过涂黑父亲节点和叔父节点来处理上溢。使得从根节点到叶子NIL节点的所有路径上的黑色节点数量加1。
下面通过2-3-4树的样式来绘制红黑树,强烈建议对比2-3-4树的添加和删除来观看学习。
节点之间的关系
添加节点所有可能情况和处理方案
首先添加的节点默认是红色,上溢的节点也是作为新添加的红色节点处理。
添加是通过判断父节点和叔父节点的颜色来处理不同的情况。
添加节点后修复红黑树的Java代码实现
/**
* 新添加的节点默认都是--红色
* <pre>
* 1. 添加到根节点:
* 1.1 直接添加,然后染黑
* 2. 添加到非根节点
* 2.1 parent是黑色
* 直接添加
* 2.2 parent是红色
* i. 叔父节点是黑色或者空(1.空,。2.黑色,)
* a.空(说明这个是在叶子节点添加的情况)
* grand节点涂红
* 按照节点的当前的类型进行旋转(分别有四种类型LL,LR,RR,RL)
* 旋转后grand的sibling涂红
* b.黑色(说明是添加节点导致上溢的情况,出现了叔父节点为黑色的情况。对应的2-3-4树,不在同一层,所以跟空的情况一样处理)。
* ii.叔父节点是红色(添加的节点,将会导致该层溢出)
* (父节点 & 叔父节点) 涂黑
* grand涂红 当做新添加节点处理
* </pre>
*
* @param node
*/
private void afterAdd(RBNode<E> node) {
RBNode<E> currentNode;
do {
currentNode = node;
if (node.parent == null) {//添加到根节点,或者上溢到根节点
root = node;
black(node);
} else {//非根节点
if (isRed(node.parent)) {//parent 是红色
RBNode<E> grand = node.parent.parent;
RBNode<E> parent = node.parent;
if (isBlack(parent.sibling())) {//叔父节点是空或者黑色。没有溢出
//旋转操作
red(grand);
if (parent.isLeftChild()) {//L
if (node.isLeftChild()) {//LL
black(parent);
} else {//LR
black(node);
rotateLeft(parent);
}
rotateRight(grand);
} else {//R
if (node.isRightChild()) {//RR
black(parent);
} else {//RL
black(node);
rotateRight(parent);
}
rotateLeft(grand);
}
} else {//叔父节点红色,溢出
// 红 <- 黑 -> 红 -> new红, new红 <- 红 <- 黑 -> 红
red(grand);
black(parent.sibling());
black(parent);
node = grand;//不使用递归
}
} else {//parent 是黑色
//直接添加,不用调整修复。就满足了红黑树的性质
}
}
} while (currentNode != node);
}
删除节点所有可能情况和处理方案
删除节点中最为复杂的情况就是删除黑色叶子节点(删除2-2)的情况
删除节点后修复红黑树的Java代码实现
/**
* <pre>
* ┌────────60───────┐
* │ │
* ┌─R_55─┐ ┌─R_65─┐
* │ │ │ │
* ┌─50─┐ 57─┐ ┌─63 70
* │ │ │ │
* R_40 R_53 R_58 R_62
*
*
* 可能会删除的节点有:
* 1. 根节点:
* 1.1 下溢导致的根节点删除
* ┌─25─┐
* │ │
* 20 30
*
* remove: 30(导致25下溢)
*
* ┌─25
* │
* R_20
*
* 由于删除30会使得25产生下溢,但是下溢的是根节点,所以不需要处理。对应的2-3-4树的高度减1
*
* 1.2 非下溢导致的
* a.
* ┌─25
* │
* R_20
*
* remove: 25
*
* 20
*
* 涂黑20(如果有红色子节点,要记得涂黑)
*
* b.
* 20
*
* remove: 20
*
* 只有一个根节点不需要涂黑
* 2. 非根节点:
* ┌────────60───────┐
* │ │
* ┌─R_55─┐ ┌─R_65─┐
* │ │ │ │
* ┌─50─┐ 57─┐ ┌─63 70
* │ │ │ │
* R_40 R_53 R_58 R_62
* 2.1 红色叶子节点(40,53,58,62)
* 直接删除即可
*
* 2.2 黑色节点
* 2.2.1 黑色非叶子节点(57,63)-- 将红色叶子节点涂黑
*
* 2.2.2 黑色叶子节点(70(叶子),由于下溢导致被删除的黑色节点,也是需要当做叶子节点处理。)
* (根据红黑树的性质5,可知黑色节点对应的是2-3-4树的层级,所以必定还有其它黑色叶子节点在同一层级)
*
* a.有黑色兄弟(在对应的2-3-4树中,被删除节点和他的兄弟在同一层)
*
* i. 黑色兄弟的子节点是红色。向兄弟借
* 旋转让兄弟或者兄弟子节点替换parent,parent替换被删除叶子节点。使得红黑树对应的 2-3-4树最后一层保持不变
*
* ii.黑色兄弟的子节点是黑色(空子节点和黑色字节点都当做黑色处理,因为无论哪一种,都无法借出)。向父亲借
* 将兄弟染红(向父节点借)
* 父亲是红色,就染黑
* 父亲是黑色,就下溢处理(这里特别要注意的地方是,下溢后,父节点就相当于新的被删除的节点,重新走afterRemove)
*
* b.有红色兄弟(说明兄弟必然有黑色儿子)(在对应的2-3-4树中,被删除节点和他的兄弟在不在同一层,但是兄弟的黑儿子和被删除节点在同一层)
* ┌──────77─────┐
* │ │
* ┌───55────┐ ┌─80─┐
* │ │ │ │
* 17─┐ ┌─R_57─┐ 79 ┌─R_88─┐
* │ │ │ │ │
* R_29 56 62 84 93
* remove: 77(实际删除的是后继节点79)
* ┌──────79─────┐
* │ │
* ┌───55────┐ ┌─88─┐
* │ │ │ │
* 17─┐ ┌─R_57─┐ 80─┐ 93
* │ │ │ │
* R_29 56 62 R_84
* i.通过旋转将兄弟的黑儿子变成兄弟节点
* 按照a的情况处理
*
* 处理溢出的时候,防止将黑色节点的红色子节点染黑,导致不平衡。
* </pre>
*
* @param node
*/
private void afterRemove(RBNode<E> node) {
RBNode<E> currentNode;
boolean underflow = false;
do {
currentNode = node;
if (node.parent == null) {//删除的是根节点
if (underflow) {//下溢不做处理
underflow = false;
} else {//删除度为1或者0的根节点导致的。如果删除的是度为1的根节点,这个时候要将根节点的红色子节点染黑
if (isRed(node.left)) {
black(node.left);
}
if (isRed(node.right)) {
black(node.right);
}
}
} else {//删除的是非根节点
if (isRed(node)) {//删除的是红色节点,不用调整,因为不会影响红黑树的性质
} else {//删除的是黑色节点。
// 删除非根节点的黑色子节点,被删除的节点必定有兄弟(性质5)
// 借的顺序是 兄弟 --> 兄弟的儿子 --> 父亲
//1. 黑(黑色叶子节点,下溢的黑色节点也要当做黑色叶子节点处理)
if (underflow || isBlack(node.left) && isBlack(node.right)) {
underflow = false;
//需要找兄弟节点借,如果兄弟节点没有,就找父节点借,节点会下溢
RBNode<E> parent = node.parent;
RBNode<E> sibling;
if (parent.hasTwoChildren()) {//在下溢的时候
sibling = node.isLeftChild() ? parent.right : parent.left;
} else {//在正常删除节点的时候,必然有一个子节点
sibling = parent.left == null ? parent.right : parent.left;
}
//兄弟节点是红色(兄弟必定有黑儿子),将兄弟的黑儿子变成兄弟
if (isRed(sibling)) {
red(parent);
black(sibling);
if (sibling.isLeftChild()) {
rotateRight(parent);
sibling = parent.left;
} else {
rotateLeft(parent);
sibling = parent.right;
}
}
//兄弟节点都是黑色的
if (isRed(sibling.left) || isRed(sibling.right)) {//兄弟有红色子节点,兄弟可以借
if (sibling.isLeftChild()) {//L
//先看左边有没有红色子节点,有的话。优先按照LL处理
//没有的话就是LR。通过左旋可以转成LL
if (isBlack(sibling.left)) {//LR
rotateLeft(sibling);
sibling = sibling.parent;
}
//此时都转成了LL情况,统一按照LL情况处理
if (colorOf(parent) == RBNode.BLACK) {
black(sibling);
} else {
red(sibling);
}
rotateRight(parent);
} else {
//RL转成RR来处理
if (isBlack(sibling.right)) {//RL
rotateRight(sibling);
sibling = sibling.parent;
}
//此时都转成了RR情况,统一按照RR情况处理
if (colorOf(parent) == RBNode.BLACK) {
black(sibling);
} else {
red(sibling);
}
rotateLeft(parent);
}
black(parent);
black(parent.sibling());
} else {//兄弟只有黑色子节点或者兄弟没有子节点。兄弟无法借出,需要从父节点借(父节点是黑色会产生下溢)
if (isRed(parent)) {
black(parent);
red(sibling);
} else {
red(sibling);
node = parent;
//实际节点是红<-黑,黑色节点下溢了。需要再执行afterRemove,但是不能让它被当有红色子节点的黑色节点处理
//而是当做被没有子节点的黑色节点处理(黑)
underflow = true;
}
}
} else {//1. 红<-黑 2. 黑->红 度为1的黑色节点
//将红色子节点染黑
if (isRed(node.left)) {
black(node.left);
} else if (isRed(node.right)) {
black(node.right);
}
}
}
}
} while (currentNode != node);
}
红黑树Java版完整实现源码
package com.kevin.datastructures.tree;
import java.util.*;
/**
* 红黑树五条性质:
* 1. 根节点必须是黑色
* 2. 节点的颜色只有红色和黑色
* 3. NIL节点都是黑色
* 4. 红色节点的子节点必须是黑色(也就是不能出现两个连续的红色)
* 5. 从任一节点到其每个叶子(NIL节点)的所有简单路径都包含相同数目的黑色节点(也就是说黑色节点才是对应2-3-4树的高度)
*
* Author: Kevin Xie
* Email: lylwo317@gmail.com
* @param <E>
*/
public class RedBlackTree<E> {
private static class RBNode<E> {
public static int RED = 0;
public static int BLACK = 1;
private int color = RED;
E element;
RBNode<E> left;
RBNode<E> right;
RBNode<E> parent;
public boolean isLeaf() {
return left == null && right == null;
}
public boolean isChild() {
return parent != null;
}
public boolean isLeftChild() {
return parent != null && parent.left == this;
}
public boolean hasTwoChildren() {
return left != null && right != null;
}
public boolean isRightChild() {
return parent != null && parent.right == this;
}
public RBNode<E> sibling() {//兄弟
if (isLeftChild()) {
return parent.right;
}
if (isRightChild()) {
return parent.left;
}
return null;
}
public RBNode(E element, RBNode<E> parent) {
this.element = element;
this.parent = parent;
}
@Override
public String toString() {
String parentString = "null";
if (parent != null) {
parentString = parent.element.toString();
}
String nodeStr = element + "_p(" + parentString + ")";
String str = "";
if (color == RED) {
str = "R_";
}
return str + nodeStr;
}
}
protected int size;
protected RBNode<E> root;
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
public void clear() {
root = null;
size = 0;
}
private Comparator<E> comparator;
public RedBlackTree() {
}
public RedBlackTree(Comparator<E> comparator) {
this.comparator = comparator;
}
/**
* 添加一个元素要做一些这些:
* 1.检查元素是否为空,空就不添加,因为添加空的元素,无法比较大小
* 2.是否是第一个节点,是的话直接root = newNode
* 3.不是第一个节点,找到合适的位置(叶子节点),然后添加上去,如果发现有相同的,就直接替换
*
* @param element
*/
public void add(E element){
if (element == null) {
throw new IllegalArgumentException("element must not be null");
}
if (root == null) {
root = createNode(element, null);
afterAdd(root);
size++;
return;
}
RBNode<E> node = root;
RBNode<E> parent;
int compare;
do {
compare = compare(element, node.element);
if (compare == 0) {
node.element = element;
return;
}
parent = node;
if (compare < 0) {
node = node.left;
} else {//compare > 0
node = node.right;
}
} while (node != null);
RBNode<E> newNode = createNode(element, parent);
if (compare < 0) {
parent.left = newNode;
} else {
parent.right = newNode;
}
afterAdd(newNode);
size++;
}
/**
* 找前驱节点,找比当前节点小的所有节点中最大的节点
*
* node
* if(有左子树){
* node.left.right.right...(左子树最右(最大)节点)
* } else {
* //当前节点没有左子树
* if(当前节点是父节点的右子节点) {//父节点就是前驱
* node.parent
* } else if(当前节点是父节点的左子节点){//就往祖父节点找,直到找到祖父节点的右子节点为止
* node.parent.parent....(parent.right == )
* } else { //没有前驱节点
* return null //也就是最左边的叶子节点
* }
* }
*
* @param node
* @return
*/
protected RBNode<E> predecessor(RBNode<E> node) {
if (node == null) {
return null;
}
if (node.left != null) {
node = node.left;
while (node.right != null) {
node = node.right;
}
return node;
} else {
while (node.parent != null && node.parent.left == node) {
node = node.parent;
}
// if (node.parent != null) {
// return node.parent;
// } else {
// return null;
// }
//简化
return node.parent;
}
}
/**
* 后继节点,与前驱相反,改left 为 right 就行
* @param node
* @return
*/
protected RBNode<E> successor(RBNode<E> node) {
if (node == null) {
return null;
}
if (node.right != null) {
node = node.right;
while (node.left != null) {
node = node.left;
}
return node;
} else {
while (node.parent != null && node.parent.right == node) {
node = node.parent;
}
// if (node.parent != null) {
// return node.parent;
// } else {
// return null;
// }
//简化
return node.parent;
}
}
public boolean contains(E element) {
return findNode(element) != null;
}
/**
* 通过,前序,中序,后序,层序遍历等手段找到元素
*/
private RBNode<E> findNode(E element) {
if (element == null) {
return null;
}
Stack<RBNode<E>> nodeStack = new Stack<>();
RBNode<E> node = root;
do {
while (node != null) {
nodeStack.push(node);
node = node.left;
}
if (!nodeStack.isEmpty()) {
RBNode<E> pop = nodeStack.pop();
if (pop.element.equals(element)) {
return pop;
}
node = pop.right;
}
} while (node != null || !nodeStack.isEmpty());
return null;
}
/**
* 根据 element 找到Node,然后删除。
*
* @param element
*/
public void remove(E element) {
removeNode(findNode(element));
}
/**
* 删除节点
*
* 1. 节点的度为2
* 找到前驱或者后继节点,然后将前驱或者后继节点覆盖当前节点,接着将前驱或者后继节点删除即可
* 2. 节点的度为1
* 删除,然后将子节点接到父节点上
* 3. 节点的度为0
* 直接删除
*
* 综上所述,本质上删除的都是度为0或者1的节点
*
*/
private void removeNode(RBNode<E> node) {
if (node == null) {
return;
}
if (node.left != null && node.right != null) {//度为2的节点
//如果直接删除,这个时候有两棵子树就好不连接到父节点了。这个时候要连接到父节,就应该在左子树中找到最大的节点(右子树中找到最小节点)
//来代替原来节点的位置。(这样不但保持了二叉搜索树的性质,还解决了删除后连接到父节点的问题)
//度为2的节点就找前驱或者后继节点来替代当前节点
RBNode<E> successor = successor(node);
//replace
node.element = successor.element;
node = successor;//前驱或者后继节点的度必然不会是2
}
//叶子节点直接删除
if (node.isLeaf()) {//度为0的节点
if (node == root) {
root = null;
} else {
if (node.parent.left == node) {
node.parent.left = null;
} else {
node.parent.right = null;
}
}
} else {//度为1的节点
if (node == root) {//node.parent == null
if (node.left != null) {
root = node.left;
} else {
root = node.right;
}
root.parent = null;
} else {
RBNode<E> replaceNode;
if (node.left != null) {
replaceNode = node.left;
} else {
replaceNode = node.right;
}
RBNode<E> parent = node.parent;
if (parent.left == node) {
parent.left = replaceNode;
} else {
parent.right = replaceNode;
}
if (replaceNode != null) {
replaceNode.parent = parent;
}
}
}
afterRemove(node);
size--;
}
private void rotateLeft(RBNode<E> grand) {
RBNode<E> rootNode = grand.parent;
RBNode<E> parent = grand.right;
RBNode<E> subTree = parent.left;
parent.left = grand;
grand.right = subTree;
afterRotate(rootNode, grand, parent, subTree);
}
private void rotateRight(RBNode<E> grand) {
RBNode<E> rootNode = grand.parent;
RBNode<E> parent = grand.left;
RBNode<E> subTree = parent.right;
parent.right = grand;
grand.left = subTree;
afterRotate(rootNode, grand, parent, subTree);
}
private void afterRotate(RBNode<E> rootNode, RBNode<E> grand, RBNode<E> parent, RBNode<E> subTree) {
if (grand.isLeftChild()) {
rootNode.left = parent;
} else if (grand.isRightChild()) {
rootNode.right = parent;
} else {//根节点
root = parent;
}
//update node.parent
if (subTree != null) {
subTree.parent = grand;
}
grand.parent = parent;
parent.parent = rootNode;
}
/**
* 新添加的节点默认都是--红色
* <pre>
* 1. 添加到根节点:
* 1.1 直接添加,然后染黑
* 2. 添加到非根节点
* 2.1 parent是黑色
* 直接添加
* 2.2 parent是红色
* i. 叔父节点是黑色或者空(1.空,。2.黑色,)
* a.空(说明这个是在叶子节点添加的情况)
* grand节点涂红
* 按照节点的当前的类型进行旋转(分别有四种类型LL,LR,RR,RL)
* 旋转后grand的sibling涂红
* b.黑色(说明是添加节点导致上溢的情况,出现了叔父节点为黑色的情况。对应的2-3-4树,不在同一层,所以跟空的情况一样处理)。
* ii.叔父节点是红色(添加的节点,将会导致该层溢出)
* (父节点 & 叔父节点) 涂黑
* grand涂红 当做新添加节点处理
* </pre>
*
* @param node
*/
private void afterAdd(RBNode<E> node) {
RBNode<E> currentNode;
do {
currentNode = node;
if (node.parent == null) {//添加到根节点,或者上溢到根节点
root = node;
black(node);
} else {//非根节点
if (isRed(node.parent)) {//parent 是红色
RBNode<E> grand = node.parent.parent;
RBNode<E> parent = node.parent;
if (isBlack(parent.sibling())) {//叔父节点是空或者黑色。没有溢出
//旋转操作
red(grand);
if (parent.isLeftChild()) {//L
if (node.isLeftChild()) {//LL
black(parent);
} else {//LR
black(node);
rotateLeft(parent);
}
rotateRight(grand);
} else {//R
if (node.isRightChild()) {//RR
black(parent);
} else {//RL
black(node);
rotateRight(parent);
}
rotateLeft(grand);
}
} else {//叔父节点红色,溢出
// 红 <- 黑 -> 红 -> new红, new红 <- 红 <- 黑 -> 红
red(grand);
black(parent.sibling());
black(parent);
node = grand;//不使用递归
}
} else {//parent 是黑色
//直接添加,不用调整修复。就满足了红黑树的性质
}
}
} while (currentNode != node);
}
/**
* <pre>
* ┌────────60───────┐
* │ │
* ┌─R_55─┐ ┌─R_65─┐
* │ │ │ │
* ┌─50─┐ 57─┐ ┌─63 70
* │ │ │ │
* R_40 R_53 R_58 R_62
*
*
* 可能会删除的节点有:
* 1. 根节点:
* 1.1 下溢导致的根节点删除
* ┌─25─┐
* │ │
* 20 30
*
* remove: 30(导致25下溢)
*
* ┌─25
* │
* R_20
*
* 由于删除30会使得25产生下溢,但是下溢的是根节点,所以不需要处理。对应的2-3-4树的高度减1
*
* 1.2 非下溢导致的
* a.
* ┌─25
* │
* R_20
*
* remove: 25
*
* 20
*
* 涂黑20(如果有红色子节点,要记得涂黑)
*
* b.
* 20
*
* remove: 20
*
* 只有一个根节点不需要涂黑
* 2. 非根节点:
* ┌────────60───────┐
* │ │
* ┌─R_55─┐ ┌─R_65─┐
* │ │ │ │
* ┌─50─┐ 57─┐ ┌─63 70
* │ │ │ │
* R_40 R_53 R_58 R_62
* 2.1 红色叶子节点(40,53,58,62)
* 直接删除即可
*
* 2.2 黑色节点
* 2.2.1 黑色非叶子节点(57,63)-- 将红色叶子节点涂黑
*
* 2.2.2 黑色叶子节点(70(叶子),由于下溢导致被删除的黑色节点,也是需要当做叶子节点处理。)
* (根据红黑树的性质5,可知黑色节点对应的是2-3-4树的层级,所以必定还有其它黑色叶子节点在同一层级)
*
* a.有黑色兄弟(在对应的2-3-4树中,被删除节点和他的兄弟在同一层)
*
* i. 黑色兄弟的子节点是红色。向兄弟借
* 旋转让兄弟或者兄弟子节点替换parent,parent替换被删除叶子节点。使得红黑树对应的 2-3-4树最后一层保持不变
*
* ii.黑色兄弟的子节点是黑色(空子节点和黑色字节点都当做黑色处理,因为无论哪一种,都无法借出)。向父亲借
* 将兄弟染红(向父节点借)
* 父亲是红色,就染黑
* 父亲是黑色,就下溢处理(这里特别要注意的地方是,下溢后,父节点就相当于新的被删除的节点,重新走afterRemove)
*
* b.有红色兄弟(说明兄弟必然有黑色儿子)(在对应的2-3-4树中,被删除节点和他的兄弟在不在同一层,但是兄弟的黑儿子和被删除节点在同一层)
* ┌──────77─────┐
* │ │
* ┌───55────┐ ┌─80─┐
* │ │ │ │
* 17─┐ ┌─R_57─┐ 79 ┌─R_88─┐
* │ │ │ │ │
* R_29 56 62 84 93
* remove: 77(实际删除的是后继节点79)
* ┌──────79─────┐
* │ │
* ┌───55────┐ ┌─88─┐
* │ │ │ │
* 17─┐ ┌─R_57─┐ 80─┐ 93
* │ │ │ │
* R_29 56 62 R_84
* i.通过旋转将兄弟的黑儿子变成兄弟节点
* 按照a的情况处理
*
* 处理溢出的时候,防止将黑色节点的红色子节点染黑,导致不平衡。
* </pre>
*
* @param node
*/
private void afterRemove(RBNode<E> node) {
RBNode<E> currentNode;
boolean underflow = false;
do {
currentNode = node;
if (node.parent == null) {//删除的是根节点
if (underflow) {//下溢不做处理
underflow = false;
} else {//删除度为1或者0的根节点导致的。如果删除的是度为1的根节点,这个时候要将根节点的红色子节点染黑
if (isRed(node.left)) {
black(node.left);
}
if (isRed(node.right)) {
black(node.right);
}
}
} else {//删除的是非根节点
if (isRed(node)) {//删除的是红色节点,不用调整,因为不会影响红黑树的性质
} else {//删除的是黑色节点。
// 删除非根节点的黑色子节点,被删除的节点必定有兄弟(性质5)
// 借的顺序是 兄弟 --> 兄弟的儿子 --> 父亲
//1. 黑(黑色叶子节点,下溢的黑色节点也要当做黑色叶子节点处理)
if (underflow || isBlack(node.left) && isBlack(node.right)) {
underflow = false;
//需要找兄弟节点借,如果兄弟节点没有,就找父节点借,节点会下溢
RBNode<E> parent = node.parent;
RBNode<E> sibling;
if (parent.hasTwoChildren()) {//在下溢的时候
sibling = node.isLeftChild() ? parent.right : parent.left;
} else {//在正常删除节点的时候,必然有一个子节点
sibling = parent.left == null ? parent.right : parent.left;
}
//兄弟节点是红色(兄弟必定有黑儿子),将兄弟的黑儿子变成兄弟
if (isRed(sibling)) {
red(parent);
black(sibling);
if (sibling.isLeftChild()) {
rotateRight(parent);
sibling = parent.left;
} else {
rotateLeft(parent);
sibling = parent.right;
}
}
//兄弟节点都是黑色的
if (isRed(sibling.left) || isRed(sibling.right)) {//兄弟有红色子节点,兄弟可以借
if (sibling.isLeftChild()) {//L
//先看左边有没有红色子节点,有的话。优先按照LL处理
//没有的话就是LR。通过左旋可以转成LL
if (isBlack(sibling.left)) {//LR
rotateLeft(sibling);
sibling = sibling.parent;
}
//此时都转成了LL情况,统一按照LL情况处理
if (colorOf(parent) == RBNode.BLACK) {
black(sibling);
} else {
red(sibling);
}
rotateRight(parent);
} else {
//RL转成RR来处理
if (isBlack(sibling.right)) {//RL
rotateRight(sibling);
sibling = sibling.parent;
}
//此时都转成了RR情况,统一按照RR情况处理
if (colorOf(parent) == RBNode.BLACK) {
black(sibling);
} else {
red(sibling);
}
rotateLeft(parent);
}
black(parent);
black(parent.sibling());
} else {//兄弟只有黑色子节点或者兄弟没有子节点。兄弟无法借出,需要从父节点借(父节点是黑色会产生下溢)
if (isRed(parent)) {
black(parent);
red(sibling);
} else {
red(sibling);
node = parent;
//实际节点是红<-黑,黑色节点下溢了。需要再执行afterRemove,但是不能让它被当有红色子节点的黑色节点处理
//而是当做被没有子节点的黑色节点处理(黑)
underflow = true;
}
}
} else {//1. 红<-黑 2. 黑->红 度为1的黑色节点
//将红色子节点染黑
if (isRed(node.left)) {
black(node.left);
} else if (isRed(node.right)) {
black(node.right);
}
}
}
}
} while (currentNode != node);
}
@SuppressWarnings("unchecked")
private int compare(E e1, E e2) {
if (comparator != null) {
return comparator.compare(e1, e2);
}
return ((Comparable<E>) e1).compareTo(e2);
}
private int colorOf(RBNode<E> node) {
return node == null ? RBNode.BLACK : node.color;
}
private boolean isBlack(RBNode<E> node) {
return colorOf(node) == RBNode.BLACK;
}
private boolean isRed(RBNode<E> node) {
return colorOf(node) == RBNode.RED;
}
private void red(RBNode<E> node) {
if (node != null) {
node.color = RBNode.RED;
}
}
private void black(RBNode<E> node) {
if (node != null) {
node.color = RBNode.BLACK;
}
}
/**
* 检查当前红黑树是否符合红黑树的第1,4,5点性质
*/
public void checkRBTreeProperties() {
if (!isBlack(root)) {//不符合性质(1)
throw new IllegalStateException("root node color isn't black. root = " + root);
}
if (root == null) {
return;
}
//黑节点数量
int rootNodeBlackHeight = 0;
RBNode<E> calHeightNode = root;
while (calHeightNode != null) {
if (isBlack(calHeightNode)) {
rootNodeBlackHeight++;
}
calHeightNode = calHeightNode.left;
}
rootNodeBlackHeight++;//加入NIL节点到统计的总数中
Queue<RBNode<E>> nodeQueue = new LinkedList<>();
nodeQueue.offer(root);
while (!nodeQueue.isEmpty()) {
RBNode<E> node = nodeQueue.poll();
if (!node.hasTwoChildren()) {//计算度为1或者度为0的节点到根节点的黑色节点数量是否等于rootNodeBlackHeight,以及是否存在两个连续红色节点
RBNode<E> iteratorNode = node;
int blackCount = 0;
int preColor = RBNode.BLACK;
while (iteratorNode != null) {//往根节点遍历,看是否存在连续两个红色节点
if (isBlack(iteratorNode)) {
blackCount++;//统计黑色节点数量
} else {//当前遍历的节点是红色
if (preColor == RBNode.RED) {//前一个节点也是红色,不满足性质(4)
throw new IllegalStateException("This is not a red-black tree. Because has continues reb node at " + node);
}
}
preColor = iteratorNode.color;
iteratorNode = iteratorNode.parent;
}
blackCount++;//加入NIL节点到统计的总数中
if (blackCount != rootNodeBlackHeight) {//黑色节点数量不相等,不满足性质(5)
throw new IllegalStateException("This is not a red-black tree. " +
"Because blackCount = " + blackCount + " rootNodeBlackHeight = " + rootNodeBlackHeight + " at " + node);
}
}
if (node.left != null) {
nodeQueue.offer(node.left);
}
if (node.right != null) {
nodeQueue.offer(node.right);
}
}
}
public RBNode<E> createNode(E element, RBNode<E> parent) {
return new RBNode<>(element, parent);
}
public static void main(String[] args) {
RedBlackTree<Integer> rbtree = new RedBlackTree<>();
for (int i = 0; i < 10; i++) {
Random ran = new Random();
HashSet<Integer> hs = new HashSet<>();
for (;;) {
int tmp = ran.nextInt(1000)+1;
hs.add(tmp);
if(hs.size() == 100) break;
}
for (Integer datum : hs.toArray(new Integer[0])) {
System.out.println("add: " + datum);
rbtree.add(datum);
rbtree.checkRBTreeProperties();
}
for (Integer datum : hs.toArray(new Integer[0])) {
System.out.println("remove: " + datum);
rbtree.remove(datum);
rbtree.checkRBTreeProperties();
}
}
}
}