TreeMap 与红黑树

面试官问:"TreeMap 底层是什么数据结构?"

候选人小习答:"红黑树。"

面试官追问:"为什么用红黑树而不是平衡二叉树?"

小习说不上来。

【面试官心理】 这道题考查的是候选人对树数据结构的理解。能说出红黑树的平衡特性与 AVL 树对比的候选人,说明对数据结构有基础研究。

一、红黑树特性 🔴

// 红黑树的 5 个约束条件:
// 1. 节点是红色或黑色
// 2. 根节点是黑色
// 3. 叶节点(NIL)是黑色
// 4. 红色节点的子节点都是黑色(无红色连续)
// 5. 从任一节点到其每个叶子的路径上,黑高相同

// 这些约束保证了:最长路径不超过最短路径的 2 倍
// → 近似平衡,查找/插入/删除都是 O(log n)

1.1 为什么不用 AVL 树

维度AVL 树红黑树
平衡度严格平衡(高度差 <= 1)近似平衡
查找性能更好(更平衡)稍差(但仍是 O(log n))
插入/删除开销大(更多旋转)
适用场景查找多(数据库索引)插入/删除多

二、TreeMap 基本操作 🔴

2.1 基本用法

// 自然顺序(key 必须实现 Comparable)
TreeMap<String, Integer> map = new TreeMap<>();
map.put("banana", 2);
map.put("apple", 1);
map.put("cherry", 3);

map.firstKey();   // "apple"(最小)
map.lastKey();    // "cherry"(最大)

map.lowerKey("cherry"); // "banana"(小于 cherry 的最大)
map.higherKey("banana"); // "cherry"(大于 banana 的最小)
map.floorKey("date");    // <= date 的最大
map.ceilingKey("date");  // >= date 的最小

2.2 自定义排序

// 方式一:Comparator
TreeMap<String, Integer> map = new TreeMap<>(Comparator.reverseOrder());
map.put("a", 1);
map.put("b", 2);
// 遍历顺序:b, a(降序)

// 方式二:自定义对象作为 key
class Person {
    String name;
    int age;
}

TreeMap<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2) {
        return p1.age - p2.age; // 按年龄排序
    }
});

三、常用场景 🟡

3.1 区间查询

TreeMap<Integer, String> map = new TreeMap<>();
map.put(10, "a");
map.put(20, "b");
map.put(30, "c");
map.put(40, "d");

// 获取 [15, 35] 范围内的所有键值对
Map<Integer, String> subMap = map.subMap(15, 35);
// 返回 {20="b", 30="c"}

// 获取小于 25 的所有键值对
Map<Integer, String> headMap = map.headMap(25);
// 返回 {10="a", 20="b"}

// 获取大于等于 25 的所有键值对
Map<Integer, String> tailMap = map.tailMap(25);
// 返回 {30="c", 40="d"}

3.2 LRU 缓存(基于 TreeMap)

// 基于时间过期的缓存
class ExpiringCache<K, V> {
    private final TreeMap<Long, K> timeMap = new TreeMap<>();
    private final Map<K, V> cache = new HashMap<>();
    private final long ttlMillis;

    public void put(K key, V value) {
        long expireTime = System.currentTimeMillis() + ttlMillis;
        timeMap.put(expireTime, key);
        cache.put(key, value);
    }

    public V get(K key) {
        // 清理过期数据
        Long expireTime = timeMap.ceilingKey(0L);
        while (expireTime != null && expireTime < System.currentTimeMillis()) {
            K expiredKey = timeMap.remove(expireTime);
            cache.remove(expiredKey);
            expireTime = timeMap.higherKey(expireTime);
        }
        return cache.get(key);
    }
}

四、与 HashMap 对比 🟡

维度HashMapTreeMap
底层结构哈希桶 + 链表/红黑树红黑树
时间复杂度O(1) 均摊O(log n)
排序自动排序
范围查询不支持支持
null 支持key/value 都可以为 nullkey 不能为 null(可比较性)
适用场景普通 kv 存储,高性能需要排序、区间查询

五、追问升级

面试官:"ConcurrentHashMap 不用红黑树做整体结构吗?"

// 不是整体用红黑树
// 而是单个哈希桶超过阈值时,桶内链表转红黑树
// 原因:单个桶内的数据需要快速查找
// 整个 ConcurrentHashMap 还是哈希表结构

// TreeMap:整体是红黑树,所有 key 按序组织
// HashMap/CHM:哈希定位 + 桶内红黑树/链表