红黑树的添加后和删除后维持平衡

学习红黑树前推荐先去了解B 树

红黑树

红黑树也是一种自平衡二叉搜索树,也叫做平衡二叉B树。

红黑树必须满足以下性质

  • 节点是Red或者Black
  • 根节点是Black
  • 叶子结点,外部节点,空节点都是Black
  • 红色节点的子节点都是黑色节点(红色节点的父节点都是黑色)(从根节点到叶子结点的所有路径上不能有两个连续的红色节点)
  • 从任一节点到叶子结点的所有路径都包含相同数目的黑色节点。

image-20210116222045673

红黑树的等价变换

  • 红黑树和4阶B树具有等价性
  • 黑节点和和红节点融合在一起形成一个B树节点
  • 红黑树的黑节点的个数和4阶B树的节点总个数相等

image-20210117210658237

image-20210117210712950

image-20210117210726138

添加之后维持性质

  • 已知B树中,新元素必定是添加到叶子结点中;
  • 4阶B树所有节点的元素个数x都符合1<=x<=3。
  • 建议新添加的节点默认是红色,这样能够让红黑树的性质尽量满足(1,2,3,5)。
  • 如果添加的节点是根节点,染成黑色。

添加时的所有情况

  • 有四种情况满足红黑树的性质,parent节点是黑色,同样也满足4阶B树的性质,因此不用做任何处理。

image-20210117224755412

  • 另外有8种情况不满足红黑树的性质,也即是parent节点是红色,这就造成了添加的节点和父节点都是红色,不满足性质4,红色节点的子节点都是黑色节点。

image-20210117224939414

LL/RR情况

​ RR情况

image-20210118112735025

处理步骤

  1. 判断添加的元素52的叔叔节点是不是红色,如果不是
  2. 将父节点50染成黑色,将祖父节点46染成红色
  3. 然后对46进行左旋操作,50成为38的子节点,50也是46,52,48的父节点

操作后的结果

image-20210118113144229

LL的情况和RR基本一样,只是将46左旋改成了76进行右旋。

LR/RL情况

image-20210118114045009

处理步骤

  1. 如果添加节点的叔父节点不是红色(为空或者黑色)
  2. 先将添加的节点染成黑色,再将祖父节点染成红色
  3. LR:先对父节点进行左旋,再对祖父节点进行右旋
  4. RL:先对父节点进行右旋,再对祖父节点进行左旋

操作后结果

image-20210118114232706

还有四种情况会造成上溢

LL情况

image-20210118115117142

添加10之后会造成叶子结点元素个数等于4,超过4阶B数叶子结点元素个数上限。造成上溢现象。

判断条件:添加节点的叔父节点是红色

处理步骤:

  1. parent,uncle节点染成黑色
  2. grand节点合并向上
  3. 之后将grand节点当成新添加的节点,因此grand节点染成红色
  4. 向上合并的时候,可能会继续出现上溢的情况,如果上溢到根节点,需要将根节点染成黑色

处理后的结果

image-20210118115537333

RR情况

image-20210118115634614

处理步骤和上面所讲的一样,都是将17,33染成黑色,25向上合并并染成红色

image-20210118115845609LR情况

image-20210118120012789

处理步骤是将17,33染成黑色,25向上合并并染成红色

image-20210118120104674

RL情况

image-20210118120226709

添加节点的代码实现

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。

image-20210118172115615

删除后的结果

image-20210118172215025

删除没有子节点的黑色节点:

判断条件:用来替代待删除的节点的子节点是黑色

黑色节点被删除后,会导致B树节点下溢。

如果被删除的兄弟节点至少有一个红色子节点,要进行旋转操作。

旋转之后的中心节点继承删除节点的parent的颜色,旋转之后的左右节点染成黑色。

删除88

image-20210118212635326

删除后

image-20210118212712076

删除88第二种情况

image-20210118212804325

删除后恢复

image-20210118212833633

删除88第三种情况

image-20210118213015889

LL,对80 进行右旋操作,76上去,需要染成红色(因为80就是红色),72和80染成黑色。

image-20210118213046295

还有一种情况,如果被删除的节点的兄弟节点没有红色节点,也就是不能向兄弟节点借。

操作:将兄弟节点染成红色,将父节点拿下来与兄弟节点合并,父节点染成黑色。

这时,如果父节点是根节点会造成根节点页下溢,所以将根节点也作为被删除的节点处理(递归调用afterRemove(parent))

最后一种情况,兄弟节点是红色。

如果兄弟节点是红色,将兄弟节点染成黑色,parent染成红色,进行旋转操作,目的是让兄弟节点的黑色子节点变成新的兄弟。

image-20210118214734202

删除88,88的兄弟节点是55,55是红色,因此将55染成黑色,80染成红色,要想然76变成新的兄弟节点,需要将80,进行右旋操作,右旋后的结果如下。

image-20210118214909581

这时候88的兄弟节点是黑色了,这就可以套用上面的兄弟节点是黑色的情况进行解决。

如果兄弟节点有红色的子节点,旋转之后的中心节点继承删除节点的parent的颜色,旋转之后的左右节点染成黑色。

如果兄弟节点没有红色子节点,将兄弟节点染成红色,将父节点拿下来与兄弟节点合并,父节点染成黑色。

image-20210118215928025

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);
            
        }
    }
}

Supongo que te gusta

Origin blog.csdn.net/qq_43672652/article/details/112799113
Recomendado
Clasificación