红黑树平衡经典案例(图解)及插入Java实现(代码)

红黑树平衡的经典案例

图1

红黑树几条规则(查阅相关论文得到):

红黑树是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。 红黑树的每个节点的属性除了有一个 key 和 3 个指针:parent、lchild、rchild 外 ,还有一个属性 :color:红或黑 。
与大多数二叉查找树一样 ,红黑树中较小的键值也是在左子树保存 。 红黑树除了具有二叉查找树所有性质外,还具有以下 5 点性质
①节点是红色或黑色。
②根是黑色。
③所有叶子都是黑色(叶子是 NUIL 节点)。
④每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)。
⑤从任一个节点到其每个叶子的所有路径都包含相同数目的黑色节点。
引用自《红黑树关键算法研究》–马国富,张涵


  1. 上图中6为待插入的节点,此时破坏了红黑树的第四个规则,出现了两个连续的红色节点
    此时的情况为6的父节点(7)和叔叔节点都为红色,需要将爷爷(12)节点变为红色,叔叔节点(13)和父节点(7)变为黑色,如图
    图2

  2. 此时5和12 同为红色,不符合红黑树的定义,观察得叔叔(30)节点为爷爷节点的右节点,而12节点为父节点的右节点,此时需要进行对5进行左旋,如图
    图3

  3. 此时看到12和5都为红色,不满足红黑树的性质,此时以5节点的叔叔节点(30)为黑色,将父节点(12)变黑,将爷爷节点(19)变红,再对爷爷节点19进行右旋,如图
    图4

  4. 平衡了,出现了叔叔节点在左子树或右子树的情况,其实方法大致相同,可根据上述几种情况推出

直接上代码(详细注释)

package com.kangin.util;

/**
 * 1.创建Rtree,定义颜色
 * 2.创建RBNode
 * 3.辅助方法定义:parentOf(node),isRed(node),setRed(node),setBlack(node),inOrderPrint()
 * 4.左旋方法定义:leftRotate(node)
 * 5.右旋方法定义:RightRotate(node)
 * 6.公开插入接口方法定义:insert(K key, V value);
 * 7.内部插入接口方法定义:insert(RBNode node);
 * 8.修正插入导致红黑树失衡的方法定义:insertFIxUp(RBNode node);
 * 9.测试红黑树正确性
 *
 * @author khb
 * @date 2021/1/27
 */
public class MyRBtree<K extends Comparable<K>, V> {
    private static final boolean RED = true;
    private static final boolean BLACK = false;

    private MyRBNode root; // 根节点

    /**
     * insert的开放方法
     *
     * @param key
     * @param value
     */
    public void insert(K key, V value) {
        MyRBNode node = new MyRBNode();
        node.setKey(key);
        node.setValue(value);
        node.setColor(RED);
        insert(node);
    }

    /**
     * 插入节点
     *
     * @param node
     */
    private void insert(MyRBNode node) {
        //查找要放位置的父节点
        MyRBNode parent = null;
        MyRBNode x = this.root;
        while (x != null) {
            parent = x;

            int cmp = node.key.compareTo(x.key);
            //cmp > 0 在右边查找
            //cmp = 0 替换值
            //cmp < 0 在左边查找
            if (cmp > 0) {
                x = x.right;
            } else if (cmp == 0) {
                x.setValue(node.getValue());
                return;
            } else {
                x = x.left;
            }
        }
        node.parent = parent;
        int cmp = node.key.compareTo(parent.key);
        if (cmp > 0) {
            parent.right = node;
        } else {
            parent.left = node;
        }
        insertFIxUp(node);
    }

    /**
     * 平衡红黑树的方法
     * 1.为根节点
     * 2.相等
     * 3.父亲为黑色
     *
     * @param node
     * @return
     */
    private void insertFIxUp(MyRBNode node) {
        this.root.color = BLACK;

        MyRBNode parent = parentOf(node);
        MyRBNode gparent = parentOf(parent);

        //-------------------------------parent为空代表根节点,不予处理,若父节点为黑色,也不予处理----------------------------------------------------------------/
        if (parent != null && isRed(parent)) {
            MyRBNode uncle = null;
            if (parent == gparent.left) {

                //-------------------------------当叔叔节点是右子树时,即父节点处在左子树上----------------------------------------------------------------/
                uncle = gparent.right;
                //------------1.如果父亲是红,叔叔也是红   将父亲叔叔变成黑色,将爷爷变成红色,用爷爷节点进入下一个循环--------------------------------------------/
                if (uncle != null && isRed(uncle)) {
                    setBlack(parent);
                    setBlack(uncle);
                    setRed(gparent);
                    insertFIxUp(gparent);
                    return;
                }
                if (uncle == null || isBlack(uncle)) {
                    //如果叔叔为黑色或者不存在时,判断在左子树还是右子树
                    if (node == parent.left) {//如果是左子树,将他的父节点变黑,爷爷节点变红,以爷爷节点进行右旋
                        setBlack(parent);
                        setRed(gparent);
                        rightRotate(gparent);
                        return;
                    } else {
                        leftRotate(parent);
                        insertFIxUp(parent);
                        return;
                    }
                }
            } else {
                //-------------------------------当叔叔节点是左子树时,即父节点处在右子树上----------------------------------------------------------------/
                uncle = gparent.left;
                //如果父亲是红,叔叔也是红   将父亲叔叔变成黑色,将爷爷变成红色,用爷爷节点进入下一个循环
                if (uncle != null && isRed(uncle)) {
                    setBlack(parent);
                    setBlack(uncle);
                    setRed(gparent);
                    insertFIxUp(gparent);
                    return;
                }
                if (uncle == null || isBlack(uncle)) {
                    if (node == parent.right) {//如果是右子树,将他的父节点变黑,爷爷节点变红,以爷爷节点进行左旋
                        setBlack(parent);
                        setRed(gparent);
                        leftRotate(gparent);
                        return;
                    }
                    if (node == parent.left) {
                        rightRotate(parent);
                        insertFIxUp(parent);
                        return;
                    }
                }
            }


        }
    }

    //获取父节点
    private MyRBNode parentOf(MyRBNode node) {
        if (node != null) {
            return node.parent;
        }
        return null;
    }

    /**
     * 判断该节点是否为红节点
     *
     * @param node
     * @return
     */
    private boolean isRed(MyRBNode node) {
        if (node != null) {
            return node.color == RED;
        }
        return false;
    }

    /**
     * 判断该节点是否为黑节点
     *
     * @param node
     * @return
     */
    private boolean isBlack(MyRBNode node) {
        if (node != null) {
            return node.color == BLACK;
        }
        return false;
    }


    /**
     * 置为红色,置为黑色
     *
     * @param node
     */
    private void setRed(MyRBNode node) {
        if (node != null) {
            node.color = RED;
        }
    }

    private void setBlack(MyRBNode node) {
        if (node != null) {
            node.color = BLACK;
        }
    }

    /**
     * 左旋 :    p                      p
     *            |                      |
     *            x                      y
     *           / \     ---->          / \
     *          lx   y                  x  ry
     *              / \                / \
     *             ly  ry             lx ly
     * <p>
     * 1.将x的右节点指向y的左节点,将y的左节点的父节点指向x
     * 2. 当p不为空时,将y的父节点指向x的父节点,判断左右子树将此时y的父节点左或者右指向y,
     * 当p为空时,更新root节点,并将y的parent置空
     * 3.将x的父节点指向y,将y的左子树指向x
     */
    private void leftRotate(MyRBNode x) {
        MyRBNode y = x.right;
        //1.将x的右节点指向y的左节点,将y的左节点的父节点指向x
        x.right = y.left;
        if (y.left != null) {
            y.left.parent = x;
        }
        //2. 当p不为空时,将y的父节点指向x的父节点,判断左右子树将此时y的父节点左或者右指向y,
        //   当p为空时,更新root节点,并将y的parent置空
        if (x.parent != null) {
            y.parent = x.parent;
            if (x == x.parent.left) {
                x.parent.left = y;
            } else {
                x.parent.right = y;
            }
        } else {
            this.root = y;
            this.root.parent = null;
        }
        //3.将x的父节点指向y,将y的左子树指向x
        x.parent = y;
        y.left = x;
    }


    /**
     * 右旋方法
     * 右旋示意图
     *     p                            p
     *     |                            |
     *     y                            x
     *    / \           ------>        / \
     *   x   ry                       lx  y
     *  / \                              / \
     * lx  rx                           rx  ry
     * <p>
     * 1.将y的左子树指向x的右子树,将x的右子树的父节点指向y
     * 2.当p不为空时,将x的父节点指向y的父节点,此处判断x为其父节点的左右节点,再将x的父节点的左右节点指向y
     * 当p为空时。根节点就为x
     * 3.将y的父节点指向x,将x的右子树指向y
     */
    private void rightRotate(MyRBNode y) {
        MyRBNode x = y.left;
        //1.将y的左子树指向x的右子树,将x的右子树的父节点指向y
        y.left = x.right;
        if (x.right != null) {
            x.right.parent = y;
        }
        //2.当p不为空时,将x的父节点指向y的父节点,此处判断x为其父节点的左右节点,再将x的父节点的左右节点指向y
        if (y.parent != null) {
            x.parent = y.parent;
            if (y == y.parent.left) {
                y.parent.left = x;
            } else {
                y.parent.right = x;
            }
        } else { //  当p为空时。根节点就为x
            this.root = x;
            this.root.parent = null;
        }
        //3.将y的父节点指向x,将x的右子树指向y
        y.parent = x;
        x.right = y;
    }

    //节点内部类
    static class MyRBNode<K extends Comparable<K>, V> {
        private MyRBNode parent;
        private MyRBNode left;
        private MyRBNode right;
        private boolean color;
        private K key;
        private V value;

        public MyRBNode() {
        }

        public MyRBNode(MyRBNode parent, MyRBNode left, MyRBNode right, boolean color, K key, V value) {
            this.parent = parent;
            this.left = left;
            this.right = right;
            this.color = color;
            this.key = key;
            this.value = value;
        }

        public MyRBNode getParent() {
            return parent;
        }

        public void setParent(MyRBNode parent) {
            this.parent = parent;
        }

        public MyRBNode getLeft() {
            return left;
        }

        public void setLeft(MyRBNode left) {
            this.left = left;
        }

        public MyRBNode getRight() {
            return right;
        }

        public void setRight(MyRBNode right) {
            this.right = right;
        }

        public boolean isColor() {
            return color;
        }

        public void setColor(boolean color) {
            this.color = color;
        }

        public K getKey() {
            return key;
        }

        public void setKey(K key) {
            this.key = key;
        }

        public V getValue() {
            return value;
        }

        public void setValue(V value) {
            this.value = value;
        }
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值