【java-core-collections】红黑树深度解析

红黑树深度解析

一、什么是红黑树

红黑树是一种自平衡二叉搜索树,每个节点额外存储一位颜色(红或黑),通过颜色约束来保持树的近似平衡,保证最坏情况下操作的时间复杂度为 O(log n)。

为什么需要红黑树? 二叉搜索树在极端情况下退化为链表(O(n)),AVL 树平衡条件太严格导致频繁旋转。红黑树在两者之间取了折中。

二、红黑树的五条性质

编号 性质 说明
1 每个节点是红色或黑色 -
2 根节点是黑色 -
3 NIL 叶子节点(空节点)是黑色 -
4 红色节点的两个子节点都是黑色 不能有连续红节点
5 从任一节点到其所有叶子节点的路径都包含相同数目的黑色节点 黑高相同

推论: 最长路径不超过最短路径的 2 倍。最短路径全黑,最长路径红黑交替。

复制代码
        B(13)
       /      \
     R(8)     R(17)
    /   \     /   \
  B(1) B(11) B(15) B(25)
  / \   / \   / \   / \
 NIL NIL ...  ...   ...

三、核心操作

3.1 左旋与右旋

旋转是红黑树维持平衡的基本操作,不改变 BST 性质。

复制代码
左旋(以 x 为轴):           右旋(以 y 为轴):

    x                           y
   / \                         / \
  a   y        ←→             x   c
     / \                     / \
    b   c                   a   b
java 复制代码
// 左旋伪代码
void leftRotate(Node x) {
    Node y = x.right;
    x.right = y.left;       // y 的左子树挂到 x 右边
    if (y.left != NIL) y.left.parent = x;
    y.parent = x.parent;    // y 接替 x 的位置
    if (x.parent == NIL) root = y;
    else if (x == x.parent.left) x.parent.left = y;
    else x.parent.right = y;
    y.left = x;             // x 变成 y 的左子
    x.parent = y;
}

3.2 插入

规则:新插入的节点默认为红色(避免违反性质 5)。

插入后通过变色 + 旋转修复平衡:

情况 父节点 叔叔节点 操作
1 - - 新节点是根 → 变黑
2 黑色 - 无需修复(不违反任何性质)
3 红色 红色 父、叔变黑,祖父变红,递归修复祖父
4 红色(左) 黑色/NIL 父变黑,祖父变红,以祖父为轴右旋
5 红色(右) 黑色/NIL 先以父为轴左旋,转为情况 4
复制代码
插入示例(依次插入 10, 20, 30):

1. 插入 10: [B(10)]                    → 根变黑
2. 插入 20: [B(10) → R(20)]            → 父黑,无需修复
3. 插入 30: [B(10) → R(20) → R(30)]    → 父红叔黑
   修复: 左旋 10,变色
   结果:
       B(20)
      /    \
    R(10)  R(30)

3.3 删除

删除比插入复杂,分两步:

  1. BST 删除:找到后继节点替换,实际被删除的最多只有一个子节点
  2. 修复平衡:如果删除的是黑色节点,违反性质 5,需要修复

修复核心思想:将兄弟节点的"多余黑色"转移上来。

情况 兄弟颜色 兄弟子节点 操作
1 - 兄变黑,父变红,旋转,转换其他情况
2 两子均黑 兄变红,向上递归
3 远子黑近子红 旋转使远子变红,转情况 4
4 远子红 调整颜色 + 旋转,完成修复

四、红黑树 vs AVL 树

特性 红黑树 AVL 树
平衡条件 最长路径 ≤ 2 倍最短路径 左右子树高度差 ≤ 1
插入旋转次数 最多 2 次 最多 O(log n) 次
删除旋转次数 最多 3 次 最多 O(log n) 次
查找性能 略慢(不那么平衡) 更快(更严格平衡)
适用场景 增删频繁(HashMap、TreeMap) 查找密集(数据库内存索引)

HashMap 为什么选红黑树? HashMap 中链表转树后,增删操作频繁(put/remove),红黑树增删性能更优。

五、Java 中的红黑树应用

5.1 TreeMap

java 复制代码
TreeMap<String, Integer> map = new TreeMap<>();
map.put("banana", 2);  // 红黑树插入,O(log n)
map.put("apple", 1);
map.put("cherry", 3);

// 利用有序性
map.firstKey();          // "apple"
map.lastKey();           // "cherry"
map.subMap("apple", "cherry");  // {"apple"=1, "banana"=2}

5.2 HashMap 的TreeNode

java 复制代码
// HashMap 中链表转红黑树的阈值
static final int TREEIFY_THRESHOLD = 8;     // 链表 → 红黑树
static final int UNTREEIFY_THRESHOLD = 6;   // 红黑树 → 链表
static final int MIN_TREEIFY_CAPACITY = 64; // 数组最小长度(不够则扩容不树化)

5.3 TreeSet

java 复制代码
// TreeSet 底层就是 TreeMap
TreeSet<Integer> set = new TreeSet<>();
set.add(3); set.add(1); set.add(2);
set.first();  // 1
set.last();   // 3
set.subSet(1, 3);  // [1, 2]

六、面试高频题

Q1: 红黑树为什么比 AVL 树更适合 HashMap?

难度:⭐⭐⭐⭐

答案:HashMap 中增删频繁(put/remove 是常态),红黑树增删最多 2~3 次旋转,AVL 树可能需要 O(log n) 次旋转。虽然红黑树查找略慢,但 HashMap 场景下综合性能更优。

Q2: 红黑树插入新节点为什么是红色?

难度:⭐⭐⭐

答案:红色节点不影响黑高(性质 5),最多只违反性质 4(连续红节点),修复成本低。如果插入黑色节点,必然违反性质 5(路径黑高不等),修复更复杂。

Q3: HashMap 链表转红黑树的阈值为什么是 8?

难度:⭐⭐⭐⭐

答案:泊松分布计算,负载因子 0.75 时,链表长度达到 8 的概率仅为 0.00000006(亿分之六)。正常情况下不会触发树化,只有 hash 碰撞极端严重时才需要,是防御性设计。

Q4: 红黑树的查找、插入、删除时间复杂度?

难度:⭐⭐

答案:均为 O(log n)。红黑树保证最长路径不超过最短路径 2 倍,n 个节点的树高 h ≤ 2log₂(n+1),因此所有操作都是对数级别。

相关推荐
李少兄2 小时前
Fastjson2 处理 JSON 字段大小写不一致的优雅方案
java·json
计算机毕业设计指导2 小时前
基于SpringBoot+Vue3的荣成市健康管理平台设计与实现
java·spring boot·后端
渔民小镇2 小时前
5 分钟搭建桌游服务器:Room 模块 + 领域事件实战
java·运维·服务器·分布式·游戏
Ulyanov2 小时前
《玩转QT Designer Studio:从设计到实战》 QT Designer Studio入门实战:智能登录系统开发
开发语言·python·qt·雷达电子对抗
SeeD NICK2 小时前
Spring Boot 3.4 正式发布,结构化日志!
java·spring boot·后端
人道领域2 小时前
深度揭秘:JDK 21 虚拟线程原理与性能调优实战
java·开发语言·python·jdk
2501_948114242 小时前
大模型API调用成本优化的工程路径:星链4SAPI聚合网关的技术实践
大数据·开发语言·人工智能·架构·php
oLLI PILO2 小时前
Ubuntu介绍、与centos的区别、基于VMware安装Ubuntu Server 22.04、配置远程连接、安装jdk+Tomcat
java·ubuntu·centos
de_wizard2 小时前
Spring Boot 整合 Apollo 配置中心实战
java·spring boot·后端