主要是对红黑的平衡进行梳理,帮助手写红黑树
红黑树的原则:
- 每个节点或者是黑色,或者是红色。
- 根节点是黑色。
- 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
以下都是错误示范
注意以下几个约定
- 节点结构
class RbNode<K extends Comparable<K>, V> {
K key;
V value;
RbNode left;
RbNode right;
RbNode parent;
boolean color;
public static final boolean red = true;
public static final boolean black = false;
public RbNode(K k, V v) {
key = k;
value = v;
color = red;
}
}
- 测试网址:https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
- nil节点不会明确标识
- 下文的中描述的叶子节点 都是 nil节点的父节点
- 节点名称描述参考
左旋和右旋:
参考AVL树,不同的是,红黑树要调整父节点指向
private RbNode rightRotate(RbNode cur) {
RbNode left = cur.left;
RbNode parent = cur.parent;
// 调整向上指向
cur.parent = left;
left.parent = parent;
if (left.right != null) {
left.right.parent = cur;
}
// 调整向下指向
cur.left = left.right;
left.right = cur;
if (left.parent == null) {
root = left;
} else {
if(left.parent.left == left){
left.parent.left = left;
}else{
left.parent.right = left;
}
}
return left;
}
插入------比较简单用文字描述:
- 为什么插入的不是黑叶子而是红叶子
- 插入红叶子:可能破坏原则4
- 插入黑叶子:可能破坏原则5
编程成本:默认插入的都是红叶子,因为简单
- 平衡调整规则:以左子节点为例(右子节点反之),插入的都是红叶子
- 左子节点是黑色:
- 左左孙子不破坏当前红黑树,不需要平衡
- 左右孙子不破坏当前红黑树,不需要平衡
- 左子节点是红色,右子是黑色(其实是nil):
- 左左孙子破坏当前红黑树,需要平衡:做右旋操作,左子节点变黑色
测试数据:50,30,20
- 左右孙子破坏当前红黑树,需要平衡:做先右旋再右旋操作,左右孙子节点变黑色
测试数据:50,30,40
- 左左孙子破坏当前红黑树,需要平衡:做右旋操作,左子节点变黑色
- 左子节点是红色,右子是红色:
- 左子和右子变黑,父变红
测试数据:50,30,60,20
- 左子和右子变黑,父变红
- 左子节点是黑色:
private RbNode add(RbNode cur, K k, V v) {
if (cur == null) {
// 新创建的
return new RbNode(k, v);
} else if (cur.key.compareTo(k) == 0) {
cur.value = v;
return cur;
}
RbNode node = null;
if (cur.key.compareTo(k) > 0) {
RbNode left = cur.left;
node = add(cur.left, k, v);
if (left == null) {
// 新增加的, 因为有递归调用, 所以要增加这个判断
node.parent = cur;
cur.left = node;
}
} else if (cur.key.compareTo(k) < 0) {
....
}
return maintain(node);
}
/**
* 返回调整后的最高节点
* @param cur
* @return
*/
private RbNode maintain(RbNode cur) {
if (cur == null) {
return null;
}
if (cur.getParent() == null) {
// 父 = null
// 1. 新創建的 尚未關聯
// 2. root
// 返回當前
return cur;
}
if (cur.getParentColor() != cur.color || cur.getParentParent() == null) {
// 当前和父颜色不一样, 并未违反原则
// 祖父节点不存在,说明在前一次递归孙子节点已经检测, 无须在遍历一遍
return cur.getParent();
}
// 父是紅色
RbNode parentParent = cur.getParentParent();
RbNode parent = cur.getParent();
if (cur.getParentBrotherColor() == RbNode.red) {
// 兄弟是紅色
cur.setParentColor(RbNode.black)
.setParentParentColor(RbNode.red)
.setParentBrotherColor(RbNode.black);
return cur.getParentParent();
}
if (parentParent.left == parent) {
// 左子
if (parent.right == cur) {
// 左右
parent = leftRotate(parent);
}
// 左左
parent.color = RbNode.black;
parentParent.color = RbNode.red;
parentParent = rightRotate(parentParent);
} else {
....
}
return parentParent;
}
删除:
- 根
- 根(无子)节点:直接删除
- 根(有子)节点:参考节点
- 节点
- 两个子节点:转换成叶子
- 一个子节点:先删除,再变色调整
- 叶子
- 红叶子:直接删除
- 黑叶子:以左边黑叶子为例,右边反之
- 兄弟节点是黑色
- 左侄子和右侄子都是null或者是黑色:直接变色
- 左侄子是红色:右旋+左旋
- 右侄子是红色:左旋
- 左侄子和右侄子都是红色:左旋
- 兄弟节点是红色:左旋
- 左侄子和右侄子都是黑色:参考兄弟节点是黑色
- 兄弟节点是黑色
一图圣千言:
public V remove(K k) {
RbNode node = getNode(root, k);
size--;
if (node == null) { return null; }
V value = (V) node.value;
if (node == root && node.left == null && node.right == null) {
// 没有子节点的root 直接删除
root = null;
return value;
}
if (node.left != null && node.right != null) {
// 双子节点, 需要转叶子
RbNode approach = node.right;
while (approach != null) {
approach = approach.left;
}
node.key = approach.key;
node.value = approach.value;
node = approach;
}
if (node.left == null && node.right == null) {
// 叶子
RbNode parent = node.parent;
if(node.color == RbNode.black){
// 黑叶子需要调整, 红叶子不需要
// 根据总结,判定条件都是 当前节点的兄弟节点和其侄子节点
// 所以调整需要放在删除前面
maintainDel(node);
}
if(parent != null){
// 删除操作
if (parent.left == node) { parent.left = null; } else { parent.right = null; }
node.parent = null;
}
} else {
// 单节点
RbNode child = node.left != null ? node.left : node.right;
// 其中一个不为空的子节点
if(node.parent == null){
// root 单节点情况,直接换root
root = child;
maintainDel(child);
}else{
// 删除操作
if (node.parent.left == node) { node.parent.left = child; } else { node.parent.right = child; }
child.parent = node.parent;
// 黑红情况需要调整
if(node.color == RbNode.black){
// 递归调整
maintainDel(child);
}
}
}
return value;
}
private void maintainDel(RbNode node) {
if (node == null) { return ; }
else if (node == root) { node.color = RbNode.black; node.parent = null; return ; }
else if (node.color == RbNode.red) {
// 当前节点是红色,
// 1. 叶子情况下,删除红色节点是可以直接删除的,不需要调整
// 2. 如果cur是中间某个节点,删除的节点是黑色,那么删除后缺一个黑色节点,
// 遇到红色,就直接变黑,就可以补充缺的哪个了
node.color = RbNode.black;
return ;
}
RbNode parent = node.getParent();
// 当前节点是黑色
// 兄弟节点必定存在
RbNode brother = node.getBrother();
if (parent.left == node) {
if (node.getBrotherColor() == RbNode.red) {
// 兄弟节点是红色
brother.color = RbNode.black;
parent.color = RbNode.red;
leftRotate(parent);
brother = node.getBrother();
}
// 兄弟节点是黑色
if (brother.getLeftColor() == RbNode.black && brother.getLeftColor() == RbNode.black) {
// 左右侄子节点都是黑色
brother.color = RbNode.red;
maintainDel(parent);
} else {
// 其中一个侄子节点是红色
if (brother.getRightColor() == RbNode.black) {
// 左侄子是红色
brother.color = RbNode.red;
brother.left.color = RbNode.black;
rightRotate(brother);
brother = node.getBrother();
}
// 右侄子是红色
parent = node.getParent();
brother.color = parent.color;
parent.color = RbNode.black;
brother.right.color = RbNode.black;
leftRotate(parent);
// 这个一轮操作就是把 node节点的上一层调整成满足红黑树条件
// 调整过程中会影响root,所以需要调整root
maintainDel(root);
}
} else {
...
}
return ;
}
最后我整理比较完整的红黑树删除的脑图: