TreeMap 源码分析

【本文首发于公众号:JavaArchJourney】

红黑树介绍

红黑树(Red-Black Tree)是一种自平衡的二叉查找树(Balanced Binary Search Tree) ,它通过对节点颜色进行约束和调整来保证树的高度相对平衡,从而使得插入、删除和查找操作的时间复杂度始终保持在 O(log n)。

  • 二叉查找树(Binary Search Tree):左子树所有节点值都小于根节点值,右子树所有节点值都大于根节点值。用于高效地进行数据查找、插入和删除操作。
  • 平衡二叉树(Balanced Binary Tree) :在二叉查找树的基础上进一步限制了树的高度,确保左右子树高度差不超过某个固定值,以维持 O(log n) 的操作时间复杂度,典型代表有红黑树和AVL树。

红黑树性质如下:

  • 性质 1:每个节点要么是红色,要么是黑色。
  • 性质 2 :根节点是黑色。
    • 如果插入时根节点变成红色,最后会强制染黑。
  • 性质 3:所有叶子节点(NIL节点)是黑色。
  • 性质 4 :如果一个节点是红色,则它的两个子节点必须是黑色。
    • 确保了路径中不会有太多红色节点。
  • 性质 5 :从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
    • 这是红黑树保持近似平衡的关键性质,保证最长路径不超过最短路径的两倍。

红黑树通过上述性质,使得最长路径 ≤ 2 × 最短路径,因此树的高度被控制在 O(log n),从而保证查找效率。

为了维持上述性质,在插入或删除节点后,红黑树需要执行一系列旋转(rotate)和变色(recoloring)操作。

TreeMap介绍

TreeMap 实现了 SortedMap 接口,基于红黑树来保证键的有序性。

TreeMap特点如下:

  • 有序映射 :TreeMap 维护了一个所有键值对的排序顺序,这个顺序可以是键的自然顺序(如果键实现了 Comparable 接口),或者是根据创建时提供的 Comparator 进行排序。
  • 红黑树实现 :TreeMap 内部使用红黑树来存储元素,这是一种自平衡二叉查找树,所以它的插入、删除和查找操作的时间复杂度都是 O(log n),比哈希表(如 HashMap)的平均情况 O(1) 要慢,但是提供了按键排序的功能。
  • 线程不安全 :TreeMap 不是同步的,如果要在多线程环境中使用,需要手动同步或者使用 Collections.synchronizedSortedMap 方法来包装 TreeMap。
  • 不允许键为 null :在 TreeMap 中,键不能为 null,否则会抛出 NullPointerException 异常。

TreeMap的类继承结构如下:

  • SortedMap 接口:
    • 支持自然排序或定制排序:可以通过自然顺序(如果键实现了 Comparable 接口)或者通过在创建 SortedMap 实例时提供的 Comparator 来定义键的排序规则。
    • 支持获取特定范围内的子集视图。
  • NavigableMap 接口:
    • 扩展了 SortedMap 接口,添加了更多用于检索与给定key最接近的键值对的方法,比如 floorEntry(K key) 返回小于等于给定键的最大键值对。

TreeMap使用示例

java 复制代码
import java.util.Map;
import java.util.TreeMap;

public class Test {

    public static void main(String[] args) {
        // 创建一个TreeMap,默认按键的自然顺序排序
        TreeMap<Integer, String> treeMap = new TreeMap<>();

        // 向TreeMap中添加键值对
        treeMap.put(3, "Apple");
        treeMap.put(1, "Banana");
        treeMap.put(2, "Orange");
        treeMap.put(5, "Grapes");
        treeMap.put(4, "Mango");

        // 输出TreeMap的所有键值对
        System.out.println("TreeMap 键值对:");
        for (Map.Entry<Integer, String> entry : treeMap.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }

        System.out.println();

        // 获取小于指定键的最大键值对
        Map.Entry<Integer, String> lowerEntry = treeMap.lowerEntry(3);
        System.out.println("小于键 3 的最大键值对: " + lowerEntry);

        // 获取大于等于指定键的最小键值对
        Map.Entry<Integer, String> ceilingEntry = treeMap.ceilingEntry(2);
        System.out.println("大于等于键 2 的最小键值对: " + ceilingEntry);

        System.out.println();

        // 获取子集视图(从键1到键4)
        Map<Integer, String> subMap = treeMap.subMap(1, true, 4, true);
        System.out.println("从键1到键4的子集视图:");
        for (Map.Entry<Integer, String> entry : subMap.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }

        System.out.println();

        // 打印第一个和最后一个键值对
        System.out.println("第一个键值对: " + treeMap.firstEntry());
        System.out.println("最后一个键值对: " + treeMap.lastEntry());
    }
}

运行结果:

java 复制代码
TreeMap 键值对:
1: Banana
2: Orange
3: Apple
4: Mango
5: Grapes

小于键 3 的最大键值对: 2=Orange
大于等于键 2 的最小键值对: 2=Orange

从键1到键4的子集视图:
1: Banana
2: Orange
3: Apple
4: Mango

第一个键值对: 1=Banana
最后一个键值对: 5=Grapes

TreeMap源码分析

以 JDK 1.8 为例,对 TreeMap 源码实现进行分析如下:

存储结构

java 复制代码
public class TreeMap<K,V>
        extends AbstractMap<K,V>
        implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    /**
     * 用于维护键值对的排序规则
     * 如果构造 TreeMap 时传入了比较器(Comparator),则使用该比较器进行排序;
     * 否则,使用键的自然顺序(即键必须实现 Comparable 接口);
     * 若既没有提供 Comparator,且键也没有实现 Comparable,则在插入元素时报错(抛出异常)
     */
    private final Comparator<? super K> comparator;

    /**
     * 指向红黑树的根节点
     */
    private transient Entry<K,V> root;

    /**
     * 当前 TreeMap 中实际包含的键值对数量(即 Entry 的个数)
     */
    private transient int size = 0;

    /**
     * 结构性修改的次数(如 put、remove 等改变树结构的操作)
     */
    private transient int modCount = 0;

    // Red-black mechanics

    /**
     * 全局静态常量,定义红黑树中节点的颜色状态
     */
    private static final boolean RED   = false;
    private static final boolean BLACK = true;

    /**
     * 红黑树节点结构
     */
    static final class Entry<K,V> implements Map.Entry<K,V> {
        /**
         * 键
         */
        K key;
        /**
         * 值
         */
        V value;
        /**
         * 左子节点
         */
        Entry<K,V> left;
        /**
         * 右子节点
         */
        Entry<K,V> right;
        /**
         * 父节点
         */
        Entry<K,V> parent;
        /**
         * 节点颜色,默认为黑色
         */
        boolean color = BLACK;
        
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        public K getKey() {
            return key;
        }
        
        public V getValue() {
            return value;
        }
        
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;

            return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
        }

        public int hashCode() {
            int keyHash = (key==null ? 0 : key.hashCode());
            int valueHash = (value==null ? 0 : value.hashCode());
            return keyHash ^ valueHash;
        }

        public String toString() {
            return key + "=" + value;
        }
    }
    
}

构造方法

java 复制代码
    /**
     * 创建一个空的 TreeMap,使用键的自然顺序(即要求键必须实现 Comparable 接口)
     */
    public TreeMap() {
        // comparator = null 表示不使用自定义比较器。插入键时将使用键自身的 compareTo() 方法来维护红黑树结构
        comparator = null;
    }

    /**
     * 创建一个空的 TreeMap,并指定一个比较器用于排序
     */
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    /**
     * 从一个普通的 Map 创建一个新的 TreeMap,使用键的自然顺序
     */
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        // 调用 AbstractMap.putAll() 方法,实际执行的是多个 put(k, v) 操作
        // 每次插入都会按照红黑树的规则调整结构。因为每次插入是 O(log n),总共有 n 个元素,所以时间复杂度为 O(n log n)
        putAll(m);
    }

    /**
     * 从一个已有的 SortedMap 创建 TreeMap,复用其排序方式和内容
     */
    public TreeMap(SortedMap<K, ? extends V> m) {
        // 获取原 SortedMap 的比较器
        comparator = m.comparator();
        try {
            // 输入的 SortedMap 已经是有序的,可以直接构建一棵平衡的红黑树,时间复杂度为 O(n),比逐个插入更高效
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

put方法

java 复制代码
    /**
     * 将键值对插入到 TreeMap 中
     * 如果键已存在,则更新对应的值并返回旧值;否则插入新键值对并返回 null
     */
    public V put(K key, V value) {
        // 获取当前红黑树的根节点
        // t 是用于遍历树的临时指针
        Entry<K, V> t = root;
        // 判断是否是空树(即第一次插入元素)
        if (t == null) {
            // 调用 compare() 方法验证键类型是否合法,同时检查是否为 null(如果是自然排序且 key 为 null 会抛出 NPE)
            compare(key, key); // type (and possibly null) check

            // 创建新的根节点
            root = new Entry<>(key, value, null);
            // 更新 size 和 modCount
            size = 1;
            modCount++;
            // 返回 null 表示没有旧值
            return null;
        }
        // 用于存储键之间的比较结果
        int cmp;
        // 记录当前节点的父节点,用于后续插入操作
        Entry<K, V> parent;
        // 获取当前使用的比较器,若不为 null,表示使用自定义比较器;否则使用自然排序(要求键必须实现 Comparable 接口)
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        // 分支一:使用自定义比较器查找插入位置
        if (cpr != null) {
            do {
                // 从根节点开始向下查找合适的位置
                // parent 始终指向待插入位置的父节点
                parent = t;
                // 比较键大小
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    // 小于当前节点,则走左子树
                    t = t.left;
                else if (cmp > 0)
                    // 大于当前节点,则走右子树
                    t = t.right;
                else
                    // 相等,则替换旧值并返回
                    return t.setValue(value);
            } while (t != null);
        }
        // 分支二:使用自然排序(Comparable)查找插入位置
        else {
            // 如果 key 为 null,则直接抛出 NullPointerException
            if (key == null)
                throw new NullPointerException();
            // 如果键不是 Comparable 类型,在这里强制转换时会抛异常
            @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
            // 后续逻辑与分支一相同,只是比较方式不同
            do {
                // 从根节点开始向下查找合适的位置
                // parent 始终指向待插入位置的父节点
                parent = t;
                // 比较键大小
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    // 小于当前节点,则走左子树
                    t = t.left;
                else if (cmp > 0)
                    // 大于当前节点,则走右子树
                    t = t.right;
                else
                    // 相等,则替换旧值并返回
                    return t.setValue(value);
            } while (t != null);
        }
        // 插入新节点
        // 创建一个新的 Entry 节点
        Entry<K, V> e = new Entry<>(key, value, parent);
        // 根据比较结果决定插入到左子节点还是右子节点
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        // 插入后修复红黑树性质,即调整红黑树结构
        fixAfterInsertion(e);
        size++;
        modCount++;
        // 返回 null 表示没有旧值
        return null;
    }

    /**
     * 红黑树插入后的平衡修复方法,确保树仍然满足红黑树性质。
     * 主要通过旋转和重新着色来维持红黑树的性质。
     * 红黑树插入修复规则简述:新插入的节点默认为红色;如果父节点是黑色,无需调整;如果父节点是红色,需要根据叔父节点的颜色进行旋转或变色;最终保证根节点始终为黑色
     */
    /**
     * From CLR
     */
    private void fixAfterInsertion(Entry<K, V> x) {
        // 新插入的节点默认设置为红色
        x.color = RED;

        // 当前节点不为空且不是根节点,且父节点为红色时需要调整
        while (x != null && x != root && x.parent.color == RED) {
            // 如果当前节点的父节点是其祖父节点的左子节点
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                // 获取当前节点的叔叔节点(父节点的兄弟节点)
                Entry<K, V> y = rightOf(parentOf(parentOf(x)));

                // 如果叔叔节点是红色
                if (colorOf(y) == RED) {
                    // 将父节点设为黑色
                    setColor(parentOf(x), BLACK);
                    // 将叔叔节点设为黑色
                    setColor(y, BLACK);
                    // 将祖父节点设为红色
                    setColor(parentOf(parentOf(x)), RED);
                    // 继续向上调整,将x设为祖父节点
                    x = parentOf(parentOf(x));
                } else {
                    // 如果当前节点是其父节点的右子节点
                    if (x == rightOf(parentOf(x))) {
                        // 将x设为父节点
                        x = parentOf(x);
                        // 左旋
                        rotateLeft(x);
                    }

                    // 将父节点设为黑色
                    setColor(parentOf(x), BLACK);
                    // 将祖父节点设为红色
                    setColor(parentOf(parentOf(x)), RED);
                    // 右旋
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                // 这部分与上面对称,处理当前节点的父节点是其祖父节点的右子节点的情况

                // 获取当前节点的叔叔节点(左子节点)
                Entry<K, V> y = leftOf(parentOf(parentOf(x)));

                // 如果叔叔节点是红色
                if (colorOf(y) == RED) {
                    // 将父节点设为黑色
                    setColor(parentOf(x), BLACK);
                    // 将叔叔节点设为黑色
                    setColor(y, BLACK);
                    // 将祖父节点设为红色
                    setColor(parentOf(parentOf(x)), RED);
                    // 继续向上调整,将x设为祖父节点
                    x = parentOf(parentOf(x));
                } else {
                    // 如果当前节点是其父节点的左子节点
                    if (x == leftOf(parentOf(x))) {
                        // 将x设为父节点
                        x = parentOf(x);
                        // 右旋
                        rotateRight(x);
                    }

                    // 将父节点设为黑色
                    setColor(parentOf(x), BLACK);
                    // 将祖父节点设为红色
                    setColor(parentOf(parentOf(x)), RED);
                    // 左旋
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }

        // 根节点始终为黑色
        root.color = BLACK;
    }

    /**
     * 左旋操作主要针对某个节点 p 进行,使其右子节点成为新的父节点,自己变为右子节点的左子节点
     * From CLR
     */
    private void rotateLeft(Entry<K, V> p) {
        if (p != null) {
            Entry<K, V> r = p.right;
            p.right = r.left;
            if (r.left != null)
                r.left.parent = p;
            r.parent = p.parent;
            if (p.parent == null)
                root = r;
            else if (p.parent.left == p)
                p.parent.left = r;
            else
                p.parent.right = r;
            r.left = p;
            p.parent = r;
        }
    }

    /**
     * 右旋操作是左旋的对称操作,针对某个节点 p 进行,使其左子节点成为新的父节点,自己变为左子节点的右子节点
     * From CLR
     */
    private void rotateRight(Entry<K, V> p) {
        if (p != null) {
            Entry<K, V> l = p.left;
            p.left = l.right;
            if (l.right != null) l.right.parent = p;
            l.parent = p.parent;
            if (p.parent == null)
                root = l;
            else if (p.parent.right == p)
                p.parent.right = l;
            else p.parent.left = l;
            l.right = p;
            p.parent = l;
        }
    }

总结:

put() 方法负责将键值对插入到红黑树中,它支持两种排序方式的插入:自然排序(要求键必须实现 Comparable 接口)和自定义比较器(显式设置了 Comparator)。插入过程中会对 key 的合法性做严格检查(null、Comparable 等)。在插入后调用 fixAfterInsertion() ,通过旋转和重新着色来维持红黑树的性质。

remove方法

java 复制代码
  /**
     * 根据键值从 TreeMap 中移除对应的映射
     * 如果存在该键,返回旧值;否则返回 null
     */
    public V remove(Object key) {
        // 调用 getEntry(key) 查找要删除的节点。这是一个基于红黑树结构的查找过程
        Entry<K, V> p = getEntry(key);
        // 如果未找到节点,返回 null
        if (p == null)
            return null;

        // 保存旧值用于返回
        V oldValue = p.value;
        // 执行删除操作
        deleteEntry(p);
        // 返回旧值
        return oldValue;
    }

    /**
     * 执行节点删除,并维护红黑树结构
     */
    private void deleteEntry(Entry<K, V> p) {
        // 更新结构性修改次数和集合大小
        modCount++;
        size--;

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        // 如果被删节点有两个子节点(即内部节点)
        if (p.left != null && p.right != null) {
            // 找到它的后继节点(中序后继)
            Entry<K, V> s = successor(p);
            // 将后继节点的内容复制给当前节点
            p.key = s.key;
            p.value = s.value;
            // 将待删除节点指向后继节点
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        // 获取替代节点(最多一个子节点)。如果是叶子节点,replacement == null
        Entry<K, V> replacement = (p.left != null ? p.left : p.right);

        // 分支一:有子节点(replacement 不为 null)
        if (replacement != null) {
            // 替换节点连接到父节点
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left = replacement;
            else
                p.parent.right = replacement;

            // 清除原节点的引用
            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;

            // 如果被删节点是黑色,需要调用 fixAfterDeletion() 维护红黑树性质
            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        }
        // 分支二:无子节点(叶子节点)
        else if (p.parent == null) { // return if we are the only node.
            // 如果是根节点,直接置空
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            // 如果是黑色节点,仍需进行删除修复
            if (p.color == BLACK)
                fixAfterDeletion(p);

            // 断开与父节点的连接
            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

    /**
     * 找到指定节点的后继节点
     * 如果右子树非空,则找右子树最左节点(最小后继);否则向上寻找第一个不是右孩子的祖先节点
     * 时间复杂度为 O(log n)
     */
    static <K, V> TreeMap.Entry<K, V> successor(Entry<K, V> t) {
        if (t == null)
            return null;
        else if (t.right != null) {
            Entry<K, V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
        } else {
            Entry<K, V> p = t.parent;
            Entry<K, V> ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

    /**
     * From CLR
     * 在删除一个节点后,通过旋转和重新着色来修复红黑树性质
     * 时间复杂度:O(log n)
     */
    private void fixAfterDeletion(Entry<K, V> x) {
        // 当前节点不是根节点且当前节点是黑色(可能是 NIL 节点)
        // 说明:如果当前节点是红色,直接染黑即可;否则才需要进入修复流程,直到根节点或当前节点变为红色
        while (x != root && colorOf(x) == BLACK) {
            // 当前节点是父节点的左孩子
            if (x == leftOf(parentOf(x))) {
                // 获取兄弟节点
                Entry<K, V> sib = rightOf(parentOf(x));

                // 兄弟节点是红色
                if (colorOf(sib) == RED) {
                    // 将兄弟节点染黑
                    setColor(sib, BLACK);
                    // 父节点染红
                    setColor(parentOf(x), RED);
                    // 对父节点左旋
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }

                // 兄弟节点及其两个子节点都是黑色
                if (colorOf(leftOf(sib)) == BLACK &&
                        colorOf(rightOf(sib)) == BLACK) {
                    // 将兄弟节点染红
                    setColor(sib, RED);
                    // 当前节点上升为父节点
                    x = parentOf(x);
                }
                // 兄弟节点至少有一个红色子节点
                else {
                    // 兄弟节点的右子节点是黑色(即内侄子为红)
                    if (colorOf(rightOf(sib)) == BLACK) {
                        // 内侄子染黑
                        setColor(leftOf(sib), BLACK);
                        // 兄弟节点染红
                        setColor(sib, RED);
                        // 对兄弟节点右旋
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    // 兄弟节点颜色设为父节点颜色
                    setColor(sib, colorOf(parentOf(x)));
                    // 父节点染黑
                    setColor(parentOf(x), BLACK);
                    // 外侄子染黑
                    setColor(rightOf(sib), BLACK);
                    // 对父节点左旋
                    rotateLeft(parentOf(x));
                    x = root;
                }
            }
            // 当前节点是父节点的右孩子(对称操作)
            // 这部分逻辑完全对称于左孩子的处理,只是方向相反:兄弟节点取左边;插入节点是左孩子时需要右旋;最终通过右旋恢复结构
            else { // symmetric
                Entry<K, V> sib = leftOf(parentOf(x));

                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }

                if (colorOf(rightOf(sib)) == BLACK &&
                        colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }

        // 无论最后 x 是不是根节点,都确保它是黑色
        setColor(x, BLACK);
    }

总结:

根据键查找到节点,然后执行节点删除操作:若有两个子节点,则使用后继节点代替;若有一个子节点,则替换链接;若是叶子节点,则直接断开。在节点删除后,调用 fixAfterDeletion 方法通过旋转和重新着色来修复红黑树性质。

查找方法

红黑树的查找,其实就是普通的二叉查找树的查找操作。

以 lowerEntry(K key) 方法为例:

java 复制代码
    /**
     * 返回小于指定键的最大键对应的键值对
     */
    public Map.Entry<K, V> lowerEntry(K key) {
        // 查找符合条件的节点,然后将内部实现类包装成对外暴露的标准 Entry
        return exportEntry(getLowerEntry(key));
    }

    /**
     * 查找小于给定键的最大键对应的节点。即:返回比 key 小的键中最大的那个节点
     * 如果不存在这样的节点(例如所有键都大于等于 key),则返回 null
     * 时间复杂度 O(log n)
     */
    final Entry<K, V> getLowerEntry(K key) {
        // 初始化从根节点开始查找
        Entry<K, V> p = root;
        // 循环向下查找直到找到合适的节点或到达叶子
        while (p != null) {
            // 比较当前节点与目标键
            // compare 方法会根据是否使用自定义 Comparator 决定如何比较
            int cmp = compare(key, p.key);
            // 当前键小于目标键(cmp > 0)
            if (cmp > 0) {
                // 当前节点的键比目标键小,可能是候选答案
                if (p.right != null)
                    // 如果右子树非空,需要向右子树查找是否有更大的符合条件的节点
                    p = p.right;
                else
                    // 如果右子树为空,说明当前节点就是目前找到的"最接近但小于 key"的节点,直接返回它
                    return p;
            }
            // 当前键大于等于目标键(cmp <= 0)
            else {
                // 当前节点的键大于等于目标键,则不可能是答案,
                if (p.left != null) {
                    // 如果左子树非空,需要往左子树继续查找
                    p = p.left;
                } else {
                    // 如果左子树为空,说明当前路径上没有更小的节点了
                    // 此时需要向上回溯,寻找最近的一个祖先节点,它是其父节点的右孩子(即它的父节点比它大,而它本身又小于目标键)
                    Entry<K, V> parent = p.parent;
                    Entry<K, V> ch = p;
                    // 向上回溯,跳过那些是父节点左孩子的节点(这些节点比它们的父节点小)
                    // 直到找到一个节点不是其父节点的左孩子(即它的父节点比它小),此时这个父节点就可能是小于目标键的最大节点
                    // 如果找不到这样的父节点,最终返回 null
                    while (parent != null && ch == parent.left) {
                        ch = parent;
                        parent = parent.parent;
                    }
                    return parent;
                }
            }
        }
        return null;
    }

    final int compare(Object k1, Object k2) {
        // 如果没有自定义比较器,则使用自然排序(Comparable)
        // 否则使用传入的 Comparator
        return comparator == null ? ((Comparable<? super K>) k1).compareTo((K) k2)
                : comparator.compare((K) k1, (K) k2);
    }

    /**
     * Return SimpleImmutableEntry for entry, or null if null
     */
    static <K, V> Map.Entry<K, V> exportEntry(TreeMap.Entry<K, V> e) {
        return (e == null) ? null :
                new AbstractMap.SimpleImmutableEntry<>(e);
    }

核心原理总结

TreeMap 是一种基于红黑树实现的有序映射(SortedMap),能够保持键的自然顺序(键必须实现 Comparable 接口)或者根据实例创建时提供的 Comparator 来排序。

  • 红黑树(Red-Black Tree)是一种自平衡的二叉查找树(Balanced Binary Search Tree) ,它通过对节点颜色进行约束和调整来保证树的高度相对平衡,从而使得插入、删除和查找操作的时间复杂度始终保持在 O(log n)。
  • 插入操作 :当插入新节点时,首先按照普通二叉查找树的方式进行插入,并将新节点标记为红色。然后调用 fixAfterInsertion 方法来修复可能破坏的红黑树性质,过程中涉及到颜色重着色和旋转操作(左旋、右旋)。
  • 删除操作 :同样遵循二叉查找树的删除逻辑。在节点删除后,调用 fixAfterDeletion 方法来恢复红黑树性质,处理过程与插入类似,但更为复杂,因为需要考虑兄弟节点的颜色以及子节点的情况来进行适当的调整。
  • 查找操作 :查找操作利用红黑树的二叉搜索特性,通过比较键值大小决定向左或向右子树递归查找,直到找到匹配项或到达叶节点为止。时间复杂度为 O(log n)。
  • 时间复杂度 :
    • 插入、删除和查找:O(log n)。
    • 遍历:O(n)。
相关推荐
一 乐1 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
卓怡学长1 小时前
w214基于jsp知道特产网
java·intellij-idea
步行cgn1 小时前
Spring 注解使用详解
java·spring
一条小小yu1 小时前
Spring IoC的理解
java·后端·spring
落魄实习生2 小时前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
旺仔学长 哈哈2 小时前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
自强的小白4 小时前
核心功能(Service接口)
java·mybatis
乌暮4 小时前
深入理解 Java 泛型:把「万能盒子」用对、用稳
java·开发语言·后端·学习
大猫和小黄5 小时前
深入理解 Java Optional:从设计哲学到工程实战
java