之前看 HashMap jdk1.8中的源码,就有提到红黑树,这次我们来了解以下红黑树究竟是怎样一种数据结构。

定义

红黑树(Red Black Tree)RBT 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。
它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。
红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的:它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。

二叉查找树

二叉查找树,Binary Search Tree 「BST」,我们首先看下二叉查找树有哪些特性呢?

  1. 某节点的左子树节点值仅包含小于该节点值
  2. 某节点的右子树节点值仅包含大于该节点值
  3. 左右子树每个也必须是二叉查找树

红黑树特征:

  1. 每个节点非黑即红
  2. 根节点是黑色的
  3. 每个叶节点都是黑色的
  4. 一个红色节点的直接子节点都是黑色的(不能有两个红色节点连着有父子关系)
  5. 从任意一个节点到叶节点,经过的黑色节点个数(黑高)是一样的

红黑树动态演示

这个网站可以帮助理解红黑树插入删除的过程。
变色的时机可能不太对,他统一放在最后变色了,理解上习惯右旋后直接变色,来判断下一步操作。

插入操作

插入会改变树的节点,树节点改变会有以下两种方式。

  • 变色 recolor
  • 旋转 rotation

为防止树全黑,且为了方便根节点到子节点黑色数相等,插入一般预设为红色节点

变色:

条件:父红叔红变颜色
颜色变化:父变黑,叔变黑,爷变红,如果变色不能达到红黑树的要求,再尝试旋转。

左旋

条件:父红叔黑右子树

右旋

条件:父红叔黑左子树

4种情况

  1. 父左子左
    左左
  2. 父左子右
    左右
  3. 父右子右
    右右
  4. 父右子左
    右左

删除操作

// todo 删除操作太麻烦了,先搁置
  真佩服那些面试能手写红黑树的大佬,我能记个插入的几种情况就不错了,还得看着规则写。能写出删除操作的是真牛!!!小弟在此给大佬顶礼膜拜Orz。

手写红黑树代码

  花了三天时间写出来的,估计挺多漏洞的,不过自己测了测插入目前没遇到啥问题,删除回头有空再写吧。算是自己造了个方形的轮子。
  有点好处就是写了个toImage()方法,可以将构造的红黑树画出来,比较直观。下面上代码:

import javax.swing.*;
import java.awt.*;

/**
 * @ClassName RedBlackTree
 * @Description 红黑树
 * @Author leo
 * @Date 2019-09-24 09:57
 **/
public class RedBlackTree extends JFrame {
    static final int R = 0;//红色
    static final int B = 1;//黑色
    Node root;

    public RedBlackTree() {
        super();
        initialize(500);// 调用初始化方法
    }

    static class Node {
        //颜色
        int color;
        //值
        int value;
        //父节点
        Node parent;
        //左节点
        Node left;
        //右节点
        Node right;

        public Node(int value) {
            this.color = R;
            this.value = value;
        }
    }

    /**
     * 插入新节点 值为参数
     *
     * @param value
     */
    public void insert(int value) {
        Node newNode = new Node(value);
        insert(newNode);
    }

    /**
     * 插入新节点
     *
     * @param newNode
     */
    public void insert(Node newNode) {
        Node curNode = root;
        if (root == null) {
            root = newNode;
            //判断是否符合规则
            judgeRule(newNode);
        } else if (curNode.value > newNode.value) {
            if (curNode.left != null) {
                insert(curNode.left, newNode);
            } else {
                curNode.left = newNode;
                newNode.parent = curNode;
                //判断是否符合规则
                judgeRule(newNode);
            }
        } else {
            if (curNode.right != null) {
                insert(curNode.right, newNode);
            } else {
                curNode.right = newNode;
                newNode.parent = curNode;
                //判断是否符合规则
                judgeRule(newNode);
            }
        }
    }

    /**
     * 插入递归寻找合适位置
     *
     * @param curNode
     * @param newNode
     */
    public void insert(Node curNode, Node newNode) {
        if (curNode.value > newNode.value) {
            if (curNode.left != null) {
                insert(curNode.left, newNode);
            } else {
                curNode.left = newNode;
                newNode.parent = curNode;
                //判断是否符合规则
                judgeRule(newNode);
            }
        } else {
            if (curNode.right != null) {
                insert(curNode.right, newNode);
            } else {
                curNode.right = newNode;
                newNode.parent = curNode;
                //判断是否符合规则
                judgeRule(newNode);
            }
        }
    }

    /**
     * 判断是否符合规则
     *
     * @param curNode
     */
    public void judgeRule(Node curNode) {
        //判断是否父节点也是红色
        if (curNode.parent == null) {
            //父节点为空,说明节点是根节点,变黑色
            curNode.color = B;
            root = curNode;
        } else {
            //不是根节点
            if (curNode.parent.color == R) {
                //父节点也为红色
                Node grandparent = curNode.parent.parent;
                Node uncle;
                int parentLR; //0左 1右
                if (curNode.parent == grandparent.left) {
                    parentLR = 0;
                    //父亲左子树
                    if (grandparent.right != null) {
                        //不为null
                        uncle = grandparent.right;
                    } else {
                        //null节点 黑色
                        uncle = null;
                    }
                } else {
                    parentLR = 1;
                    //父亲右子树
                    if (grandparent.left != null) {
                        //不为null
                        uncle = grandparent.left;
                    } else {
                        //null节点 黑色
                        uncle = null;
                    }
                }
                if (uncle != null && uncle.color == R) {
                    //父红叔红变颜色
                    recolor(curNode, uncle);
                } else {
                    //父红叔黑判断 当前节点左右子树
                    if (curNode.value < curNode.parent.value) {
                        if (parentLR == 0) {
                            //父左子左 右旋
                            rightRotate(curNode.parent);
                        } else {
                            //父右子左 右旋
                            RLRotate(curNode);
                            leftRotate(curNode);
                        }

                    } else {
                        if (parentLR == 1) {
                            //父右子右 左旋
                            leftRotate(curNode.parent);
                        } else {
                            //父左子右 左旋
                            LRRotate(curNode);
                            rightRotate(curNode);
                        }
                    }
                }
            }
        }
    }

    /**
     * 变色
     *
     * @param curNode
     * @param uncle
     */
    private void recolor(Node curNode, Node uncle) {
        //父变黑
        curNode.parent.color = B;
        //叔变黑
        uncle.color = B;
        //爷变红
        uncle.parent.color = R;
        judgeRule(uncle.parent);
    }

    /**
     * 左旋 父子互换,前父挂左子,前子挂前爷,父子颜色互换
     *
     * @param curNode
     */
    private void leftRotate(Node curNode) {
        //当前父节点
        Node parent = curNode.parent;
        //当前爷节点
        Node grandparent = curNode.parent.parent;
        //判断爷节点是否为空
        if (grandparent != null) {
            //如果不为空
            if (parent == grandparent.left) {
                //父节点是左子树,当前节点就挂爷节点左子树
                grandparent.left = curNode;
            } else {
                //父节点是右子树,当前节点就挂爷节点右子树
                grandparent.right = curNode;
            }
        }
        //当前父节点挂在当前节点左子树
        parent.parent = curNode;
        //当前节点左子树挂在前父节点右子树
        parent.right = curNode.left;
        if (parent.right != null) {
            parent.right.parent = parent;
        }
        //当前节点左子树指向当前父节点
        curNode.left = parent;
        //当前节点挂到当前爷节点上
        curNode.parent = grandparent;
        //当前节点和当前父节点变色
        int color = curNode.color;
        curNode.color = parent.color;
        parent.color = color;
        if (curNode.parent == null) {
            root = curNode;
        }
    }

    /**
     * 右旋 左子树
     */
    private void rightRotate(Node curNode) {
        //当前父节点
        Node parent = curNode.parent;
        //当前爷节点
        Node grandparent = curNode.parent.parent;
        if (grandparent != null) {
            if (parent == grandparent.left) {
                grandparent.left = curNode;
            } else {
                grandparent.right = curNode;
            }
        }
        //当前父节点挂在当前节点左子树
        parent.parent = curNode;
        //当前节点右子树挂在前父节点左子树
        parent.left = curNode.right;
        if (parent.left != null) {
            parent.left.parent = parent;
        }
        //当前节点左子树指向当前父节点
        curNode.right = parent;
        //当前节点挂到当前爷节点上
        curNode.parent = grandparent;
        //当前节点和当前父节点变色
        int color = curNode.color;
        curNode.color = parent.color;
        parent.color = color;
        if (curNode.parent == null) {
            root = curNode;
        }
    }

    /**
     * 右左情况前置操作
     *
     * @param curNode
     */
    private void RLRotate(Node curNode) {
        //当前父节点
        Node parent = curNode.parent;
        //当前爷节点
        Node grandparent = curNode.parent.parent;
        //当前节点挂到爷节点上
        curNode.parent = grandparent;
        //爷节点的右子树指向当前节点
        grandparent.right = curNode;
        //前父节点挂当前节点
        parent.parent = curNode;
        //当前节点右子树
        parent.left = curNode.right;
        if (parent.left != null) {
            parent.left.parent = parent;
        }
        //当前节点
        curNode.right = parent;

    }

    /**
     * 左右情况前置操作
     *
     * @param curNode
     */
    private void LRRotate(Node curNode) {
        //当前父节点
        Node parent = curNode.parent;
        //当前爷节点
        Node grandparent = curNode.parent.parent;
        //当前节点挂到爷节点上
        curNode.parent = grandparent;
        //爷节点的左子树指向当前节点
        grandparent.left = curNode;
        //前父节点挂当前节点
        parent.parent = curNode;
        //当前节点右子树挂前父节点左子树
        parent.right = curNode.left;
        if (parent.right != null) {
            parent.right.parent = parent;
        }
        curNode.left = parent;

    }

    /**
     * 画布初始化
     *
     * @param paperSize
     */
    private void initialize(int paperSize) {// 初始化方法
        this.setSize(paperSize * 2, paperSize);// 设置窗体大小
        setDefaultCloseOperation(EXIT_ON_CLOSE);/// 设置窗体关闭方式
        this.setTitle("绘制几何图形");// 设置窗体标题
        MyCanvas c = new MyCanvas(paperSize);// 创建画布对象
        add(c);// 将画布添加到窗体中
    }

    private class MyCanvas extends Canvas {// 创建内部类MyCanvas继承Canvas
        //节点半径
        int nodeRadius = 10;
        //画布大小
        int paperSize;
        //层级限制
        int levelLimit = 6;
        //连线在x轴投影长度计算常量
        int xLineLength = 512;

        MyCanvas(int paperSize) {
            this.paperSize = paperSize;
        }


        @Override
        public void paint(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;// 调用新画图类Graphics2D(强制转化为Graphics2D这个类)
            draw(g2, paperSize - nodeRadius, 0, root, 1);
        }

        private void draw(Graphics2D g2, int x, int y, Node node, int level) {
            if (level > levelLimit) {
                level = levelLimit;//设置大于level限制,线x轴长度就不变了,展示可能会重叠,画布越大这个层级可以越高
            }
            //设置节点颜色
            g2.setColor(node.color == 1 ? Color.BLACK : Color.RED);
            //画填充色圆
            g2.fillOval(x, y, nodeRadius * 2, nodeRadius * 2);// 画一个圆形-坐标、宽高(Draw方法绘制的图形是空心的)
            //设置值的颜色为白色
            g2.setColor(Color.WHITE);
            g2.drawString(String.valueOf(node.value), x + nodeRadius / 2, y + nodeRadius / 2 * 3);
            //连线颜色为黑色
            g2.setColor(Color.BLACK);
            //线x轴投影长度
            int lineX = xLineLength >> level++;
            if (node.left != null) {
                g2.drawLine(x + nodeRadius, y + nodeRadius * 2, x + nodeRadius - lineX, y + nodeRadius * 4);
                draw(g2, x - lineX, y + nodeRadius * 4, node.left, level);
            }
            if (node.right != null) {
                g2.drawLine(x + nodeRadius, y + nodeRadius * 2, x + nodeRadius + lineX, y + nodeRadius * 4);
                draw(g2, x + lineX, y + nodeRadius * 4, node.right, level);
            }
        }

    }
}

测试代码

public static void main(String[] args){
        int[] arr = {1,2,3,4,5,6,7,8,9};
        RedBlackTree rbt = new RedBlackTree();
        for (int a:arr){
            rbt.insert(a);
        }
        rbt.setVisible(true);
    }

输出图像
rbt