以下是学习恋上数据结构与算法的记录,本篇主要内容是红黑树
◼红黑树(RedBlackTree)
◼红黑树也是一种自平衡的二叉搜索树,以前也叫做平衡二叉B树(Symmetric Binary B-tree)
◼红黑树必须满足以下5 条性质
1.节点是RED 或者BLACK
2.根节点是BLACK
3.叶子节点(外部节点,空节点null)都是BLACK(虚拟出的节点)
4.RED 节点的子节点都是BLACK
✓RED 节点的parent 都是BLACK
✓从根节点到叶子节点的所有路径上不能有2 个连续的RED 节点
5.从任一节点到叶子节点的所有路径都包含相同数目的BLACK 节点
◼红黑树的等价变换
●红黑树和4阶B树(2-3-4树)具有等价性(下图省略了NULL 节点)
●BLACK 节点与它的RED子节点融合在一起,形成1个B树节点
●红黑树的BLACK 节点个数与4阶B树的节点总个数相等
◼添加
已知:B树中,新元素必定是添加到叶子节点中,4阶B树所有节点的元素个数x 都符合1 ≤ x≤ 3
●新添加的节点默认为RED,这样能够让红黑树的性质尽快满足(性质1、2、3、5 都满足,性质4 不一定)
◼如果添加的是根节点,染成BLACK 即可
◼有4 种情况满足红黑树的性质4 :parent 为BLACK
同样也满足4阶B树的性质,因此不用做任何额外处理
◼有8 种情况不满足红黑树的性质4 :parent 为RED(DoubleRed )
其中前4 种属于B树节点上溢的情况
添加–修复性质4 –上溢
◼判定条件:uncle (叔父节点,parent的兄弟节点)是RED
1.parent(父节点)、uncle 染成BLACK
2.grand(祖父节点) 向上合并,染成RED,当做是新添加的节点进行处理
◼grand 向上合并时,可能继续发生上溢,若上溢持续到根节点,只需将根节点染成BLACK。
其中后4 种情况
添加–修复性质4 –LL\RR
◼判定条件:uncle 不是RED
1.parent 染成BLACK,grand 染成RED
2.grand 进行单旋操作
●LL:右旋转
●RR:左旋转
添加–修复性质4 –LR\RL
◼判定条件:uncle 不是RED
1.自己染成BLACK,grand 染成RED
2.进行双旋操作
●LR:parent 左旋转,grand 右旋转
●RL:parent 右旋转,grand 左旋转
protected void afterAdd(Node<E> node) {
Node<E> parent = node.parent;
// 添加的是根节点 或者 上溢到达了根节点
if (parent == null) {
black(node);
return;
}
// 如果父节点是黑色,直接返回
if (isBlack(parent)) return;
// 叔父节点
Node<E> uncle = parent.sibling();
// 祖父节点
Node<E> grand = red(parent.parent);
if (isRed(uncle)) { // 叔父节点是红色【B树节点上溢】
black(parent);
black(uncle);
// 把祖父节点当做是新添加的节点
afterAdd(grand);
return;
}
// 叔父节点不是红色
if (parent.isLeftChild()) { // L
if (node.isLeftChild()) { // LL
black(parent);
} else { // LR
black(node);
rotateLeft(parent);
}
rotateRight(grand);
} else { // R
if (node.isLeftChild()) { // RL
black(node);
rotateRight(parent);
} else { // RR
black(parent);
}
rotateLeft(grand);
}
}
◼删除:
B树中,最后真正被删除的元素都在叶子节点中
删除–RED节点:直接删除,不用作任何调整
删除–BLACK节点:
●拥有2 个RED 子节点的BLACK 节点
✓不可能被直接删除,因为会找它的子节点替代删除
✓因此不用考虑这种情况
●拥有1 个RED 子节点的BLACK 节点
●BLACK 叶子节点
◼删除–拥有1个RED子节点的BLACK节点
●判定条件:用以替代的子节点是RED
●将替代的子节点染成BLACK 即可保持红黑树性质
删除–BLACK叶子节点–sibling(兄弟节点)为BLACK
◼BLACK 叶子节点被删除后,会导致B树节点下溢(比如删除88)
◼如果sibling 至少有1 个RED 子节点,进行旋转操作,旋转之后的中心节点继承parent 的颜色,旋转之后的左右节点染为BLACK(和兄弟节点借一位节点)若兄弟节点没有节点可以借。
◼判定条件:sibling 没有1 个RED 子节点
●将sibling 染成RED、parent 染成BLACK 即可修复红黑树性质
◼如果parent 是BLACK,会导致parent 也下溢,这时只需要把parent 当做被删除的节点处理即可
删除–BLACK叶子节点–sibling为RED
◼如果sibling 是RED,sibling 染成BLACK,parent 染成RED,进行旋转,于是又回到sibling 是BLACK 的情况
protected void afterRemove(Node<E> node) {
// 如果删除的节点是红色
// 或者 用以取代删除节点的子节点是红色
if (isRed(node)) {
black(node);
return;
}
Node<E> parent = node.parent;
// 删除的是根节点
if (parent == null) return;
// 删除的是黑色叶子节点【下溢】
// 判断被删除的node是左还是右
boolean left = parent.left == null || node.isLeftChild();
Node<E> sibling = left ? parent.right : parent.left;
if (left) { // 被删除的节点在左边,兄弟节点在右边
if (isRed(sibling)) { // 兄弟节点是红色
black(sibling);
red(parent);
rotateLeft(parent);
// 更换兄弟
sibling = parent.right;
}
// 兄弟节点必然是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
// 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if (parentBlack) {
afterRemove(parent);
}
} else { // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
// 兄弟节点的左边是黑色,兄弟要先旋转
if (isBlack(sibling.right)) {
rotateRight(sibling);
sibling = parent.right;
}
color(sibling, colorOf(parent));
black(sibling.right);
black(parent);
rotateLeft(parent);
}
} else { // 被删除的节点在右边,兄弟节点在左边
if (isRed(sibling)) { // 兄弟节点是红色
black(sibling);
red(parent);
rotateRight(parent);
// 更换兄弟
sibling = parent.left;
}
// 兄弟节点必然是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
// 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if (parentBlack) {
afterRemove(parent);
}
} else { // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
// 兄弟节点的左边是黑色,兄弟要先旋转
if (isBlack(sibling.left)) {
rotateLeft(sibling);
sibling = parent.left;
}
color(sibling, colorOf(parent));
black(sibling.left);
black(parent);
rotateRight(parent);
}
}
}
//辅助方法
//染色方法
private Node<E> color(Node<E> node, boolean color) {
if (node == null) return node;
((RBNode<E>)node).color = color;
return node;
}
//染红色
private Node<E> red(Node<E> node) {
return color(node, RED);
}
//染黑色
private Node<E> black(Node<E> node) {
return color(node, BLACK);
}
//判断颜色
private boolean colorOf(Node<E> node) {
return node == null ? BLACK : ((RBNode<E>)node).color;
}
//判断是否是黑色节点
private boolean isBlack(Node<E> node) {
return colorOf(node) == BLACK;
}
//判断是否是红色节点
private boolean isRed(Node<E> node) {
return colorOf(node) == RED;
}
//创建节点方法
protected Node<E> createNode(E element, Node<E> parent) {
return new RBNode<>(element, parent);
}
//红黑树节点类
private static class RBNode<E> extends Node<E> {
boolean color = RED;
public RBNode(E element, Node<E> parent) {
super(element, parent);
}
@Override
public String toString() {
String str = "";
if (color == RED) {
str = "R_";
}
return str + element.toString();
}
}
红黑树的实现关键是添加删除后节点的颜色一定要符合红黑树的性质
红黑树的平衡
最初遗留的困惑:为何那5条性质,就能保证红黑树是平衡的?
◼因为那5条性质,可以保证红黑树等价于4阶B树
相比AVL树,红黑树的平衡标准比较宽松:
●没有一条路径会大于其他路径的2倍
●是一种弱平衡、黑高度平衡
●红黑树的最大高度是2∗log2(n+1),依然是O(logn) 级别
AVL树vs 红黑树
◼AVL树:
平衡标准比较严格:每个左右子树的高度差不超过1
最大高度是1.44∗log2n+2−1.328(100W个节点,AVL树最大树高28)
搜索、添加、删除都是O(logn) 复杂度,其中添加仅需O(1) 次旋转调整、删除最多需要O(logn) 次旋转调整
◼红黑树:
平衡标准比较宽松:没有一条路径会大于其他路径的2倍
最大高度是2∗log2(n+1)(100W个节点,红黑树最大树高40)
搜索、添加、删除都是O(logn) 复杂度,其中添加、删除都仅需O(1) 次旋转调整
搜索的次数远远大于插入和删除,选择AVL树;
搜索、插入、删除次数几乎差不多,选择红黑树
相对于AVL树来说,红黑树牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树
红黑树的平均统计性能优于AVL树,实际应用中更多选择使用红黑树