【数据结构】红黑树

红黑树是一种自平衡的二叉搜索树,由Rudolf Bayer于1972年提出。常用于C++的map、set,Java的HashMap等,适用于海量数据并频繁进行插入删除操作的场景。红黑树相比AVL树,在插入删除操作较多时更具优势,但查找性能稍逊。插入操作会遵循特定规则以保持平衡,如颜色调整、旋转等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

红黑树

思维导图

在这里插入图片描述

出现的背景,初衷,要解决的问题

背景:Rudolf Bayer在1972年发明了红黑树,称为“对称二叉B树”,但红黑树这个名字来源于Leo J Guibas 和 Robert Sedgewick在1978年写的一篇论文。

Rudolf Bayer是一名慕尼黑大学的信息学教授,(信息学是一门研究信息的收集、分类、操作、存储、检索和传播的科学),他发明红黑树的初衷是为了存储和有效地检索多维数据

简介:红黑树是一棵自平衡的二叉搜索树

应用场景:

1.C++中的map,set
2.Java中的HashMap,TreeMap,TreeSet
3.用于实现Linux中的CPU调度

优势和劣势

优势:

大多数地二叉搜索树检索信息的时间复杂度为O(h),h是二叉树的高,而且如果二叉树是一颗斜树,检索的时间复杂度会达到最差情况O(n),n是树的节点个数。
而红黑树检索的时间复杂度最多是O(logn)

劣势:

在插入,删除数据后需要进行调整平衡操作(染色,左旋,右旋)。
不适用于数据较少的场景

适用的场景

适用于海量数据,且需要经常进行插入删除操作的场景

类似的树和它的对比

AVL(平衡树)比起红黑树来说更加平衡,通俗的说就是查找性能可能更优;但平衡树在插入和删除时自旋的次数可能会大于红黑树,所以如果项目中插入删除节点操作更多选择红黑树会更好,如果查询操作更多则选择AVL树更好

底层原理,关键实现

源码

组成和关键点

红黑树节点需要遵守的规则

(满足以上规则的树大致是一个平衡树)

  • 1.每个节点是红的或者黑的
  • 2.根节点始终是黑的
  • 3.红节点均不相邻(一个节点的父节点和子节点不会为红)
  • 4.从任何节点(包括根节点)到达它子孙的NULL节点,经过的黑节点数相同

红黑树的一些特点

  • 1.三个节点的链在红黑树中是不存在的
  • 2.从一个节点到它最远叶子节点的节点数,不超过两倍到它最近叶子节点的节点数
  • 3.红黑树有n个节点,它的高h<=2log2(n+1)

黑高

  • 黑高是指从根节点到叶子节点经过的黑节点数(包含黑叶子节点)
  • 性质:黑高>=树高/2

保持平衡

  • 变色

    • 颜色由红变黑或由黑变红
  • 左旋

    • 以某个节点作为支点,它的右子节点变为它的父节点,右子节点的左节点变为它的左节点,右子节点的右节点不变
  • 右旋

    • 与左旋相反,类比即可

插入

  • 操作

    • 查找插入位置
    • 插入后的自平衡
  • 情景分析

    • 1.红黑树为空

      • 把插入节点作为根节点,并设置为黑
    • 2.插入节点key已存在

      • 用新节点value值覆盖旧的value
    • 3.插入节点的父节点为黑节点

      • 由于插入的节点是红色的,如果插入节点的父节点是黑色时,不会影响红黑树的平衡,直接插入即可,无需做自平衡调整
    • 4.插入节点的父节点为红色

      • 4.1叔叔节点存在并且为红

        • 由于不能存在两个相邻的红节点,所以祖父节点为黑;因此插入节点三代的颜色为:黑红红,显然最简单的处理方法就是将之变为:红黑红
        • 处理方法:
          1.先将父节点和叔叔节点变黑
          2.将祖父节点变红
          3.将祖父节点设置为当前节点,进行后续处理
        • 1.祖父节点在变色后是红色,如果祖父节点的父节点是黑色,那就不需要做任何处理
          2.如果祖父节点的父节点是红色,就违背了红黑树的性质:红色节点不能相邻;需要将祖父节点设置为当前节点,继续做自平衡操作
      • 4.2叔叔节点不存在或者为黑,并且插入节点的父节点是祖父节点的左子节点

        • 4.2.1新插入节点,为其父节点的左子节点(LL红色情况)

          • 处理:将祖父节点设为红,父节点设为黑,进行右旋
        • 4.2.2新插入节点,为其父节点的右子节点(LR红色情况)

          • 1.将父节点作为当前节点,进行左旋
            2.此时与4.2.1的情况相同(LL双红)将左旋后的父节点继续作为当前节点,按4.2.1处理
      • 4.3叔叔节点不存在或者为黑,插入节点的父节点是祖父节点的右子节点,与4.2对应

        • 4.3.1RR
        • 4.3.2RL

红黑树手写(含打印函数):

//红黑树类

/**
 * 1.创建RBTree,定义颜色
 * 2.创建静态内部类 RBNode
 * 3.辅助方法定义: parentOf(node), isRed(node), isBlack(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.测试红黑树的正确性
 *
 * @param <K>
 * @param <V>
 */
public class RBTree<K extends Comparable<K>, V> {

    private static final boolean RED = true;
    private static final boolean BLACK = false;
    private RBNode root;

    // 打印红黑树
    public void padding ( String ch, int n )
    {
        int i;
        for ( i = 0; i < n; i++ )
            System.out.printf(ch);
    }
    void print_node (RBNode root, int level )
    {
        if ( root == null )
        {
            padding ( "\t", level );
            System.out.println( "NIL" );
        }
        else
        {
            print_node ( root.right, level + 1 );
            padding ( "\t", level );
            if(root.color == BLACK)
            {
                System.out.printf("(%d)\n", root.key );
            }
            else
                System.out.printf( "%d\n",root.key );
            print_node ( root.left, level + 1 );
        }
    }

    void print_tree()
    {
        print_node(this.root,0);
        System.out.printf("-------------------------------------------\n");
    }


    /**
     * 获取当前节点的父节点
     * @param node
     * @return
     */
    private RBNode parentOf(RBNode node) {
        if(node != null){
            return node.parent;
        }
        return null;
    }

    /**
     * 当前节点是否是红色
     * @param node
     * @return
     */
    private boolean isRed(RBNode node){
        if(node != null){
            return node.color == RED;
        }
        return false;
    }

    /**
     * 设置节点颜色为红色
     * @param node
     */
    private void setRed(RBNode node){
        if(node != null){
            node.color = RED;
        }
    }

    /**
     * 当前节点是否是黑色
     * @param node
     * @return
     */
    private boolean isBlack(RBNode node){
        if(node != null){
            return node.color == BLACK;
        }
        return false;
    }

    /**
     * 设置节点颜色为黑色
     * @param node
     */
    private void setBlack(RBNode node){
        if(node != null){
            node.color = BLACK;
        }
    }

    /**
     * 中序打印二叉树
     */
    public void inOrderPrint(){
        inOrderPrint(this.root);
    }

    private void inOrderPrint(RBNode node){
        if(node != null){
            inOrderPrint(node.left);
            System.out.println(node);
            inOrderPrint(node.right);
        }
    }

    /**
     * 左旋
     * 设x为当前节点,y为x的右子节点
     * @param x
     *
     * 步骤:
     *  1.将y的左子节点设为x的右子节点, 将y的左子节点的父亲设置为x
     *  2.如果x父亲存在,将x的父亲设置为y的父亲设置,将x的父亲的儿子设置为y
     *  3.将x的父亲设置为y, 将x设置为y的左子节点
     *
     *  注意: 如果y的左子节点为空也需要赋值给x.right
     */
    private void leftRotate(RBNode x){
        RBNode y = x.right;

        x.right = y.left;
        if (y.left != null){ //等于null的时候也需要赋值给x.right
            y.left.parent = x;
        }
        if (x.parent != null){
            y.parent = x.parent;
            if (x.parent.left == x){
                x.parent.left = y;
            }else {
                x.parent.right = y;
            }
        }else {
            this.root = y;
            this.root.parent = null;
        }
        x.parent = y;
        y.left = x;
    }

    private void rightRotate(RBNode x){
        RBNode y = x.left;
        x.left = y.right;
        if (y.right != null){ //等于null的时候也需要赋值给x.right
            y.right.parent = x;
        }
        if (x.parent != null){
            y.parent = x.parent;
            if (x.parent.left == x){
                x.parent.left = y;
            }else {
                x.parent.right = y;
            }
        }else {
            this.root = y;
            this.root.parent = null;
        }
        x.parent = y;
        y.right = x;
    }

    public void insert(K key, V value) {
        RBNode node = new RBNode();
        node.setKey(key);
        node.setValue(value);
        node.setColor(RED);
        insert(node);
    }

    private void insert(RBNode node) {
        RBNode parent = null;
        RBNode x = this.root;
        while(x != null) {
            parent = x;

            int cmp = node.key.compareTo(x.key);
            if(cmp > 0){
                x = x.right;
            }else if(cmp == 0){
                x.setValue(node.getValue());
                return ;
            }else{
                x = x.left;
            }
        }

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

        //修正红黑树平衡的方法
        insertFixUp(node);
    }

    /**
     *
     * 1. 红黑树为空,处理根节点
     * 2. 插入节点key已经存在,不用处理
     * 3. 父节点为黑,不用处理
     * 4. 需要处理
     *  4.1 父节点存在且为红色
     *      父节点是祖父的左子节点
     *      4.1.1 叔叔节点存在且为红色 变色,然后以祖父节点进行下一轮处理
     *      4.1.2 叔叔节点不存在或为黑色
     *      4.1.3 当前节点是父节点的左子节点 将父节点设为黑,祖父节点设为红,以父节点进行右旋
     *      4.1.4 当前节点是父节点的右子节点 以父节点左旋,然后传入父节点进行下一轮操作
     *
     *      父节点是祖父的右子节点
     *            与上面类似
     */

    private void insertFixUp(RBNode node){
        this.root.color = BLACK;

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

        if(parent != null && isRed(parent)){
            RBNode uncle = null;

            if(parent == gparent.left) {
                uncle = gparent.right;
                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;
                    } else {
                        rightRotate(parent);
                        insertFixUp(parent);
                        return;
                    }
                }
            }
        }
    }

    static class RBNode <K extends Comparable<K>, V>{
        private RBNode parent;
        private RBNode left;
        private RBNode right;
        private boolean color;
        private K key;
        private V value;

        @Override
        public String toString() {
            return  "key:" + key +
                    " value:" + value;
        }

        public RBNode(){}

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

        public RBNode getParent() {
            return parent;
        }

        public RBNode getLeft() {
            return left;
        }

        public RBNode getRight() {
            return right;
        }

        public boolean isColor() {
            return color;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

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

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

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

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

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

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

//main方法
public class TestRBTree {
    public static void main(String[] args) {
        RBTree<Integer, String> tree = new RBTree<>();
        for(int i = 0; i < 10; i++){
            String s = "a"+i;
            tree.insert(i+1, s);
            tree.print_tree();
            System.out.println();
        }
    }
}

参考资料

1.Red-Black Tree | Set 1 (Introduction) - GeeksforGeeks
2.B站Up主:小刘讲源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值