红黑树
红黑树是自平衡的二叉查找树。它是一种查找、增加、删除效率都比较均衡的二叉查找树,增加或者删除的时候,只要能够保证操作后的树结构从根到叶子节点的最长路径不会是最短路径的两倍,那么就不会触发平衡策略进行旋转,要知道,旋转真的是非常耗时的。
具有以下性质:
每个节点要么是红的,要么是黑的。
根节点一定是黑的。
每个叶子节点都是黑色的空节点(NIL节点)。
如果一个节点是红的,那么它的两个子节点都是黑的。
从任意节点到其每个叶子的所有路径都包含相同数目的黑色节点。
用上面五个条件,我们可以模拟推导出一个结论:即红黑树确保从根到叶子节点的最长路径不会是最短路径的两倍,用非严格的平衡策略来换取增、删节点时,旋转次数的降低,任何不平衡都会在三次旋转之内解决。
使用场景:
广泛用在C++的STL中。
注明的linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块。
epoll在内核中的实现,用红黑树管理事件块。
nginx中,用红黑树管理timer等。
Java 8中的HashMap当链表长度大于8时,转为红黑树进行存储。
红黑树的查询性能略逊于AVL树,因为AVL树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的AVL树最多多一次比较,但是,红黑树在插入和删除上完爆AVL树,AVL树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑转变和旋转的开销,相较于AVL树为了维持平衡的开销要小得多。
旋转程序
当前节点用node表示;
父节点用father表示;
爷爷节点用grandpa表示;
叔叔节点用uncle表示;
while (node != null && node != root && node.parent.color == RED) {
// 父节点是爷爷节点的左子树
if (parentOf(node) == leftOf(grandpaOf(node))) {
RBTreeNode uncleRight = rightOf(grandpaOf(node));
/*
判断父节点和叔叔节点是否都为红色
父节肯定为红色,除非是根节点,一旦是黑色,压根就不用左旋转或者右旋转
*/
if (colorOf(uncleRight) == RED) {
setColor(parentOf(node), BLACK);
setColor(uncleRight, BLACK);
setColor(grandpaOf(node), RED);
node = grandpaOf(node);
} else {
// ①左右情况使用左旋转,
if (node == rightOf(parentOf(node))) {
node = parentOf(node);
this.leftRotate(node);
}
/*
经过步骤1后,变成了左左,或者直接就是左左,进行爷爷节点的右旋
*/
this.setColor(parentOf(node), BLACK);
this.setColor(grandpaOf(node), RED);
this.rightRotate(grandpaOf(node));
}
} else { // 否则就是右子树
RBTreeNode uncleLeft = leftOf(grandpaOf(node));
if (colorOf(uncleLeft) == RED) {
setColor(parentOf(node), BLACK);
setColor(uncleLeft, BLACK);
setColor(grandpaOf(node), RED);
node = grandpaOf(node);
} else {
// ②右左情况使用右旋
if (node == leftOf(parentOf(node))) {
node = parentOf(node);
this.rightRotate(node);
}
/*
经过步骤2后变成了右右,或者直接就是右右,左旋
*/
this.setColor(parentOf(node), BLACK);
this.setColor(grandpaOf(node), RED);
this.leftRotate(grandpaOf(node));
}
}
}
root.color = BLACK;