对红黑树的插入、删除理解及java代码实现

1. 红黑树的性质:

性质1 节点要么是黑色,要么是红色;
性质2 根节点永远是黑色;
性质3 NIL节点都是黑色的;
性质4.红色节点的子节点只能是黑色;
性质5 从一个节点到其子树的NIL节点的每一条路径必须包含相同数目的黑色节点;
  1. 根据红黑树的性质:如果节点的两个子节点都为红色,则可以进行颜色反转,节点变红,两个子节点都变黑;
  2. 节点的旋转:如果需要插入的节点相对于父亲节点的位置 和 父亲节点相对于 爷爷节点的位置相同时,单旋转,如果位置不同,则需要双旋转,其实也就是两次单旋转;
  3. 节点的插入:未满足红黑树的性质,插入的节点都需要是红色的;如果父亲节点是黑色,则,插入结束;如果父亲节点是红色,则需要旋转、变色等手段维持红黑树的状态;旋转时候会面临一个问题,如果父亲节点的兄弟节点也是红色节点,则旋转后,红黑树的状态会被破坏,但这个问题可以自顶向下变色来解决(实现时采用自底向上,方法更优),具体方法:对从根节点到需要插入的节点的路径进行遍历,如果发现有某个节点的两个子节点都是红色,则可将该节点变红,两个子节点变黑,注意如果该节点是根节点需要将根节点变黑(因为跟节点只能是黑色),这样,如果父亲节点与父亲的兄弟节点同时为红,则会被颜色反转为黑色,节点变为红色(注意该节点与该节点的父亲节点的颜色冲突),通过该步骤即可保证父亲节点与父亲的兄弟节点不会同时为红色 ,则要么父亲节点是黑色,插入操作直接成功,要么父亲节点是红色,父亲的兄弟节点是黑色,经过一次旋转(单选装或者双旋转)之后成功;该步骤需要访问从根节点到所插入的节点的父亲节点的路径,所以红黑树的插入操作时间复杂度为logN;

5. 节点的删除:

5.1 节点删除的情况:

5.1.1 节点的左子树、右子树都不为null;
5.1.2 节点的左子树为nul、右子树不为null;
5.1.3 节点的右子树为null,左子树不为null;
5.1.4 节点的左子树、右子树都为null;

5.2 对各种情况进行思路分析:

5.2.1 节点左、右子树都不null:针对这种情况,通常为了简化操作,都会采用左子树的最大子节点或者右子树的最小子节点的值来替换当前节点的值,然后删除左子树的最大子节点或右子树的最小子节点,替换后需要删除的节点的可能情况为其他三种;

5.2.2 节点的左子树为nul、右子树不为null 或者 节点的右子树为null,左子树不为null;两种情况类似,取节点的左子树为nul、右子树不为null进行具体情况分析,另一种类似:

  • 5.2.2.1 需要删除的节点D为红色节点,那么根据红黑树的性质,其右子节点不可能为红色(违反性质4),不可能出现右子节点是黑色节点的情况(违反性质五);所有右子节点只可能是NIL,直接删除该节点即可;
  • 5.2.2.2 需要删除的节点D为黑色节点,那么根据红黑树的性质,其右子节点要么就是NIL,要么是红色节点,且该红色节点的两个子节点为NIL,同样参考性质四和五;如果右子节点为NIL,则这种情况分析参考节点的左子树、右子树都为null的分析(即5.2.3);如果右子节点为红色节点,那么就简单了,删除该节点,并把右子节点变成黑色,顶替该节点的即可;
    节点为黑色,右子节点为红色的情况

5.2.3 两个子树都为Nil,这种情况是最复杂的情况(以该节点为父节点的左子节点为例分析,该节点是右子节的情况类似):

  • 5.2.3.1 该节点D为红色节点,直接删除该节点即可;
  • 5.2.3.2 该节点为黑色节点,删除该节点会导致该路径上的黑色节点数量 -1,并且因为没有子节点,根本无法通过子树内部修正路径;所以得考虑从该节点的兄弟节点处入手,让他匀一个黑色节点过来,因为该节点为黑色节点,所以其兄弟节点必然存在(性质5)且可能是红色节点,也可能是黑色节点;
    • 5.2.3.2.1 如果该节点(D)的兄弟节点(S)为红色节点,他们的父节点§一定是黑色节点,下图表明了其兄弟为红色节点时候的操作(注意:DR就是NIL,且SL和SR一定不是NIL(性质5)):
      在这里插入图片描述

在这里插入图片描述

经过该操作后S成为根节点,右子树的的路径的黑色节点数量不受影响,左子树路径的黑色节点数量也依然不受影响,因为节点D被删除,路径上黑色节点少了一个,但此时左子树已经不再是空了,而是通过旋转,从右子树得到了SL,且SL是黑色的不为NIL节点;再看P子树,问题已经转换为DR的兄弟节点是黑色节点的问题了(该分析思路的起点是(删除D后)DR的兄弟节点S是红色节点的),因为SL的子节点可能是NIL也可能存在红色的子节点,所以还是有几种情况需要讨论,接下来的处理可参考兄弟节点S为黑色的思路分支;

  • 5.2.3.2.2 如果该节点(D)的兄弟节点(S)为黑色节点,他们的父节点 §则颜色不定,SL和SR的颜色自然也不能确定,可能存在的组合就比较多了;但有时候确定了其中某个或者某两个节点的颜色后,我们并不关心其他节点的颜色,他的颜色不影响我们的操作,所以需要分析的分支有如下:(需要注意的是,S为黑色节点,那么SL和SR要么为NIL,要么就是红色的节点,且其子节点为NIL;
    • 5.2.3.2.2.1 SL为红色,此时我们不关心p和SR是什么颜色(用白色表示):
      在这里插入图片描述

    • 5.2.3.2.2.2 SR为红色,此时我们不关系P和SL是什么颜色(用白色表示):
      在这里插入图片描述

    • 5.2.3.2.2.3 SL和SR都不为红色,P为红色:
      在这里插入图片描述

    • 5.2.3.2.2.4 SL和SR都不为红色,P为黑色,这种情况在P子树内没办法解决:
      在这里插入图片描述
      解决办法为将S变成红色,让P子树的左右子树路径黑色节点数字统一,且都比不通过P节点的路径的黑色节点数子都少一个,然后去找P当成DR进行类似的操作,去找P的兄弟节点来进行平衡操作,递归直到平衡结束;

6 代码实现:

代码思路:

插入操作:先如一般查找树一样插入节点,然后平衡操作;平衡分几种情况:

  • 父节点为黑色,无需调整
  • 父亲节点为红色,且父亲节点的兄弟节点为黑色,需要调整,注意分左左,左右,右左和右右几种情况(这里说的是插入节点的父亲节点相对于其父亲的位置和插入的节点相对父亲节点的位置),左右和右左需要两次旋转,左左和右右只需要一次旋转;
  • 父亲节点为红色,且父亲节点的兄弟节点也为红色,此时需要反转颜色,父亲节点及其兄弟节点变黑,父亲节点的父亲节点变红,然后以父亲节点的父亲节点为节点再次修正,直到完成修正或者已经到根节点了,那么直接将根节点再变黑即可;

删除操作:先修正,再删除,依然分几种情况:

  • 节点的左右子树均不为空,进行删除转换;

  • 删除转换后必定有一侧为空,如果该节点为红色节点,直接删除,并返回;因为其子节点一定都为NIL,原因见上面的删除的思维分析;

  • 如果节点不为红色节点则分情况,其中之一为空,另一子节点为红色节点:(不存在另一节点为非NIL的黑色节点的情况,原因见思维分析),删除节点并用子节点顶替,子节点变黑色即可;

  • 如果节点不为红色节点且子节点都为NIL,则需要关注该节点的兄弟节点sibling,继续分情况考虑,

    • sibling有一个节点为红色节点,通过旋转、变色解决,只是位置不同,旋转方式不同
    • sibling节点子节点都不为红,且节点的父节点为红,父节点和sibling反转颜色即可;
    • sibling节点子节点和父节点都不红,则通过将sibling变黑,达到子树内部黑色节点数量一致,都比外部少1,然后将处理对象变为父亲节点,递归进行,直到处理完成,注意,如果处理节点已经到root了就无需继续处理了,root树内部平衡就相当于整棵树已经平衡了;

最后删除需要删除的节点即可;

最终完整代码如下:

package com.guzn.DataStructure.BinaryTree.RBTree;

import com.guzn.DataStructure.BinaryTree.BinaryTree;
import com.guzn.DataStructure.BinaryTree.Util;

import java.util.concurrent.BlockingDeque;

public class RBTree<T extends  Comparable<T>> extends BinaryTree {
    private RBNode<T> root;
    public static final boolean RED = false;
    public static final boolean BLACK = true;
    public static class RBNode<T extends Comparable<T>>{
        public boolean color;
        public T key;
        public RBNode<T> parent;
        public RBNode<T> left;
        public RBNode<T> right;
        RBNode(T t,RBNode<T> parent,RBNode<T> left,RBNode<T> right,boolean color){
            key = t;
            this.parent = parent;
            this.left = left;
            this.right = right;
            this.color = color;
        }
        RBNode(T t,RBNode<T> parent){
            this(t,parent,null,null,RED);
        }
        RBNode(T t){
            this(t,null,null,null,RED);
        }

        @Override
        public String toString() {
            return key.toString() + " " + (color ? "B" : "R");
        }
        public RBNode<T> getLeft() {
            return left;
        }
        public RBNode<T> getRight() {
            return right;
        }
        public void setLeft(RBNode<T> left) {
            this.left = left;
        }

        public void setRight(RBNode<T> right) {
            this.right = right;
        }
    }
    RBTree(RBNode<T> root){
        this.root = root;
    }
    RBTree(){
    }
    public RBNode<T> getRoot(){
        return root;
    }
    public void insert(T key){
        if(key == null) return;
        RBNode<T> node = new RBNode(key);
        RBNode<T> parent = null;
        RBNode<T> temp = root;
        while(temp != null){
            parent = temp;
            if(key.compareTo(temp.key) < 0){
                temp = temp.left;
            } else {
                temp = temp.right;
            }
        }
        node.parent = parent;
        if(parent == null){ //root == null
            root = node;
        }else{
            if (key.compareTo(parent.key) < 0){
                parent.left = node;
            }else{
                parent.right = node;
            }
        }
        insertFixup(node);
        return;
    }
    private void insertFixup(RBNode<T> node){
        while(node != root){
            RBNode<T> parent = node.parent;
            //1. 父亲节点为黑色,无需调整;
            //2. 父亲节点为红色,且父亲节点的兄弟节点为黑色,需要调整,注意分左左,左右,右左和右右几种情况
            //3. 父亲节点为红色,且父亲节点的兄弟节点也为红色,此时需要反转颜色,父亲节点及其兄弟节点变黑,父亲节点的父亲节点变红,然后以父亲节点的父亲节点为节点再次修正;
            if (parent.color == BLACK) break;//父亲节点为黑色,无需调整;
            if(parent.color == RED){
                boolean parentIsLeft = parent.parent.left == parent;
                RBNode<T> sibling;
                if(parentIsLeft){
                    sibling = parent.parent.right;
                } else {
                    sibling = parent.parent.left;
                }
                if (sibling == null || sibling.color == BLACK ){
                /*父亲节点的兄弟节点为黑色,注意,此处按照红黑树的规则,
                sibling不可能为非NIL的黑色节点,但是我们是已经插入了一个节点
                在调整,调整完之前它都不是一个合格的红黑树,所以此处sibling是
                可能为非NIL的黑色节点的*/
                    if(parentIsLeft){ //父亲节点属于左子树
                        if (parent.left == node){//左左
                            parent.color = BLACK;
                            parent.parent.color = RED;
                            rightRotate(parent.parent);
                        } else { //左右
                            leftRotate(parent);
                            node.color = BLACK;
                            node.parent.color = RED;
                            rightRotate(node.parent);
                        }
                    } else {//父亲节点属于的右子树
                        if(parent.right == node ){ //右右
                            parent.color = BLACK;
                            parent.parent.color = RED;
                            leftRotate(parent.parent);
                        } else { //右左
                            rightRotate(parent);
                            node.parent.color = RED;
                            node.color = BLACK;
                            leftRotate(node.parent);
                        }
                    }
                } else { 父亲节点的兄弟节点为红色,变色,下一个循环对parent.parent进行类似处理
                    sibling.color = BLACK;
                    parent.color = BLACK;
                    node = parent.parent;
                    node.color = RED;
                }
            }
        }
        root.color = BLACK;
        return;
    }
    private boolean delete(T key){
        RBNode<T> node = findNode(key);
        if(node == null) return false;
        //1. 左右子树均不为空,进行删除转换
        //2. 删除转换后必定有一侧为空,如果该节点为红色节点,直接删除,并返回:
        //3. 如果节点不为红色节点则分情况:
        //  3.1 其中之一为空,另一子树为红色节点(不存在另一节点为黑色节点非NIL的情况),删除节点,子节点变色并顶替解决并返回;
        //  3.2 均为空;
        //1.均不为空,则进行删除转换后继节点
        if(node.left != null && node.right != null){
            RBNode<T> next = node.right;
            while(next.left != null){
                next = next.left;
            }
            node.key = next.key;
            node = next;
        }
        //2 节点为红色,直接删除
        if(node.color == RED){
            //node是红色节点且至少有一个子节点为NIL,所以不可能是根节点,且另一个子节点一定是NIL;
            if(node.parent.left == node){
                node.parent.left = null;
            } else {
                node.parent.right = null;
            }
            return true;
        }
        //3.1 node是黑色,其中之一为NIL,另一子树为红色节点:
        //左子树是红色节点
        if (node.left != null){
            node.left.color = BLACK;
            node.left.parent = node.parent;
            if (node == root){
                root = node.left;
            } else{
                if(node.parent.left == node){
                    node.parent.left = node.left;
                } else {
                    node.parent.right = node.left;
                }
            }
        } else if (node.right != null) { //右子树是红色节点
            node.right.color = BLACK;
            node.right.parent = node.parent;
            if (node == root){
                root = node.right;
            } else {
                if (node.parent.left == node){
                    node.parent.left = node.right;
                } else {
                    node.parent.right = node.right;
                }
            }

        }else{ //3.2 节点为黑色节点且子节点均为NIL
            removeBlackNodeWithNoChildAndFixup(node);
        }
        clearNode(node);
        return true;
    }
    private void clearNode(RBNode<T> node){
        node.left = null;
        node.right = null;
        node.parent = null;
        node.key = null;
        node = null;
    }
    public RBNode<T> findNode(T key) {
        if(key == null) return null;
        RBNode<T> node =root;
        while(node != null){
            if(node.key.compareTo(key) < 0){
                node = node.right;
            } else if (node.key.compareTo(key) == 0){
                return node;
            } else {
                node = node.left;
            }
        }
        return null;
    }
    private void removeBlackNodeWithNoChildAndFixup(RBNode<T> node){
        RBNode<T> nodeTemp = node;
        //Fixup:
        while(nodeTemp != root) {
            boolean isLeft = nodeTemp.parent.left == nodeTemp;
            RBNode<T> sibling;
            if (isLeft) {
                sibling = nodeTemp.parent.right;
            } else {
                sibling = nodeTemp.parent.left;
            }
            //sibling is red ,转换成兄弟节点为黑色的情况;
            if (sibling.color == RED) {
                sibling.color = BLACK;
                nodeTemp.parent.color = RED;
                if (isLeft) {
                    leftRotate(nodeTemp.parent);
                    sibling = nodeTemp.parent.right;
                } else {
                    rightRotate(nodeTemp.parent);
                    sibling = nodeTemp.parent.left;
                }
            }
            //现在兄弟节点为黑色了;有以下几种情况:
            //1. 兄弟节点的内子节点为红色,通过旋转和变色达到平衡;
            //2. 兄弟节点的外子节点为红色,通过旋转和变色也能达到平衡;
            //3. 兄弟节点的内外子节点均不为红色,父节点为红色,通过变色可达到平衡;
            //4.  兄弟节点的内外子节点和父节点都不为红色,那么将兄弟节点子树的路径-1,树内部左右平衡,然后向上递归,寻找平衡机会;
            //1
            isLeft = nodeTemp.parent.left == nodeTemp;
            if (isLeft) {
                if (sibling.left != null && sibling.left.color == RED) {  //1
                    sibling.left.color = nodeTemp.parent.color;
                    nodeTemp.parent.color = BLACK;
                    rightRotate(sibling);
                    leftRotate(nodeTemp.parent);
                    break;
                } else if (sibling.right != null && sibling.right.color == RED) { // 2
                    sibling.color = nodeTemp.parent.color;
                    nodeTemp.parent.color = BLACK;
                    sibling.right.color = BLACK;
                    leftRotate(nodeTemp.parent);
                    break;
                } else if (nodeTemp.parent.color == RED) {// 3
                    sibling.color = RED;
                    nodeTemp.parent.color = BLACK;
                    break;
                } else { // 4
                    sibling.color = RED;
                    nodeTemp = nodeTemp.parent;
                }
            } else {
                if (sibling.right != null && sibling.right.color == RED){ //1
                    sibling.right.color = nodeTemp.parent.color;
                    nodeTemp.parent.color = BLACK;
                    leftRotate(sibling);
                    rightRotate(nodeTemp.parent);
                    break;
                } else if (sibling.left != null && sibling.left.color == RED){ // 2
                    sibling.color = nodeTemp.parent.color;
                    nodeTemp.parent.color = BLACK;
                    sibling.left.color = BLACK;
                    rightRotate(nodeTemp.parent);
                    break;
                } else if (nodeTemp.parent.color == RED) { // 3
                    sibling.color = RED;
                    nodeTemp.parent.color = BLACK;
                    break;
                } else { // 4
                    sibling.color = RED;
                    nodeTemp = nodeTemp.parent;
                }
            }
        }
        //remove
        if (node == root){
            root = null;
        } else {
            if(node.parent.left == node){
                node.parent.left = null;
            } else {
                node.parent.right = null;
            }
        }
    }
    private void leftRotate(RBNode<T> node){
        if(node.right == null) return; //如果右子节点是null,无法旋转
        RBNode right = node.right;
        right.parent = node.parent;
        if(node.parent != null) { // node不是根节点
            if(node.parent.left == node){
                node.parent.left = right;
            }else{
                node.parent.right = right;
            }
        } else { //node是根节点
            root = right;
        }
        node.right = right.left;
        if(right.left != null) {
            right.left.parent = node;
        }
        right.left = node;
        node.parent =right;
    }
    private void rightRotate(RBNode<T> node){
        if(node.left == null) return; //如果左子节点是null,无法旋转
        RBNode left = node.left;
        node.left = left.right;
        if(left.right != null){
            left.right.parent = node;
        }
        left.parent = node.parent;
        if(node.parent != null){ // node 不是根节点
            if(node == node.parent.left){
                node.parent.left = left;
            } else {
                node.parent.right = left;
            }
        }else{ // node是根节点,需要更新root引用
            root = left;
        }
        node.parent = left;
        left.right = node;
    }
    public static void main(String[] args){
        //test rotate:
        /*
                    1
                   / \
                  2   3
                 / \ / \
                4  5 6  7
         */
        RBTree.RBNode<Integer> node1 = new  RBTree.RBNode<>(1);
        RBTree.RBNode<Integer> node2 = new  RBTree.RBNode<>(2,node1);
        RBTree.RBNode<Integer> node3 = new  RBTree.RBNode<>(3,node1);
        RBTree.RBNode<Integer> node4 = new  RBTree.RBNode<>(4,node2);
        RBTree.RBNode<Integer> node5 = new  RBTree.RBNode<>(5,node2);
        RBTree.RBNode<Integer> node6 = new  RBTree.RBNode<>(6,node3);
        RBTree.RBNode<Integer> node7 = new  RBTree.RBNode<>(7,node3);
        node1.setLeft(node2);
        node1.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);
        node3.setRight(node7);
        RBTree<Integer> tree = new RBTree<>(node1);
        System.out.println(Util.BFS(tree.getRoot()));
//        tree.leftRotate(node1);
//        tree.leftRotate(node2);
        tree.rightRotate(node1);
        System.out.println(Util.BFS(tree.getRoot()));

        //test insert
        RBTree<Integer> tree1 = new RBTree<>();
        int[] array = {1,1,5,71,6,85,12,645,0};
        for(int i =0 ;i < array.length;++i){
            tree1.insert(array[i]);
        }
        System.out.println("test insert:");
        System.out.println(Util.BFS(tree1.getRoot()));

        //test remove
        if(tree1.delete(0)){
            System.out.println("remove 0 succeed");
        } else {
            System.out.println("remove 0 failed");
        }
        System.out.println(Util.BFS(tree1.getRoot()));
        if(tree1.delete(6)){
            System.out.println("remove 6 succeed");
        } else {
            System.out.println("remove 6 failed");
        }
        System.out.println(Util.BFS(tree1.getRoot()));
        if(tree1.delete(71)){
            System.out.println("remove 71 succeed");
        } else {
            System.out.println("remove 71 failed");
        }
        System.out.println(Util.BFS(tree1.getRoot()));
        if(tree1.delete(5)){
            System.out.println("remove 5 succeed");
        } else {
            System.out.println("remove 5 failed");
        }
        System.out.println(Util.BFS(tree1.getRoot()));
        if(tree1.delete(85)){
            System.out.println("remove 85 succeed");
        } else {
            System.out.println("remove 85 failed");
        }
        System.out.println(Util.BFS(tree1.getRoot()));
        if(tree1.delete(645)){
            System.out.println("remove 645 succeed");
        } else {
            System.out.println("remove 645 failed");
        }
        System.out.println(Util.BFS(tree1.getRoot()));
        if(tree1.delete(1)){
            System.out.println("remove 1 succeed");
        } else {
            System.out.println("remove 1 failed");
        }
        System.out.println(Util.BFS(tree1.getRoot()));
        if(tree1.delete(12)){
            System.out.println("remove 12 succeed");
        } else {
            System.out.println("remove 12 failed");
        }
        System.out.println(Util.BFS(tree1.getRoot()));
        if(tree1.delete(1)){
            System.out.println("remove 1 succeed");
        } else {
            System.out.println("remove 1 failed");
        }
        System.out.println(Util.BFS(tree1.getRoot()));
    }
}

为了方便测试,写了个工具类用来广度搜索,代码如下:

package com.guzn.DataStructure.BinaryTree;
import com.guzn.DataStructure.BinaryTree.RBTree.RBTree;

import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Util {
    public static <T extends Comparable<T>> List<RBTree.RBNode<T>> BFS(RBTree.RBNode<T> node){
        List<RBTree.RBNode<T>> list = new LinkedList<>();
        if(node == null) return list;
        Queue<RBTree.RBNode<T>> queue = new LinkedList<>();
        queue.offer(node);
        while(!queue.isEmpty()){
            RBTree.RBNode<T> nodeTemp = queue.poll();
            list.add(nodeTemp);
//            if(nodeTemp.left != null){
//                queue.offer(nodeTemp.left);
//            }
//            if(nodeTemp.right != null){
//                queue.offer(nodeTemp.right);
//            }
//显示出null节点为了方便查看树的结构;
            if(nodeTemp != null){
                queue.offer(nodeTemp.left);
                queue.offer(nodeTemp.right);
            }
        }
        return list;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值