数据结构:红黑树

一、核心定义

红黑树(Red-Black Tree)是一种近似平衡的二叉搜索树(BST) ,通过给节点添加「颜色」属性并遵守严格的颜色规则,保证任意节点到其所有叶子节点的「黑色节点路径长度一致」,从而将树的高度控制在 2log₂(n+1) 以内,确保查询、插入、删除的时间复杂度稳定在 O(log n)

它是工业界应用最广泛的平衡二叉树(如 Java 的 TreeMap/TreeSet、Linux 内核、HashMap 链表转树),核心优势是「插入/删除时旋转次数少」,适合频繁修改的场景。

资料:https://pan.quark.cn/s/43d906ddfa1bhttps://pan.quark.cn/s/90ad8fba8347https://pan.quark.cn/s/d9d72152d3cf

二、红黑树的5条核心规则(必须满足)

  1. 颜色规则:每个节点要么是红色,要么是黑色;
  2. 根节点规则:根节点必须是黑色;
  3. 叶子节点规则:所有「NIL 叶子节点」(空节点)是黑色(注:实现中通常省略 NIL 节点,默认空指针为黑色叶子);
  4. 红色节点规则:红色节点的子节点必须是黑色(即「红节点不相邻」,杜绝连续红节点);
  5. 路径规则:从任意节点到其所有叶子节点的路径中,黑色节点的数量相同(称为「黑高」)。

关键概念:黑高(Black Height)

  • 定义:从某节点到叶子节点的路径中,黑色节点的数量(不含当前节点);
  • 约束:红黑树的黑高决定了树的平衡程度,任意路径的总长度 ≤ 2×黑高(因为红节点不连续)。

三、核心特性

  1. 继承BST特性:左子树所有节点值 < 根节点值,右子树所有节点值 > 根节点值,中序遍历为有序序列;
  2. 近似平衡:树的高度 ≤ 2log₂(n+1)(最坏情况),远优于退化为链表的 BST(O(n));
  3. 修改效率高:插入最多旋转 2 次,删除最多旋转 3 次,远少于 AVL 树(删除可能多次旋转);
  4. 空间开销低:仅需为每个节点存储 1bit 的颜色信息(红/黑),比 AVL 树的「高度字段」(int 型)更节省空间。

四、插入操作(核心流程)

插入是红黑树最核心的操作,遵循「先按 BST 插入 → 标记为红色 → 修复颜色规则」的逻辑(标记为红色可最小化破坏规则)。

1. 插入前准备

  • 新节点默认标记为红色(若标记为黑色,会直接破坏「路径黑节点数量一致」规则;标记为红色仅可能破坏「红节点不相邻」规则);
  • 插入位置为 BST 的叶子节点位置(替换 NIL 叶子)。

2. 失衡场景与修复(按优先级处理)

设:新节点为 Z,父节点为 P,祖父节点为 G,叔叔节点为 UG 的另一个子节点)。

场景 条件 修复策略
场景1 父节点 P 是黑色 无需修复(所有规则满足)
场景2 父节点 P 是红色,叔叔 U 是红色 1. 将 PU 改为黑色; 2. 将 G 改为红色; 3. 以 G 为新节点递归检查(可能触发上层失衡)
场景3 父节点 P 是红色,叔叔 U 是黑色,且 ZP 的右子树(LR型) 1. 对 P 做左旋转,将 Z 变为 P 的父节点; 2. 转化为场景4
场景4 父节点 P 是红色,叔叔 U 是黑色,且 ZP 的左子树(LL型) 1. 将 P 改为黑色,G 改为红色; 2. 对 G 做右旋转; 3. 修复完成(无需递归)

补充:若父节点 P 是祖父 G 的右子树,对应镜像场景(RR型/RL型),修复策略为「场景3→旋转方向相反,场景4→旋转方向相反」。

3. 插入修复示例(LL型)

复制代码
初始树(G为根,黑色):
        G(B)
       /
      P(R)
     /
    Z(R) (新插入)

修复步骤:
1. P 改为黑色,G 改为红色:
        G(R)
       /
      P(B)
     /
    Z(R)
2. 对 G 右旋转:
      P(B)
     /   \
    Z(R)  G(R)
3. 根节点强制改为黑色(最终):
      P(B)
     /   \
    Z(R)  G(B)

五、删除操作(核心逻辑)

删除是红黑树最复杂的操作,核心是「先按 BST 删除 → 修复被破坏的颜色规则」,难点在于处理「黑色节点被删除后导致的黑高失衡」。

1. 核心前提

  • 若删除的是红色节点:直接删除,不破坏任何规则(无需修复);
  • 若删除的是黑色节点:会导致该路径的黑高减少 1,触发「双重黑」修复(给删除位置标记「额外黑」,通过调整颜色/旋转消除)。

2. 修复核心思路

设:被删除黑色节点的子节点为 X(携带「双重黑」标记),兄弟节点为 S,父节点为 P

核心场景 修复策略
场景1 X 是根节点
场景2 S 是红色
场景3 S 是黑色,且 S 的两个子节点都是黑色
场景4 S 是黑色,S 的左子节点红色、右子节点黑色(RL型)
场景5 S 是黑色,S 的右子节点红色(RR型)

注:若 X 是父节点的右子树,对应镜像场景(LL型/LR型),旋转方向相反。

六、红黑树 vs AVL树(面试高频对比)

对比维度 红黑树 AVL树
平衡标准 近似平衡(黑高一致) 严格平衡(
树高上限 2log₂(n+1) log₂n + 1
旋转次数 插入最多2次,删除最多3次 插入最多2次,删除可能O(log n)次
空间开销 1bit/节点(颜色) 4字节/节点(高度)
查询效率 略低(树高稍高) 更高(树高更矮)
修改效率 更高(旋转少) 更低(旋转多)
适用场景 插入/删除频繁(如HashMap、TreeSet) 查询频繁(如数据库索引辅助结构)
工业应用 极广泛(Java、Linux、Redis) 较少(仅部分小众场景)

七、代码实现示例(Java:红黑树核心逻辑)

java 复制代码
// 红黑树节点颜色枚举
enum Color {
    RED, BLACK
}

// 红黑树节点定义
class RBNode {
    int val;
    Color color;
    RBNode left, right, parent; // 父节点(红黑树必须维护)

    public RBNode(int val) {
        this.val = val;
        this.color = Color.RED; // 新节点默认红色
        this.left = this.right = this.parent = null;
    }
}

// 红黑树核心实现
class RBTree {
    private RBNode root;
    private final RBNode NIL; // 哨兵节点(代替空指针,统一处理叶子节点)

    public RBTree() {
        NIL = new RBNode(-1);
        NIL.color = Color.BLACK;
        root = NIL;
    }

    // 左旋转
    private void leftRotate(RBNode x) {
        RBNode y = x.right; // y是x的右子树
        x.right = y.left;   // 将y的左子树设为x的右子树

        if (y.left != NIL) {
            y.left.parent = x;
        }
        y.parent = x.parent; // y的父节点改为x的父节点

        if (x.parent == NIL) { // x是根节点
            root = y;
        } else if (x == x.parent.left) { // x是左子树
            x.parent.left = y;
        } else { // x是右子树
            x.parent.right = y;
        }
        y.left = x; // x设为y的左子树
        x.parent = y;
    }

    // 右旋转
    private void rightRotate(RBNode y) {
        RBNode x = y.left; // x是y的左子树
        y.left = x.right;  // 将x的右子树设为y的左子树

        if (x.right != NIL) {
            x.right.parent = y;
        }
        x.parent = y.parent; // x的父节点改为y的父节点

        if (y.parent == NIL) { // y是根节点
            root = x;
        } else if (y == y.parent.right) { // y是右子树
            y.parent.right = x;
        } else { // y是左子树
            y.parent.left = x;
        }
        x.right = y; // y设为x的右子树
        y.parent = x;
    }

    // 插入后修复红黑树规则
    private void insertFixup(RBNode z) {
        while (z.parent.color == Color.RED) { // 父节点是红色才需要修复
            if (z.parent == z.parent.parent.left) { // 父节点是祖父的左子树
                RBNode u = z.parent.parent.right; // 叔叔节点

                // 场景2:叔叔是红色
                if (u.color == Color.RED) {
                    z.parent.color = Color.BLACK;
                    u.color = Color.BLACK;
                    z.parent.parent.color = Color.RED;
                    z = z.parent.parent; // 递归检查祖父
                } else {
                    // 场景3:叔叔是黑色,z是右子树(LR型)
                    if (z == z.parent.right) {
                        z = z.parent;
                        leftRotate(z);
                    }
                    // 场景4:叔叔是黑色,z是左子树(LL型)
                    z.parent.color = Color.BLACK;
                    z.parent.parent.color = Color.RED;
                    rightRotate(z.parent.parent);
                }
            } else { // 父节点是祖父的右子树(镜像场景)
                RBNode u = z.parent.parent.left; // 叔叔节点

                // 场景2:叔叔是红色
                if (u.color == Color.RED) {
                    z.parent.color = Color.BLACK;
                    u.color = Color.BLACK;
                    z.parent.parent.color = Color.RED;
                    z = z.parent.parent;
                } else {
                    // 场景3:叔叔是黑色,z是左子树(RL型)
                    if (z == z.parent.left) {
                        z = z.parent;
                        rightRotate(z);
                    }
                    // 场景4:叔叔是黑色,z是右子树(RR型)
                    z.parent.color = Color.BLACK;
                    z.parent.parent.color = Color.RED;
                    leftRotate(z.parent.parent);
                }
            }
        }
        root.color = Color.BLACK; // 根节点强制设为黑色
    }

    // 插入节点(BST插入 + 修复)
    public void insert(int val) {
        RBNode z = new RBNode(val);
        RBNode y = NIL; // 记录父节点
        RBNode x = root;

        // 1. 按BST规则找到插入位置
        while (x != NIL) {
            y = x;
            if (z.val < x.val) {
                x = x.left;
            } else if (z.val > x.val) {
                x = x.right;
            } else {
                return; // 不允许重复值
            }
        }
        z.parent = y;

        // 2. 挂载新节点
        if (y == NIL) {
            root = z; // 树为空,新节点为根
        } else if (z.val < y.val) {
            y.left = z;
        } else {
            y.right = z;
        }

        // 3. 初始化叶子节点(哨兵)
        z.left = NIL;
        z.right = NIL;

        // 4. 修复红黑树规则
        insertFixup(z);
    }

    // 中序遍历(验证有序性)
    public void inorder(RBNode node) {
        if (node != NIL) {
            inorder(node.left);
            System.out.print(node.val + "(" + (node.color == Color.RED ? "R" : "B") + ") ");
            inorder(node.right);
        }
    }

    // 获取根节点(供遍历调用)
    public RBNode getRoot() {
        return root;
    }
}

// 测试代码
public class RBTreeTest {
    public static void main(String[] args) {
        RBTree rbTree = new RBTree();
        int[] nums = {10, 20, 30, 15, 25, 5};
        for (int num : nums) {
            rbTree.insert(num);
        }
        // 中序遍历输出:5(R) 10(B) 15(R) 20(B) 25(R) 30(B)
        rbTree.inorder(rbTree.getRoot());
    }
}
相关推荐
zore_c1 小时前
【C语言】文件操作详解2(文件的顺序读写操作)
android·c语言·开发语言·数据结构·笔记·算法·缓存
大袁同学1 小时前
【C++完结篇】:深入“次要”但关键的知识腹地
开发语言·数据结构·c++·算法
明洞日记1 小时前
【数据结构手册006】映射关系 - map与unordered_map的深度解析
数据结构·c++
量子炒饭大师1 小时前
初探算法的魅力——【暴力枚举】
c语言·数据结构·c++·算法·动态规划
hweiyu001 小时前
数据结构:伸展树
数据结构
hweiyu001 小时前
数据结构:平衡二叉树
数据结构
Undergoer_TW1 小时前
20251204_线程安全问题及STL数据结构的存储规则
数据结构·c++·哈希算法
952361 小时前
并查集 / LRUCache
数据结构·算法
potato_may10 小时前
链式二叉树 —— 用指针构建的树形世界
c语言·数据结构·算法·链表·二叉树