目录

1、简介

2、红黑树的特点

3、插入和删除造成不平衡的修复方法

3.1 变色

 3.2 右旋

 3.3 左旋

4、完整实现红黑树


1、简介

红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。

红黑树是一种特化的AVL树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。

它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。

红黑树是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但 对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。

由于每一棵红黑树都是一颗二叉排序树,因此,在对红黑树进行查找时,可以采用运用于普通二叉排序树上的查找算法,在查找过程中不需要颜色信息。


2、红黑树的特点

红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色。 在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  1. 性质一:每个节点要么是黑色,要么是红色
  2. 性质二:根节点是黑色
  3. 性质三:每个叶子节点(NIL即NULL)
  4. 性质四:每个红色节点的两个子节点一定是黑色
  5. 性质五:任意一节点到两个子节点的路径都包含相同的黑节点,俗称黑高

如下图:

java红黑树的原理 java 红黑树_java红黑树的原理

3、插入和删除造成不平衡的修复方法

当我们在对红黑树进行插入和删除等操作时,对树做了修改,那么可能会违背红黑树的性质。

为了保持红黑树的性质,我们可以对相关结点做一系列的调整,通过对树进行旋转(例如左旋和右旋操作),即修改树中某些结点的颜色及指针结构,以达到对红黑树进行插入、删除结点等操作时,红黑树依然能保持它特有的性质(五点性质)。

3.1 变色

所谓变色当然是红黑互换颜色,当然根节点还是黑色。

java红黑树的原理 java 红黑树_java红黑树的原理_02

                            

java红黑树的原理 java 红黑树_红黑树_03

 3.2 右旋

下图我们可以发下在右子树有两个红色节点相连,这不符合上面提过的性质5,我们需要对它变色右旋。

java红黑树的原理 java 红黑树_java_04

第一步:先让L和LL变成相反的颜色,第二步以L的位置为旋转点让root的左孩子变成LL,L变成RL的右孩子,如果LLR有右孩子,LL的右孩子变成L的左孩子。此时,已恢复红黑树的平衡了。

 

java红黑树的原理 java 红黑树_子节点_05

java红黑树的原理 java 红黑树_红黑树_06

 右旋代码如下:

/**
     *右旋方法
     *右旋示意图
     *
     *         P                    P
     *         |                    |
     *         y                    x
     *        / \    ------->      / \
     *       x  ry                lx  y
     *      / \                      / \
     *     lx  ly                   ly  ry
     *
     * 1.将y的左子节点指向x的右子节点,并更新x的右子节点的父亲节点为y
     * 2.当y的父节点不为空时,更新x的父节点为y的父节点,更新y的父节点指定子节点为x
     * 3.更新y的父节点为x,更新x的右子节点为y
     */
    private void rightRotate(RBNode y){
        RBNode x=y.left;
        //1.将y的左子节点指向x的右子节点,并更新x的右子节点的父亲节点为y
        y.left=x.right;
        if(x.right!=null){
            x.right.parent=y;
        }

        //2.当y的父节点不为空时,更新x的父节点为y的父节点,更新y的父节点指定子节点为x
        if(y.parent!=null){
            x.parent=y.parent;
            if(y == y.parent.left){//判断是否为父节点左孩子
                y.parent.left=x;
            }else{
                y.parent.right=x;
            }
        }else{//y为根节点
            this.root=x;
            this.root.parent=null;
        }

        //3.更新y的父节点为x,更新x的右子节点为y
        y.parent=x;
        x.right=y;
    }

 3.3 左旋

可以看到下图RR双红,首先我们还是先将LR和其父节点L变色,然后左旋恢复红黑树平衡

java红黑树的原理 java 红黑树_子节点_07

第一步变色

 

java红黑树的原理 java 红黑树_父节点_08

 第二步左旋 让root的左孩子变为LR 同时L变成LR的左孩子,如果LR有左孩子,需变为L右孩子。

java红黑树的原理 java 红黑树_子节点_09

 代码实现如下:

/**
     * 左旋方法
     * 左旋示意图 左旋x结点
     *    p               p
     *    |               |
     *    x   ---->       y
     *   / \            / \
     *  lx  y          x   ry
     *     / \        / \
     *   ly   ry     lx  ly
     *
     */
      private void leftRotate(RBNode x){
          RBNode y=x.right;
          //1.将x的右子结点指向y的左子结点(ly),将y的左子节点父节点更新为x
          x.right=y.left;
          if(y.left!=null){
              y.left.parent=x;
          }
          //2.当x的父节点不为null时,更新y的父节点为x的父节点指定子树(当时x子树的位置) 指定为y
          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;
      }

4、完整实现红黑树

/**
 * TODO
 *
 * @author DELL
 * @version 1.0
 * @since 2022-04-24  10:06:39
 * 性质一:每个节点要么是黑色,要么是红色
 * 性质二:根节点是黑色
 * 性质三:每个叶子节点(NIL即NULL)
 * 性质四:每个红色节点的两个子节点一定是黑色
 * 性质五:任意一节点到两个叶子节点的路径都包含相同的黑节点,俗称黑高
 *
 * 变色:结点的颜色由红变黑或由黑变红
 * 左旋:以某个结点作为作为支点(旋转节点),其左子结点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的右子节点,右子节点保持不变
 * 右旋:以某个结点作为作为支点(旋转节点),其右子结点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的左子节点,左子节点保持不变
 *
 * 创建RBtree,定义颜色
 * 创建RBNode
 * 辅助定义方法:parentOf(node),isRed(node),setRed(node),setBlack(node),inOrderPrint()
 * 左旋方法定义:leftRotate(node)
 * 右旋方法定义:rightRotate(node)
 * 公开插入接口定义方法定义:insert(K key,V value)
 * 内部插入接口方法定义:insert(RBNode node)
 * 修正插入导致红黑树失衡的方法定义:insertFixUp(RBNode node)
 */
public class NewRBTree<K extends Comparable<K>,V>{
    private final boolean RED=true;

    private final boolean BLACK=false;

    private Node<K, V> root;


    class Node<K extends Comparable<K>,V>{

        private K key;

        private V value;

        private boolean color;

        private Node<K, V> parent;

        private Node<K, V> left;

        private Node<K, V> right;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public boolean isColor() {
            return color;
        }

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

        public Node getLeft() {
            return this.left;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        public Node<K, V> getParent() {
            return parent;
        }

        public Node<K, V> getRight() {
            return right;
        }
    }
    /**
     * 左旋方法
     * 左旋示意图 左旋x结点
     *    p               p
     *    |               |
     *    x   ---->       y
     *   / \            / \
     *  lx  y          x   ry
     *     / \        / \
     *   ly   ry     lx  ly
     *
     */
    private void leftRotate(Node<K, V> x){

        Node<K, V> y=x.right;

        x.right=y.left;

        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;
            y.parent=null;
        }

        x.parent=y;

        y.left=x;
    }
    /**
     *右旋方法
     *右旋示意图
     *
     *         P                    P
     *         |                    |
     *         y                    x
     *        / \    ------->      / \
     *       x  ry                lx  y
     *      / \                      / \
     *     lx  ly                   ly  ry
     */
    private void RightRotate(Node<K, V> y){
       // 1.将x的右子节点指向y的左子节点,并更新y的左子节点的父亲节点为x
        Node<K, V> x=y.left;

        y.left=x.right;
        // 2.当y的父节点不为空时,更新y的父节点为x的父节点
        if(y.parent!=null){
            x.parent=y.parent;
            //判断y是其父节点的左孩子还有右孩子
            if(y==y.parent.left){
                y.parent.left=x;
            }else{
                y.parent.right=x;
            }

        }else{
            this.root=x;
            x.parent=null;
        }
        //3.更新y的父节点为x,更新x的右子节点为y
        y.parent=x;
        x.right=y;

    }

    //是否是红色
    private boolean isRed(Node node){
        return node.color==RED;
    }
    //是否是红色
    private boolean isBlack(Node node){
        return node.color==BLACK;
    }
    //返回节点的父亲
    private Node<K, V> parentOf(Node node){
        return node.parent;
    }
    //设置红色
    private void setRED(Node node){
        node.color=RED;
    }
    //设置黑色
    public void setBLACK(Node node){
        node.color=BLACK;
    }
    //添加
    public void insert(K key,V value){
        Node<K, V> node=new Node<>(key,value);
        node.setColor(RED);
        insert(node);
    }

    private void insert(Node<K, V> node){
        Node<K, V> newRoot=root;
        Node<K, V> parent=null;

        while(newRoot!=null){
            parent=newRoot;
            if(node.key.compareTo(newRoot.key)>0){
                newRoot=newRoot.right;
            }else if(node.key.compareTo(newRoot.key)<0){
                newRoot=newRoot.left;
            }else{
                newRoot.value=node.value;
                return;
            }
        }
        node.parent=parent;

        if(parent==null){
            this.root=node;
        }else if(parent.key.compareTo(node.key)>0){
            parent.left=node;
        }else{
            parent.right=node;
        }

        insertFixUp(node);//修复红黑树
    }

    /**
     * 插入后修复红黑树平衡的方法
     * |---情景1:红黑树为空树
     * |---情景2:插入的节点key已经存在
     * |---情景3:插入的节点的父节点为黑色
     * |---情景4:插入节点的父亲节点为红色
     *     |---情景4.1:叔叔节点存在,并且为红色(父-叔双红),将爸爸和叔叔染成黑色,爷爷染成红色,进行下一轮处理
     *     |---情景4.2:叔叔节点不存在,或者为黑色,父节点为爷爷节点的左子树
     *         |---情景4.2.1:插入节点为其父节点的左子节点(LL情况),将爸爸染成黑色,将爷爷染成红色然后以爷爷为旋转节点 右旋
     *         |---情景4.2.2:插入节点为其父节点的右子节点(LR情况),以爸爸节点进行一次左旋,得到LL双红的情景,然后指定爸爸节点为当前节点进行下一轮处理
     *     |---情景4.3:叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
     *          |---情景4.3.1:插入节点为其父节点的右子节点(RR情况)将爸爸染成黑色,将爷爷染成红色然后以爷爷为旋转节点 左旋
     *          |---情景4.3.2:插入节点为其父节点的左子节点(RL情况),以爸爸节点进行一次右旋,得到RR双红的情景,然后指定爸爸节点为当前节点进行下一轮处理
     */
    private void insertFixUp(Node<K, V> node){
        Node parent=node.parent;

        this.root.setColor(BLACK);
        //情景4:插入节点的父亲节点为红色
         if(parent!=null && isRed(parent)){
             //情景4.1:叔叔节点存在,并且为红色(父-叔双红),将爸爸和叔叔染成黑色,爷爷染成红色,进行下一轮处理
             Node gParent=parent.parent;
             Node uncle=null;
             int cmp=gParent.key.compareTo(parent.key);
             if(cmp<0){
                 uncle=gParent.left;
             }else if(cmp>0){
                 uncle=gParent.right;
             }

             if(uncle!=null && isRed(uncle)){
                 setBLACK(uncle);
                 setBLACK(parent);
                 setRED(gParent);
                 insertFixUp(node);
                 return;
             }

            if((uncle==null || isBlack(uncle)) && cmp>0){
                if(parent.key.compareTo(node.key)>0){
                     setBLACK(parent);
                     setRED(gParent);
                     RightRotate(gParent);
                     return;
                }else{
                     leftRotate(parent);
                     insertFixUp(parent);
                     return;
                }
            }else{
                if(parent.key.compareTo(node.key)>0){
                    RightRotate(parent);
                    insertFixUp(parent);
                    return;
                }else{
                    setBLACK(parent);
                    setRED(gParent);
                    leftRotate(gParent);
                    return;
                }
            }

         }
    }

    //中序打印
    public void inOrderPrint(){

        if(this.root==null) return;

        inOrderPrint(this.root);
    }

    public Node getRoot(){
        return this.root;
    }

    private void inOrderPrint(Node<K, V> node){
        if(node==null) return;

        inOrderPrint(node.left);
        System.out.println("key:"+node.key+",value:"+node.value);
        inOrderPrint(node.right);
    }

}

 最后再附上验证打印红黑树的类TreeOperation

/**
 * TODO
 *
 * @author DELL
 * @version 1.0
 * @since 2022-04-22  19:25:39
 */
public class TreeOperation {
      /*
    树的结构示例:
              1
            /   \
          2       3
         / \     / \
        4   5   6   7
    */

    // 用于获得树的层数
    public static int getTreeDepth(NewRBTree.Node root) {
        return root == null ? 0 : (1 + Math.max(getTreeDepth(root.getLeft()), getTreeDepth(root.getRight())));
    }


    private static void writeArray(NewRBTree.Node currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
        // 保证输入的树不为空
        if (currNode == null) return;
        // 先将当前节点保存到二维数组中
        res[rowIndex][columnIndex] = String.valueOf(currNode.getKey() + "-" + (currNode.isColor() ? "R" : "B") + "");

        // 计算当前位于树的第几层
        int currLevel = ((rowIndex + 1) / 2);
        // 若到了最后一层,则返回
        if (currLevel == treeDepth) return;
        // 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
        int gap = treeDepth - currLevel - 1;

        // 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
        if (currNode.getLeft() != null) {
            res[rowIndex + 1][columnIndex - gap] = "/";
            writeArray(currNode.getLeft(), rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
        }

        // 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
        if (currNode.getRight() != null) {
            res[rowIndex + 1][columnIndex + gap] = "\\";
            writeArray(currNode.getRight(), rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
        }
    }


    public static void show(NewRBTree.Node root) {
        if (root == null) System.out.println("EMPTY!");
        // 得到树的深度
        int treeDepth = getTreeDepth(root);

        // 最后一行的宽度为2的(n - 1)次方乘3,再加1
        // 作为整个二维数组的宽度
        int arrayHeight = treeDepth * 2 - 1;
        int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
        // 用一个字符串数组来存储每个位置应显示的元素
        String[][] res = new String[arrayHeight][arrayWidth];
        // 对数组进行初始化,默认为一个空格
        for (int i = 0; i < arrayHeight; i ++) {
            for (int j = 0; j < arrayWidth; j ++) {
                res[i][j] = " ";
            }
        }

        // 从根节点开始,递归处理整个树
        // res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0');
        writeArray(root, 0, arrayWidth/ 2, res, treeDepth);

        // 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
        for (String[] line: res) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < line.length; i ++) {
                sb.append(line[i]);
                if (line[i].length() > 1 && i <= line.length - 1) {
                    i += line[i].length() > 4 ? 2: line[i].length() - 1;
                }
            }
            System.out.println(sb.toString());
        }
    }
}