学习红黑树前推荐先去了解B 树
红黑树
红黑树也是一种自平衡二叉搜索树,也叫做平衡二叉B树。
红黑树必须满足以下性质
- 节点是Red或者Black
- 根节点是Black
- 叶子结点,外部节点,空节点都是Black
- 红色节点的子节点都是黑色节点(红色节点的父节点都是黑色)(从根节点到叶子结点的所有路径上不能有两个连续的红色节点)
- 从任一节点到叶子结点的所有路径都包含相同数目的黑色节点。
红黑树的等价变换
- 红黑树和4阶B树具有等价性
- 黑节点和和红节点融合在一起形成一个B树节点
- 红黑树的黑节点的个数和4阶B树的节点总个数相等
添加之后维持性质
- 已知B树中,新元素必定是添加到叶子结点中;
- 4阶B树所有节点的元素个数x都符合1<=x<=3。
- 建议新添加的节点默认是红色,这样能够让红黑树的性质尽量满足(1,2,3,5)。
- 如果添加的节点是根节点,染成黑色。
添加时的所有情况
- 有四种情况满足红黑树的性质,parent节点是黑色,同样也满足4阶B树的性质,因此不用做任何处理。
- 另外有8种情况不满足红黑树的性质,也即是parent节点是红色,这就造成了添加的节点和父节点都是红色,不满足性质4,红色节点的子节点都是黑色节点。
LL/RR情况
RR情况
处理步骤
- 判断添加的元素52的叔叔节点是不是红色,如果不是
- 将父节点50染成黑色,将祖父节点46染成红色
- 然后对46进行左旋操作,50成为38的子节点,50也是46,52,48的父节点
操作后的结果
LL的情况和RR基本一样,只是将46左旋改成了76进行右旋。
LR/RL情况
处理步骤
- 如果添加节点的叔父节点不是红色(为空或者黑色)
- 先将添加的节点染成黑色,再将祖父节点染成红色
- LR:先对父节点进行左旋,再对祖父节点进行右旋
- RL:先对父节点进行右旋,再对祖父节点进行左旋
操作后结果
还有四种情况会造成上溢
LL情况
添加10之后会造成叶子结点元素个数等于4,超过4阶B数叶子结点元素个数上限。造成上溢现象。
判断条件:添加节点的叔父节点是红色
处理步骤:
- parent,uncle节点染成黑色
- grand节点合并向上
- 之后将grand节点当成新添加的节点,因此grand节点染成红色
- 向上合并的时候,可能会继续出现上溢的情况,如果上溢到根节点,需要将根节点染成黑色
处理后的结果
RR情况
处理步骤和上面所讲的一样,都是将17,33染成黑色,25向上合并并染成红色
LR情况
处理步骤是将17,33染成黑色,25向上合并并染成红色
RL情况
添加节点的代码实现
public void afterAdd(Node<E> node){
Node parent = node.parent;
//添加的是根节点
if(parent==null){
black(node);
return;
}
//添加的节点的父节点是黑色,直接返回,不用操作
if(isBlack(parent)){
return;
}
//叔父节点
Node<E> uncle = parent.sibling();
//祖父节点
Node<E> hrand = parent.parent;
//判断叔父节点是红色,上溢
if(isRed(uncle)){
//将父节点和祖父节点都染成黑色
black(parent);
black(grand);
//把祖父节点当做新添加的节点
red(grand);
afterAdd(grand);
return;
}
//叔父节点是黑色,并且父节点是祖父节点的左节点
if(parent.isLeftChild()){
//无论LL还是LR都需要将祖父节点染成红色
red(grand);
if(node.isLeftChild){
//LL
//将父节点染成黑色,祖父节点染成红色
black(parent);
}else{
//LR
//当前新增节点染成黑色,祖父节点染成红色
black(node);
//左旋父节点
rotateLeft(parent);
}
//右旋祖父节点,也是共有的操作
rotateRight(grand);
}else{
red(grand);
if(node.isLeftChild()){
//RL
//将当前新增的节点染成黑色
black(node);
//先对父亲节点进行右旋,再对祖父节点进行左旋
rotateRight(parent);
}else{
//RR
//将父亲节点染成黑色,左旋祖父节点
black(parent);
}
//左旋祖父节点公有,提取出来
rotateLeft(grand);
}
}
删除之后维持性质
删除根据颜色划分的话就两种情况,一种是删红色节点,一种是删除黑色节点
删除红色节点,直接删除,没什么问题
删除黑色节点有三种情况
删除拥有两个红色子节点的黑色节点:这种情况是不存在的,因为会找到他的子节点替代删除。
删除拥有一个红色子节点的黑色节点:
判定条件:用以替代的子节点是红色。
操作步骤:
将替代的子节点(50,72)染成黑色,删除待删的节点46和76,这时50的父节点是55,76的父节点是80。
删除后的结果
删除没有子节点的黑色节点:
判断条件:用来替代待删除的节点的子节点是黑色
黑色节点被删除后,会导致B树节点下溢。
如果被删除的兄弟节点至少有一个红色子节点,要进行旋转操作。
旋转之后的中心节点继承删除节点的parent的颜色,旋转之后的左右节点染成黑色。
删除88
删除后
删除88第二种情况
删除后恢复
删除88第三种情况
LL,对80 进行右旋操作,76上去,需要染成红色(因为80就是红色),72和80染成黑色。
还有一种情况,如果被删除的节点的兄弟节点没有红色节点,也就是不能向兄弟节点借。
操作:将兄弟节点染成红色,将父节点拿下来与兄弟节点合并,父节点染成黑色。
这时,如果父节点是根节点会造成根节点页下溢,所以将根节点也作为被删除的节点处理(递归调用afterRemove(parent))
最后一种情况,兄弟节点是红色。
如果兄弟节点是红色,将兄弟节点染成黑色,parent染成红色,进行旋转操作,目的是让兄弟节点的黑色子节点变成新的兄弟。
删除88,88的兄弟节点是55,55是红色,因此将55染成黑色,80染成红色,要想然76变成新的兄弟节点,需要将80,进行右旋操作,右旋后的结果如下。
这时候88的兄弟节点是黑色了,这就可以套用上面的兄弟节点是黑色的情况进行解决。
如果兄弟节点有红色的子节点,旋转之后的中心节点继承删除节点的parent的颜色,旋转之后的左右节点染成黑色。
如果兄弟节点没有红色子节点,将兄弟节点染成红色,将父节点拿下来与兄弟节点合并,父节点染成黑色。
afterRemove代码实现
public void afterRemove(Node<E> ndoe,Node<E> replacement){
//如果待删除的节点是红色
if(isRed(node)){
return;
}
//取代的节点是红色
if(isRed(replacement)){
black(replacement);
return;
}
//如果删除的节点是根节点
Node parent = node.parent;
if(parent==null){
return;
}
//删除的节点是黑色
//判断被删除的node是左还是右节点
boolean left = parent.left==null||node.isLeftChild(); //如果等于null说明删除的是左节点
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)){
//兄弟节点没有一个红色子节点,父节点要向下和兄弟节点合并
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if(parentBlack){
afterRemove(parent,null);
}
}else{
//兄弟节点至少有一个红色子节点
if(isBlack(sibling.right)){
rotateRightt(parent);
//重新确定兄弟节点
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)){
//兄弟节点没有一个红色子节点,父节点要向下和兄弟节点合并
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if(parentBlack){
afterRemove(parent,null);
}
}else{
//兄弟节点至少有一个红色子节点
if(isBlack(sibling.left)){
rotateLeft(parent);
//重新确定兄弟节点
sibling = parent.left;
}
//将兄弟节点染成和父节点一样的颜色
color(sibling,colorOf(parent));
//将兄弟节点的左子节点染成黑色
black(sibling.left);
//将父节点染成黑色
black(parent);
//对父节点进行右旋
rotateRight(parent);
}
}
}