红黑树学习 java实现简单的红黑树
remove
传入要删除的值
public Integer remove(Integer val) {
// 找到值为val的结点
Node p = getNode(val);
// 找不到则直接返回null
if (p == null) return null;
Integer oldValue = p.value;
// 删除找到的结点
deleteNode(p);
return oldValue;
}
deleteNode
用二叉搜索树的删除方法, 因为红黑树实际上是二叉搜索树的变形
删除的结点有三种情况
- p有两个孩子, 那么从右孩子中找到最小的结点与p值交换, 删除这个最小结点, 因为是二叉搜索树, 所以这个结点一定是右孩子中最左的孩子, 所以他没有左孩子, 可以进入情况2
- p只有一个孩子, 直接将这个孩子与p的父亲进行连接操作, 操作完成后, 如果p是黑色则调整红黑树
- p没有孩子, 直接将p删除, p如果是黑色, 那就调整红黑树
因为如果p是红色, 就不会改变红黑树的稳态, 所以无需调整红黑树, 如果是黑色, 那么在路径上该子树就会比兄弟子树的黑色结点数目少1, 要进行维稳
public void deleteNode(Node p) {
size--;
// p有两个孩子, 从右孩子中找到最小的值交换
if (p.left != null && p.right != null) {
Node s = minInRight(p);
p.value = s.value;
p = s;
}
// 需要删除的结点, 若与其他结点交换过, 那么当前p一定没有左孩子
Node replacement = (p.left == null ? p.right: p.left);
// 由上所得, replacement如果不是空, 那么一定是p的右孩子或者左孩子
// 如果p是黑色, 那么p的左孩子或者右孩子如果是个黑色有值结点就会破坏红黑树
if(replacement != null) {
//要删除p, 所以将p唯一一个孩子与p的父亲连接
replacement.parent = p.parent;
if(p.parent == null) {
root = replacement;
} else if(p == p.parent.left) {
p.parent.left = replacement;
} else {
p.parent.right = replacement;
}
p.left = p.right = p.parent = null;
// 如果删除的结点是黑色, 那么会破坏红黑树的稳态, 所以进行调整
// 这里的操作是直接变黑, 因为p是黑色的话replacement一定是红色
if(p.color == BLACK) fixAfterDel(replacement);
} else if(p.parent == null) {
// p是红黑树中的惟一一个结点
root = null;
} else {
// p没有孩子, 直接将自己删除
// 如果p是黑色, 进行调整
if(p.color == BLACK) {
fixAfterDel(p);
}
if(p.parent != null) {
if(p == p.parent.left) {
p.parent.left = null;
} else if(p == p.parent.right) {
p.parent.right = null;
}
p.parent = null;
}
}
}
这里是找到可以替换删除的结点的函数
static Node minInRight(Node t) {
if(t == null) return null;
if(t.right == null) return null;
// t的右孩子不为空, 找到右孩子中最小的结点, 即找到右孩子中最左的孩子
Node p = t.right;
while (p.left != null) p = p.left;
return p;
}
fixAfterDel
调整删除结点后的红黑树
这里的几种情况可以参考这个链接红黑树删除图解
我这里引用一张图
总而言之就是把所有的情况都转为2.2.1
private void fixAfterDel(Node x) {
// x不是根节点并且是黑色就进行循环
// 这里如果replacement是红色的, 直接变黑就可以保持稳态
while(x != root && colorOf(x) == BLACK) {
if(x == leftOf(parentOf(x))) {
// 由于上面删除了p, 所以x树的黑结点比bro少1
Node bro = rightOf(parentOf(x));
// 如果兄弟是红色, 就用旋转把兄弟变黑, 并且保持少1状态
// 因为兄弟是红色, 所以爹一定是黑色
if(colorOf(bro) == RED) {
setColor(bro, BLACK);
setColor(parentOf(x), RED);
rotateLeft(parentOf(x));
// 此时的x是转过来的爹, bro是原来bro的右孩子
bro = rightOf(parentOf(x));
}
// 到了这里bro一定是黑色
// 如果bro的两个孩子都是黑色, 只需要将bro变红
// 然后判断爹, 如果爹是红色直接跳出循环变黑就行
// 否则以爹为入参重新调整, 因为bro变红之后, 这一棵子树的黑结点少了1
if(colorOf(rightOf(bro)) == BLACK && colorOf(leftOf(bro)) == BLACK) {
setColor(bro, RED);
x = parentOf(x);
} else { // 全红或者一红一黑
// 如果右侄子是黑色, 那么进行右旋, 将新的右侄子变为红色
if(colorOf(rightOf(bro)) == BLACK) {
// 将左侄子变黑, 兄弟变红然后右旋
// 完成之后, 兄弟还是黑色, 原来的红色兄弟变成右侄子
setColor(leftOf(bro), BLACK);
setColor(bro, RED);
rotateRight(bro);
bro = rightOf(parentOf(x));
}
// 下面是对右侄子为红色, 左侄子为黑色或者全红的处理
// 经过上一步之后, 保证了右侄子不是黑色
// 为了维稳, 必须要让x树多一个黑色结点
// bro将要变为爹, 所以让bro保持爹的颜色, 只需将左右子树的黑色维稳
setColor(bro, colorOf(parentOf(x)));
// 最后bro会变成爹, 爹变成左子树, 由于原来的bro是黑色
// 所以让将会变成bro的bro的右孩子涂黑
// 这样左边多了一个黑色结点, 右边的黑色结点保持不变
setColor(parentOf(x), BLACK);
setColor(rightOf(bro), BLACK);
rotateLeft(parentOf(x));
// 跳出循环
x = root;
}
} else { // 对称操作
Node bro = leftOf(parentOf(x));
if(colorOf(bro) == RED) {
setColor(bro, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x));
bro = parentOf(x);
}
if(colorOf(rightOf(bro)) == BLACK && colorOf(leftOf(bro)) == BLACK) {
setColor(bro, RED);
x = parentOf(x);
} else {
if(colorOf(leftOf(bro)) == BLACK) {
setColor(rightOf(bro), BLACK);
setColor(bro, RED);
rotateLeft(bro);
bro = leftOf(parentOf(x));
}
setColor(bro, colorOf(parentOf(x)));
setColor(leftOf(bro), BLACK);
setColor(parentOf(x), BLACK);
rotateRight(parentOf(x));
x = root;
}
}
}
// 将x设为黑色, 这里有两种情况
// 1. x是红色, 这里变黑之后保持了黑结点的稳态
// 2. x是根节点, 根节点本来就是黑色的, 再变黑一次也没关系
setColor(x, BLACK);
}
后记
其实这里的代码与TreeMap的源码基本一样, 只是将Node变成了简单的结点而不是键值对, 可以说这就是对TreeMap的源码学习吧