红黑树的简要描述和详细实现

红黑叔的简要描述和详细实现

特点:

  • 平衡二叉树
  • 结点非黑即红
    • (默认-红:根必须黑,默认红可减少更改颜色次数,提高效率)
  • 可通过”红黑规则“平衡高度
1.成员定义:
public int qa;                  //值
public TreeNode left;           //左节点
public TreeNode right;          //右节点
public TreeNode father;         //父节点
public boolean color = Red;    //默认根节点为红
public static int size;         //红黑树的结点个数
public static TreeNode head;           //根结点
public static boolean Red = false;
public static boolean Black = true;
2.红黑规则:
  1. 非黑即红;
  2. 根必须为黑色;
  3. 若结点无子结点,则指向nil,nil视为叶节点,nil为黑色;
  4. 若结点为红,则子、父节点必须为黑,不能双红相连;
  5. 每个结点对其后代所有叶结点(nil)的路径,都有相同的黑色结点个数;
3.旋转:

代码比较简单,简单判断各个节点是否存在,然后将指向进行变换就行了

注:有3条线切换,顺序如下:

  1. 先进行 cur-父 -> cur-祖 的变换(先执行下面的可能会导致cur节点消失);
  2. 再查看 祖.father 是否存在,存在就变换(先执行3可能会导致无法连接上 祖.father);
  3. 最后切换 祖-父 的切换;
    在这里插入图片描述
    右旋实现,左旋可以参考自写:
public void rightRotate(TreeNode node) {
        if (node != null && node.left != null) {
            //右旋结点的左节点-left
            TreeNode left = node.left;
            //让左节点.right - node.left,同时让左节点.right.father - node-------------先处理左节点和node的指向会导致左节点.right的丢失,
            node.left = left.right;
            if (left.right != null) {
                left.right.father = node;
            }
            //让node.father - left.father--------------------先处理左节点和node的指向,后处理node.father-left的指向会导致node.father的丢失,
            left.father = node.father;
            if (node.father == null) {
                //node原为根结点--将left指为新根结点
                head = left;
            } else if (node.father.left == node) {
                //node在node.father的左子树
                left.father.left = left;
            } else {
                //node在node.father的右子树
                left.father.right = left;
            }
            //最后处理left 和 node 的指向关系
            left.right = node;
            node.father = left;
        }
    }
4.添加结点—红黑规则的调整思想:

插入节点为cur:值为50

注:寻找节点位置的内容就不在这里展示了,简单二分查找就行了—添加位置一定会是子树的最底层节点

先分cur的父节点是在祖节点的左子树还是右子树写代码,一分为二,可以少些很多代码

  • 添加情况-根据2-3-4树的演化:

    • 2树

      • 2-3-4树:直接添加到已有的节点处,成为2树;

      • 红黑树:

        • 1.添加到黑色下:因为添加为红色,直接添加,无需调整;

        • 2.添加为红色节点下:红黑树根为黑,即是转下面的3、4树情况添加;

          如果有叔叔节点,则是4树添加,没有叔叔节点则是3树添加;

    • 3树

      • 2-3-4树:根据大小合并到已有的2节点,成为3树

      • 红黑树:分为4种情况需要调整:

        • 2种情况不用调整-添加为50:
          在这里插入图片描述
        • 2种情况需要旋转2次(可以先旋转一次,然后变成情况3): 在这里插入图片描述
        • 2种情况只需要旋转一次:
          在这里插入图片描述
  1. 4树

    • 2-3-4树:先将原3树中间节点进行上移,成为父节点,然后根据大小合并到下面的2个节点中;

    • 红黑树-红黑树的4树调整情况,父节点为黑直接省略:

      • 叔叔节点为红色:

        将父、叔节点变黑,祖节点变红,使祖节点及其以下满足红黑树规则,然后对祖节点进行调整调用即可;

      • 祖父节点为黑色(即向上递归调整的情况-因为当前侧颜色红多,因此必然失衡,不过借用后续的代码也能实现调整完成-与三树调整策略相同):

红黑叔添加调整实现代码:

/**
     * 平衡调整代码:2-3-4树与红黑树:---2树即1节点插入1节点
     * 1.  2树
     * ---2-3-4树:直接添加,进行节点合并;
     * ---红黑树:无需调整---(1-父节点为黑,直接插入红节点, 2-父节点为红,则不是2树添加)
     * 2. 3树
     * ---2-3-4树:根据大小合并成3树
     * ---红黑树: 6种情况(其中2种直接成为 左-中-右,无需调整)
     * ------根左左  根右右 : 进行一次旋转就行;
     * ------根左右  根右左 : 先进行一次旋转,成为左左、右右情况,在3树进行旋转即可
     * 3. 4树
     * ---2-3-4树: 先将原3节点的中节点上升成为父节点,然后将添加节点根据情况与下面的2节点合并
     * ---红黑树: 将其转换成 爷-红,父叔-黑,使爷节点满足红黑树要求,然后进行旋转,向上递归调用调整即可
     */
    public void Balance(TreeNode cur) {
        //祖节点为空
        if (fatherIsequals(fatherIsequals(cur)) == null) {
            //就把父节点设置为根,并且改颜色,然后直接退出;
            head = cur.father;
            cur.father.color = Black;
            return;
        }
        //添加节点的父节点需要为red,不然没有调整的必要-cur.color=red
        while (cur != null && head != null && cur.father.color==Red) {
            //父节点是祖节点的左节点
            if (fatherIsequals(fatherIsequals(cur)).left == fatherIsequals(cur)) {
                TreeNode rfN = rightIsequals(fatherIsequals(fatherIsequals(cur)));
                /**
                 * 叔节点不为空,即是4树
                 */
                if (rfN!=null && rfN.color==Red) {
                    //将祖-爷节点设置为红,父叔节点颜色设置为黑---满足红黑树规则
                    fatherIsequals(fatherIsequals(cur)).color = Red;
                    rightIsequals(fatherIsequals(fatherIsequals(cur))).color = Black;
                    fatherIsequals(cur).color = Black;
                    //对祖节点进行操作,相当于递归调用
                    cur = fatherIsequals(fatherIsequals(cur));
                } else {
                    //没有叔节点,为3树,
                    if (cur == rightIsequals(fatherIsequals(cur))) {
                        //插入为父节点的右边,即 根左右,先左旋--根左左
                        cur = fatherIsequals(cur);
                        leftRotate(cur);
                    }
                    //3树对应红黑树的根左左情况-右旋+颜色调整
                    fatherIsequals(cur).color = Black;
                    fatherIsequals(fatherIsequals(cur)).color = Red;
                    rightRotate(fatherIsequals(fatherIsequals(cur)));
                }
            } else {
                //叔节点是否存在并且是否为红,存在即为4树插入
                if (leftIsequals(fatherIsequals(fatherIsequals(cur))).color==Red) {
                    //4树插入我们对其祖、叔、父节点进行变色
                    fatherIsequals(fatherIsequals(cur)).color = Red;
                    leftIsequals(fatherIsequals(fatherIsequals(cur))).color = Black;
                    fatherIsequals(cur).color = Black;
                    //整个4树符合红黑树要求,于是将根-祖节点视为插入节点进行调整
                    cur = fatherIsequals(fatherIsequals(cur));
                } else {
                    //叔节点不存在为3树,或叔节点为黑(此时cur为向上递归的子树头节点)我们对其判断,一次旋转还是二次旋转(插入右树左节点)
                    if (cur == leftIsequals(fatherIsequals(cur))) {
                        //将插入节点与父节点进行右旋,此时原父节点被视为插入节点
                        cur = fatherIsequals(cur);
                        rightRotate(cur);
                    }
                    //进行颜色变换,再左旋
                    fatherIsequals(fatherIsequals(cur)).color = Red;
                    fatherIsequals(cur).color = Black;
                    leftRotate(fatherIsequals(fatherIsequals(cur)));
                }
            }
        }
        //根节点颜色为黑
        head.color = Black;
    }
4.后驱节点:

在红黑树、二叉搜索树等规律数据结构中,大于当前节点的最小节点即为后驱节点,即中序遍历当前节点值的后一个即使后驱节点-这里我们删除红黑树用后驱节点:
代码:

/**
     * 寻找后驱节点
     */
    public TreeNode seccessor(TreeNode node) {
        if (node == null) {
            return null;
        }
        TreeNode cur;
        //先寻找非叶节点情况
        if (node.right != null) {
            //来到初始node节点的左数最右节点
            cur = node.right;
            while (cur != null) {
                cur = cur.left;
            }
            return cur;
        } else if (node.father == null) {
            //无右数,无父节点,没有后驱节点
            return null;
        } else {
            /**同理
             * node没有右子树,有父节点, 在红黑树里面不需要找后驱节点,但找后驱节点这里补一下-(红黑树:
             * 叶节点直接删除,有左节点就代替node-不会出现左子树多节点情况,因为右旋调整)
             */
            cur = node.father;
            while (cur != null && node==cur.right) {
                node = cur;
                cur = cur.father;
            }
            return cur;
        }
    }
5.删除结点:

这里删除使用后驱节点,即中序遍历每个值的后驱节点便是下一个值

注:每个节点的后驱节点在2-3-4树中都只是在最底层

删除也分为3种情况(画图太麻烦了,就不画了)

  • 删除节点存在2个子树:

    • 找到该删除节点的后驱节点,然后将后驱节点值赋给删除节点,然后删除后驱节点即可,每个后驱节点都可以转换成下面的2种情况;
  • 删除节点只有1个子树-分为2种情况:

    先获得那个子节点replacement

    • 1.删除为根节点-这里直接可以将叶子节点也包括内:

      先将head=replacement,然后判断replacement不为空,则将replacement的父指针指向null,最后将删除节点所有指针指null即可;

    • 2.只有replacement子节点,这里有2种思路:

      • 1.将replacement子节点的值赋给replacement,然后删除子节点即可;
      • 2.直接删除replacement节点,然后进行删除后的平衡调整;
  • 删除节点为叶子节点:

    • 先进行调整调用,然后进行删除,要判断一下删除节点是哪个子树,对应父节点的指针也要指空;

删除代码:

/**
     * 删除值-:3种情况:
     * 对应节点:cur,一个子节点:replacement,父节点:father
     * 1.当删除节点有 左右 子树:
     * ---可以直接找到删除节点的后驱节点(前驱节点效果一样),然后将后驱节点值赋给cur,然后删除后驱节点即可(后驱节点必然是2-3-4树的底层节点,为第2.3种情况)
     * 2.当删除节点存在一个节点:
     * ---情况1:删除节点为head根节点:
     * ------将cur所有指针指向空,replacement赋给head(当replacement为空,即为叶节点同样有效)
     * ---情况2:正常节点(cur只能是黑,replacement只能是红---因当cur为红,replacement只能为黑,而cur没有另一颗子树,于是红黑树不平衡):
     * ------将father和replacement建立上下层关系(判断cur是father哪个子树),然后将cur所有指针指向空
     * ------调整平衡(例:cur只有2个节点,但另一棵树可以存在7个节点)-(cur为黑时)
     * 3.删除叶子节点:
     * ---先调整(cur为黑时),然后根据cur是father的子树情况,将father.子树设空,然后cur指向都设空0
     */
    public void remove(int value) {
        //判断值是否存在,不存在直接退出
        TreeNode cur = exist(value);
        if (cur == null) {
            return;
        }
        //第1种情况:当删除节点有 左、右 子树时
        if (leftIsequals(cur) != null && rightIsequals(cur) != null) {
            //找到删除节点的 后驱节点,并将删除节点换成后驱节点值,然后去删除后驱节点(也就是下面2种情况),
            TreeNode secc = seccessor(cur);
            cur.qa = secc.qa;
            cur = secc;
        }
        //第2种情况:只有一个子树存在
        //将子节点赋给replacement
        TreeNode replacement = leftIsequals(cur) == null ? rightIsequals(cur) : leftIsequals(cur);
        //删除节点为根节点
        if (fatherIsequals(cur) == null) {
            //将子节点作为新根
            head = replacement;
            if (replacement != null) {
                //即红黑树只有cur和replacement的情况,还必须让新根节点.father指向空
                replacement.father = null;
            }
            //同时将删除节点指向都 指向空
            cur.left = cur.right = null;
            size--;
        } else if (replacement != null) {
            //将子节点的父节点-父节点,同时将父节点指向子节点replacement
            replacement.father = cur.father;
            if (cur == leftIsequals(fatherIsequals(cur))) {
                father.left = replacement;
            }
            if (cur == rightIsequals(fatherIsequals(cur))) {
                father.right = replacement;
            }
            //同时将删除节点指向都 指向空
            cur.left = cur.right = cur.father = null;
            size--;
            if (colorIsequals(cur) == Black) {
                /**
                 *  做调整-有可能删除之后导致红黑树不平衡(例:cur子树只有cur+replacement,但是cur的父节点的另一颗子树可以存在7个节点):
                 *  先调整-叶子节点为红,没必要调整;
                 *  (正常添加cur是黑,replacement只能是红---因当cur为红,replacement只能为黑,而cur没有另一颗子树,于是红黑树不平衡):
                 */
                pl(cur);
            }
        } else {
            //第3种情况:叶子节点的删除
            //先调整-叶子节点为红,没必要调整
            if (colorIsequals(cur) == Black) {
                pl(cur);
            }
            //再删除
            if (cur == leftIsequals(fatherIsequals(cur))) {
                father.left = null;
            }
            if (cur == rightIsequals(fatherIsequals(cur))) {
                father.right = null;
            }
            //同时将删除节点指向都 指向空
            cur.left = cur.right = cur.father = null;
            size--;
        }
        if(head!=null){
            head.color = Black;
        }
    }
6.删除的调整:

删除节点cur-Black,父节点-father,兄弟节点-rN,这里也分为几种情况—以cur是father的左子节点为例:

  • 删除节点为红色节点,直接可以删除,不会影响红黑树的规则;

  • 删除节点为黑色

    • 兄弟节点存在并且为红-其下必有2个黑色子节点B1,B2,删除cur会导致红黑树不平衡:

      先将rN-Red,father-Black(这样father左子树黑色cur、B1,右子树黑B2-满足平衡),然后左旋;再将rN重新定位,这样就满足下面的第3种的情况:

    • 兄弟节点为黑-又分3种情况:

      • 1.当兄弟节点有子节点时:

        因为cur是father的左子节点,所以兄弟节点 (只有右节点) 和 (满子节点)的处理情况一样;

        • 兄弟节点只有左节点-先变成只有右节点情况:

          将 (rN) 和 (rN.left) 颜色进行对调,然后对rN进行右旋,需要重新定位cur的兄弟节点rN;

        • 先将rN的颜色变成父节点颜色,父节点颜色变红,rN右节点颜色变红,然后对父节点进行左旋,就完成调整了,退出循环;

      • 2.当兄弟节点为叶子节点-2种情况:

        • 1.父节点为红:

          只需要将父节点颜色变黑,rN颜色变红即可,退出循环;

        • 2.父节点为黑:

          同样先将rN颜色变红,但因为原父节点路径有2黑,现变成1黑,所以我们需要向上递归,将调整节点设为父节点;


代码实现:
 /**
     * 删除的调整-以cur=father.left为例:
     * 1.cur为红,直接删除无需调整
     * 2.cur为黑,并且不为根:判断兄弟节点rN颜色
     * ---2.1:rN为红(cur-黑,所以rN必然有2个黑子节点):
     * ------为左旋做调整-左旋后右边只有一个黑节点,所以father变红(rN.left会变father.right),rN变黑,左旋,然后rN重新指向father.right
     * ------变成第2.2.2情况情况去处理
     * ---2.2:rN为黑(rN子节点的3种情况):
     * ------2.2.1:rN有节点-节点必然为红(cur子树只有1黑):
     * —————————1.rN只有左子节点-变换成只有右子树情况:
     * ============右旋前颜色调整 rN变Red,rN.left变Black,然后右旋,rN重新指向father.right
     * —————————2.rN 只有右子节点、有2个子节点调整情况一样:
     * ============rN.color变成father.color(以成为左旋后的新顶节点),father.color变黑,rN.right变黑,然后左旋,左旋前的rN.left存在的话是红不管;然后退出
     * ------2.2.2:rN没有子节点:
     * —————————1.father为红,只需将father变黑,rN变红即可,然后退出
     * —————————2.father为黑,那么father节点有2个黑色节点路径,删除cur后最多一个黑色路径,所以要向上调整:
     * ===========rN变红,然后以father节点while调整
     * @param cur
     */
    private void pl(TreeNode cur) {
        //当cur为根或者颜色为红不需要调整,直接删除
        if(cur!=head && colorIsequals(cur)==Black){
            //cur为左节点的情况
           while(cur == leftIsequals(fatherIsequals(cur))){
                //记录cur的兄弟节点
                TreeNode rNode = rightIsequals(cur);
                if(colorIsequals(rNode) == Red){
                    /**
                     * 兄弟节点为红色,需要调整:
                     * 左树:只有cur-Black,而右树:兄弟节点为红,红黑树路径黑节点一样,所以肯定>=3
                     * ---将rNode颜色变Black,father节点变Red,然后左旋
                     * ---注:此时father这颗红黑树是平衡的,
                     * ------cur为黑,兄弟为红,兄弟肯定有2黑子节点,我们左旋会把rnode.left旋到cur.father的右子节点,
                     * ------而rnode下只有一个黑,所以father变Red-不能双红,rnode变black,就平衡了
                     */
                    rNode.color = Black;
                    fatherIsequals(cur).color = Red;
                    //对父节点左旋
                    leftRotate(fatherIsequals(cur));
                    //左旋导致现在cur的兄弟节点变换了,所以要重新赋值
                    rNode = rightIsequals(fatherIsequals(cur));
                }
                //-------开始情况判断,是兄弟节点帮忙,还是向上递归
                if(leftIsequals(rNode)==null && rightIsequals(rNode)==null){
                    /**
                     * 兄弟节点没有子节点情况,即只剩父、兄节点,存在2种情况(兄节点必然为Black-cur为Black):
                     * 1.父节点为Red:
                     * ---将父节点变Black,兄节点变Red,满足平衡
                     * 2.父节点为Black-原父节点路径2个黑节点最多剩1个,所以需要调整:
                     * ---将兄节点变Red,然后将父节点视为调整节点,递归调用;
                     */
                    if(colorIsequals(fatherIsequals(cur))==Red){
                        //情况1:将父节点变Black,兄节点变Red,满足平衡
                        fatherIsequals(cur).color = Black;
                        rNode.color = Red;
                        //然后退出-cur=head不满足循环条件
                        cur = head;
                    }else {
                        rNode.color = Red;
                        //情况2:对father进行调整
                        cur = fatherIsequals(cur);
                    }
                }else if(rightIsequals(rNode)!=null){
                    /**
                     * 兄弟节点有子节点情况:cur为黑,兄弟节点也为黑-其子节点为红,所以有3种情况,
                     * 1.兄弟节点有2个子树
                     * 2.兄弟节点只有右子树
                     * 3.兄弟节点只有左节点-兄弟节点为右子树,1.2情况处理方法一样,所以先将3情况调整成2情况
                     * 同时因为上一个情况有while,所以将cur指向head来退出while
                     */
                    if(rightIsequals(cur)==null){
                        /**
                         *情况3:兄弟节点只有左节点情况-cur为Black,所以兄弟节点也为Black:
                         * ---将兄弟节点左节点B1与兄弟节点颜色互换,然后对兄弟节点右旋,
                         */
                        leftIsequals(rNode).color = Black;
                        rNode.color = Red;
                        rightRotate(rNode);
                        //重新定位cur的兄弟节点
                        rNode = rightIsequals(fatherIsequals(cur));
                    }
                    /**
                     * 然后做统一处理:
                     * ---先将rNode颜色换成原father颜色-以成为新father,原father颜色成Black,同时rNode.right成Black
                     */
                    rNode.color = fatherIsequals(cur).color;
                    fatherIsequals(cur).color = Black;
                    rightIsequals(rNode).color = Black;
                    //然后对father做左旋,最后退出
                    leftRotate(fatherIsequals(cur));
                    cur = head;
                }
            }else {
                //cur为右节点的情况
                //记录cur的兄弟节点
                TreeNode rNode = leftIsequals(cur);
                if(colorIsequals(rNode) == Red){
                    /**
                     * 兄弟节点为红色,需要调整:
                     * 右树:只有cur-Black,而左树:兄弟节点为红,红黑树路径黑节点一样,所以肯定>=3
                     * ---将rNode颜色换Black,father变Red,然后右旋
                     */
                    rNode.color = Black;
                    fatherIsequals(cur).color = Red;
                    //对父节点右旋
                    rightRotate(fatherIsequals(cur));
                    //右旋导致现在cur的兄弟节点变换了,所以要重新赋值
                    rNode = leftIsequals(fatherIsequals(cur));
                }
                if(leftIsequals(rNode)==null && rightIsequals(rNode)==null){
                    /**
                     * 兄弟节点没有子节点情况,即只剩父、兄节点,存在2种情况(兄节点必然为Black-cur为Black):
                     * 1.父节点为Red:
                     * ---将父节点变Black,兄节点变Red,满足平衡
                     * 2.父节点为Black-原父节点路径2个黑节点最多剩1个,所以需要调整:
                     * ---将兄节点变Red,然后将父节点视为调整节点,递归调用;
                     */
                    if(colorIsequals(fatherIsequals(cur))==Red){
                        //情况1:将父节点变Black,兄节点变Red,满足平衡
                        fatherIsequals(cur).color = Black;
                        rNode.color = Red;
                        //然后退出-cur=head不满足循环条件
                        cur = head;
                    }else {
                        //情况2:需要对father进行调整
                        rNode.color = Red;
                        cur = fatherIsequals(cur);
                    }
                }else if(leftIsequals(rNode)!=null){
                    /**
                     * 兄弟节点为黑,cur也为黑,所以有3种情况,
                     * 1.兄弟节点有2个子树
                     * 2.兄弟节点只有左子树
                     * 3.兄弟节点只有右节点-因为1.2情况处理方法一样,所以先将3情况调整成2情况
                     * 同时因为上一个情况有while,所以将cur指向head来退出while
                     */
                    if(leftIsequals(cur)==null){
                        /**
                         *情况3:兄弟节点只有右节点情况-cur为Black,所以兄弟节点也为Black:
                         * ---将兄弟节点右节点B1与兄弟节点颜色互换,然后对兄弟节点左旋,
                         */
                        rightIsequals(rNode).color = Black;
                        rNode.color = Red;
                        leftRotate(rNode);
                        //原先的leftIsequals(rNode),现在是新的cur的兄弟节点
                        rNode = leftIsequals(fatherIsequals(cur));
                    }
                    /**
                     * 然后做统一处理:
                     * ---先将rNode颜色换成原father颜色-以成为新father,原father颜色成Black,同时rNode.right成Black
                     */
                    rNode.color = fatherIsequals(cur).color;
                    fatherIsequals(cur).color = Black;
                    leftIsequals(rNode).color = Black;
                    //然后对father做右旋,最后退出
                    rightRotate(fatherIsequals(cur));
                    cur = head;
                }
            }
        }
        cur.color = Black;
        head.color = Black;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值