一、AVL树VS红黑树
增加的最坏时间复杂度 | 删除的最坏时间复杂度 | 查询的最坏时间复杂度 | |
AVL树 | O(log N) + O(1) | O(log N) + O(log N) | O(N) + O(1) |
---|---|---|---|
红黑树 | O(log N) | O(log N) | O(log N) |
工欲善其事必先利其器,要想搞懂红黑树,还要先学习B树。红黑树和四阶B树是完全对应的。
一、B树
1、简述及特点
B树其实就是多路的搜索树,常用于文件系统,数据库实现(一般是好几百阶),Linux进程调度等等。
假设一个结点存储的元素个数是x,那么有以下结论:
根结点:1 < x < m-1
非根结点:m/2向上取整 - 1 < x < m-1
如果有子结点,则子结点个数为x + 1
仔细观察上面的5阶B树,其实它也是一棵多叉搜索树,结点之间大小关系是固定的,同一结点内部也是从小到大从左往右排的。
2、添加情况
添加的元素都是添加到叶子结点的。
由于B树每个结点存储的元素有上限,添加的时候可能会造成上溢,如果没有上溢就是在结点里面找到对应位置插入即可。发生上溢后的处理方式很简单:将最中间的结点拉到父结点上面合并,两端分裂为独立的子结点,合并完后指向分裂出来的子结点。不过这么做可能会导致一直上溢到根结点。如下图:
3、删除情况
由于B树每个结点存储的元素有下限,删除的时候可能会造成下溢,如果没有下溢就是找到元素位置直接删除即可。发生下溢后的处理方式很简单:先看看自己的兄弟结点能不能借一个结点出来。如果兄弟存储的元素比较多可以借,则执行相应的旋转操作;如果借不了,将父结点最中间的元素拉下来合并。不过这么做也许会导致一直下溢到根结点。如下图:
二、红黑树(心中有B树)
1、简述及特点
红黑树是一种特殊的二叉搜索树。红黑树的每个结点上都有存储位表示结点的颜色,可以是红(Red)或黑(Black)。
红黑树有以下特点:
结点必须是黑色或者红色
根结点必须是黑色
叶子结点和外部结点都是黑色的
红色结点的孩子都是黑色结点,红色结点的父结点都是黑色结点
从根结点到叶子结点的所有路径上不能包括两个连续的红色结点
从任意结点到叶子结点的所有路径包含相同数量的黑色结点
红黑树和四阶B树的等价转换:
2、添加情况
------父结点为黑色,直接添加即可。
------父结点为红色:
------叔叔结点为红色,添加会上溢,参考B树解决(解决后上层可能还会上溢,递归搞定)。
------叔叔节点为黑色,将与B树等价转换后的超级结点执行相应的旋转操作即可。
3、删除情况
------被删除的结点为红色,直接删除即可。
------被删除的结点为黑色:
------被删除的结点所在的与B树等价转换后的超级结点中有红色结点,直接染色即可。
------被删除的结点所在的与B树等价转换后的超级结点中没有红色结点:
------兄弟结点为红色,执行相应的旋转操作把兄弟结点变为黑色,再按照兄弟结点为黑色的情况处理即可。
------兄弟结点为黑色:
------兄弟结点可以借出一个红色结点,执行相应的旋转操作即可。
------兄弟结点借不出红色结点,将父结点中拉一个结点下来合并(可能会造成连续下溢,递归解决)。
4、注意点
写代码要搞明白以下几点:
① 红黑树的颜色划分?
新建的结点默认用红色会快速平衡。红黑树的空结点也是有颜色的,是黑色!
② 删除某个结点之后,父亲结点的指向?
结点已经被删除了,自然不能用父结点的left或right来获取该结点的兄弟结点。可以反其道而行之,直接判断那边为空就表示删除的是哪一边,那么兄弟结点必然在非空的一边。
③ 下溢解决时,递归调用函数问题?
递归调用的时候,其实只是假设那个父结点要被删除,事实上没有被删除,此时再用问题2的解决方法就会有问题了。此时正解应该用parent.left == node进行判断再来获取兄弟结点。
4、代码实现
public class RedBlackTree<E> extends BBST<E> {
private static final int RED = 1;
private static final int BLACK = 0;
private class RBNode<E> extends Node<E> {
int color = RED;
public RBNode(Node parent, E element) {
super(parent, element);
}
public void ToBlack() {
this.color = BLACK;
}
public void ToRed() {
this.color = RED;
}
public void ToColor(int color) {
this.color = color;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
if (this.color == RED)
str.append("Red_");
else
str.append("Black_");
str.append(this.element);
return str.toString();
}
}
private boolean isBlack(Node<E> node) {
return node == null ? true : ((RBNode<E>) node).color == BLACK; //节点为空默认为黑色呀
}
private boolean isRed(Node<E> node) {
return node == null ? false : ((RBNode<E>) node).color == RED;
}
private void ToBlack(Node<E> node) {
((RBNode<E>) node).ToBlack();
}
private void ToRed(Node<E> node) {
((RBNode<E>) node).ToRed();
}
private void ToColor(Node<E> node, int color) {
((RBNode<E>) node).ToColor(color);
}
@Override
protected Node<E> createNode(Node<E> parent, E element) {
return new RBNode<>(parent, element);
}
/**
* 红黑树增加结点后保持红黑树特性的方法
*
* @param node 新增的结点
*/
@Override
protected void afterAdd(Node<E> node) { //红黑树添加分为两大类: 1、父结点为黑色则直接添加; 2、父结点为红色再次分类
if (root == node) { //根结点直接刷成黑色即可
ToBlack(node);
return;
}
Node<E> parent = node.parent;
Node<E> grandparent = parent.parent;
if (grandparent == null) //祖父结点为空只可能是父亲是根结点且为黑色, 那么直接插入即可
return;
if (isBlack(parent)) //父结点是黑色直接添加(对应情况1)
return;
//父结点为红色, 又分为两类: 1、叔叔结点为红色即超级结点状态为红黑红; 2、叔叔结点为黑色即超级结点状态为红黑或黑红
Node<E> uncle = grandparent.right == parent ? grandparent.left : grandparent.right;
if (isRed(uncle)) { //叔叔结点为红色, 上溢(对应情况1)
ToBlack(parent);
ToBlack(uncle);
ToRed(grandparent);
afterAdd(grandparent); //祖父结点作为新插入的结点继续递归调用并返回
return;
}
if (grandparent.left == parent) { //叔叔结点为黑色, 超级结点内部旋转(对应情况2)
if (parent.left == node) { //LL
ToBlack(parent);
ToRed(grandparent);
rotateRight(grandparent);
} else { //LR
ToBlack(node);
ToRed(grandparent);
rotateLeft(parent);
rotateRight(grandparent);
}
} else {
if (parent.left == node) { //RL
ToBlack(node);
ToRed(grandparent);
rotateRight(parent);
rotateLeft(grandparent);
} else { //RR
ToBlack(parent);
ToRed(grandparent);
rotateLeft(grandparent);
}
}
}
/**
* 红黑树删除结点后保持红黑树特性的方法
*
* @param node 已经删除的结点
* @param replacement 替代的结点
*/
@Override
protected void afterRemove(Node<E> node, Node<E> replacement) { //红黑树删除分为两大类: 1、要删除的结点为红色直接删除; 2、要删除的结点为黑色再次分类
if (node.parent == null) //被删除结点是根结点直接删掉
return;
if (isRed(node)) //删除的是红色结点也是直接删除即可(对应情况1)
return;
//要删除的结点为黑色再次分类: 1、黑色节结点所在的超级结点中有红色替代结点; 2、没有红色替代结点
if (isRed(replacement)) {
ToBlack(replacement); //将替代结点刷成黑色即可(对应情况1)
return;
}
//删除的相当于是黑色的叶子结点(对应情况2)
Node<E> parent = node.parent;
if (parent == null) //只存在两种情况: 1、只有一个黑色根结点(前面已处理); 2、一个根结点+一个替代结点(前面已经将替代染黑), 所有直接返回即可
return;
//因为该结点已经被删除了, 不能再用parent.left或者parent.right指向该结点了
boolean left = parent.left == null || parent.left == node; //递归删除的时候并没有断掉指向的结点, 所以添加一个条件
Node<E> sibling = left ? parent.right : parent.left;
Node<E> grandparent = parent.parent;
//删除的是黑色的叶子结点再分两类: 1、兄弟结点为黑色; 2、兄弟结点为红色
if (left) { //被删除结点在左边
if (isBlack(sibling)) { //兄弟结点为黑色(对应情况1)
if (isBlack(sibling.left) && isBlack(sibling.right)) { //兄弟结点没有左右孩子(下溢), 从父亲那里拿一个下来合并
if (isRed(parent)) { //父结点是红色, 直接拉下来
ToBlack(parent);
ToRed(sibling);
} else if (isBlack(parent)) { //父结点是黑色, 又会下溢
ToBlack(parent);
ToRed(sibling);
afterRemove(parent, null); //祖父结点佯装为新删除的结点继续递归调用并返回
}
} else if (isRed(sibling.right)) { //兄弟结点有右孩子(都用RR来处理)
int pColor = ((RBNode<E>) parent).color;
ToColor(sibling, pColor); //叔叔结点跟随父结点的颜色, 子结点都刷成黑色
ToBlack(parent);
ToBlack(sibling.right);
rotateLeft(parent);
} else if (isRed(sibling.left)) { //兄弟结点有左孩子(用RL来处理)
int pColor = ((RBNode<E>) parent).color;
ToColor(sibling.left, pColor); //叔叔结点跟随父结点的颜色, 子结点都刷成黑色
ToBlack(parent);
ToBlack(sibling);
rotateRight(sibling);
rotateLeft(parent);
}
} else if (isRed(sibling)) { //兄弟结点为红色, 先变为黑色再处理(对应情况2)
ToBlack(sibling);
ToRed(parent);
rotateLeft(parent);
sibling = parent.right; //旋转后要改变对应的兄弟结点
//又变为兄弟结点为黑色的情况了
if (isBlack(sibling.left) && isBlack(sibling.right)) { //兄弟结点状态: 黑
if (isRed(parent)) {
ToBlack(parent);
ToRed(sibling);
} else if (isBlack(parent)) {
ToBlack(parent);
ToRed(sibling);
afterRemove(parent, null);
return;
}
} else if (isRed(sibling.right)) { //兄弟结点状态: 黑红或者红黑红, 统一处理为RR
int pColor = ((RBNode<E>) parent).color;
ToColor(sibling, pColor);
ToBlack(sibling.right);
ToBlack(parent);
rotateLeft(parent);
} else if (isRed(sibling.left)) { //兄弟结点状态: 红黑, 统一处理为RL
int pColor = ((RBNode<E>) parent).color;
ToColor(sibling.left, pColor);
ToBlack(sibling);
ToBlack(parent);
rotateRight(sibling);
rotateLeft(parent);
}
}
} else if (!left) { //被删除结点在右边(和左边删除情况完全对称)
if (isBlack(sibling)) {
if (isBlack(sibling.left) && isBlack(sibling.right)) {
if (isRed(parent)) {
ToBlack(parent);
ToRed(sibling);
} else if (isBlack(parent)) {
ToBlack(parent);
ToRed(sibling);
afterRemove(parent, null);
}
} else if (isRed(sibling.left)) { //兄弟结点有左孩子(都用LL来处理)
int pColor = ((RBNode<E>) parent).color;
ToColor(sibling, pColor); //叔叔结点跟随父结点的颜色, 子结点都刷成黑色
ToBlack(parent);
ToBlack(sibling.left);
rotateRight(parent);
} else if (isRed(sibling.right)) { //兄弟结点有左孩子(都用LR来处理)
int pColor = ((RBNode<E>) parent).color;
ToColor(sibling.right, pColor); //叔叔结点跟随父结点的颜色, 子结点都刷成黑色
ToBlack(parent);
ToBlack(sibling);
rotateLeft(sibling);
rotateRight(parent);
}
} else if (isRed(sibling)) { //兄弟结点为红色, 先变为黑色再处理(对应情况2)
ToBlack(sibling);
ToRed(parent);
rotateRight(parent);
sibling = parent.left; //旋转后要改变对应的兄弟结点
//又变为兄弟结点为黑色的情况了
if (isBlack(sibling.left) && isBlack(sibling.right)) { //兄弟结点状态: 黑
if (isRed(parent)) {
ToBlack(parent);
ToRed(sibling);
} else if (isBlack(parent)) {
ToBlack(parent);
ToRed(sibling);
afterRemove(parent, null);
}
} else if (isRed(sibling.left)) { //兄弟结点状态: 红黑或者红黑红, 统一处理为LL
int pColor = ((RBNode<E>) parent).color;
ToColor(sibling, pColor);
ToBlack(sibling.left);
ToBlack(parent);
rotateRight(parent);
} else if (isRed(sibling.right)) { //兄弟结点状态: 黑红, 统一处理为LR
int pColor = ((RBNode<E>) parent).color;
ToColor(sibling.right, pColor);
ToBlack(sibling);
ToBlack(parent);
rotateLeft(sibling);
rotateRight(parent);
}
}
}
}
}