红黑树:Java 实现与核心原理详解

红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,它通过对每个节点进行着色(红色或黑色)并遵循特定规则,确保树的高度始终保持在对数级别,从而保证插入、删除和查找操作的时间复杂度为 O (log n)。本文将详细介绍红黑树的 Java 实现及其核心原理。

红黑树的基本性质

红黑树除了具备二叉搜索树的基本性质外,还需满足以下五个关键性质:

  1. 每个节点要么是红色,要么是黑色
  2. 根节点是黑色
  3. 所有叶子节点(NIL 节点,空节点)是黑色
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的
  5. 对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

这些性质确保了红黑树的关键特性:从根到叶子的最长路径不超过最短路径的两倍。这使得树大致上是平衡的。

红黑树的 Java 实现

下面是一个完整的红黑树 Java 实现,包含插入、删除、查找等核心操作:

public class RedBlackTree<T extends Comparable<T>> {
    private static final boolean RED = true;
    private static final boolean BLACK = false;

    private Node root;

    private class Node {
        T key;
        Node left, right;
        boolean color;
        int size;

        public Node(T key, boolean color, int size) {
            this.key = key;
            this.color = color;
            this.size = size;
        }
    }

    // 判断节点是否为红色
    private boolean isRed(Node x) {
        if (x == null) return false;
        return x.color == RED;
    }

    // 返回树的大小
    public int size() {
        return size(root);
    }

    private int size(Node x) {
        if (x == null) return 0;
        return x.size;
    }

    // 左旋操作
    private Node rotateLeft(Node h) {
        Node x = h.right;
        h.right = x.left;
        x.left = h;
        x.color = h.color;
        h.color = RED;
        x.size = h.size;
        h.size = size(h.left) + size(h.right) + 1;
        return x;
    }

    // 右旋操作
    private Node rotateRight(Node h) {
        Node x = h.left;
        h.left = x.right;
        x.right = h;
        x.color = h.color;
        h.color = RED;
        x.size = h.size;
        h.size = size(h.left) + size(h.right) + 1;
        return x;
    }

    // 颜色转换
    private void flipColors(Node h) {
        h.color = RED;
        h.left.color = BLACK;
        h.right.color = BLACK;
    }

    // 插入操作
    public void insert(T key) {
        root = insert(root, key);
        root.color = BLACK;
    }

    private Node insert(Node h, T key) {
        if (h == null) return new Node(key, RED, 1);

        int cmp = key.compareTo(h.key);
        if (cmp < 0) h.left = insert(h.left, key);
        else if (cmp > 0) h.right = insert(h.right, key);
        else h.key = key;

        // 修复红黑树
        if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h);
        if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
        if (isRed(h.left) && isRed(h.right)) flipColors(h);

        h.size = size(h.left) + size(h.right) + 1;
        return h;
    }

    // 查找操作
    public boolean contains(T key) {
        return get(key) != null;
    }

    public T get(T key) {
        return get(root, key);
    }

    private T get(Node x, T key) {
        while (x != null) {
            int cmp = key.compareTo(x.key);
            if (cmp < 0) x = x.left;
            else if (cmp > 0) x = x.right;
            else return x.key;
        }
        return null;
    }

    // 删除操作
    public void delete(T key) {
        if (!contains(key)) return;

        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;

        root = delete(root, key);
        if (!isEmpty()) root.color = BLACK;
    }

    private Node delete(Node h, T key) {
        if (key.compareTo(h.key) < 0) {
            if (!isRed(h.left) && !isRed(h.left.left))
                h = moveRedLeft(h);
            h.left = delete(h.left, key);
        } else {
            if (isRed(h.left))
                h = rotateRight(h);
            if (key.compareTo(h.key) == 0 && (h.right == null))
                return null;
            if (!isRed(h.right) && !isRed(h.right.left))
                h = moveRedRight(h);
            if (key.compareTo(h.key) == 0) {
                Node x = min(h.right);
                h.key = x.key;
                h.right = deleteMin(h.right);
            } else h.right = delete(h.right, key);
        }
        return balance(h);
    }

    // 辅助方法:移动红色到左侧
    private Node moveRedLeft(Node h) {
        flipColors(h);
        if (isRed(h.right.left)) {
            h.right = rotateRight(h.right);
            h = rotateLeft(h);
            flipColors(h);
        }
        return h;
    }

    // 辅助方法:修复红黑树
    private Node balance(Node h) {
        if (isRed(h.right)) h = rotateLeft(h);
        if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
        if (isRed(h.left) && isRed(h.right)) flipColors(h);

        h.size = size(h.left) + size(h.right) + 1;
        return h;
    }

    // 测试代码
    public static void main(String[] args) {
        RedBlackTree<Integer> tree = new RedBlackTree<>();
        tree.insert(5);
        tree.insert(3);
        tree.insert(7);
        tree.insert(2);
        tree.insert(4);
        tree.insert(6);
        tree.insert(8);

        System.out.print("中序遍历结果: ");
        tree.inorderTraversal();

        System.out.println("是否包含键 4: " + tree.contains(4));
        System.out.println("是否包含键 9: " + tree.contains(9));

        tree.delete(3);
        System.out.print("删除键 3 后的中序遍历结果: ");
        tree.inorderTraversal();
    }
}
核心操作解析
旋转操作

旋转操作是红黑树保持平衡的关键,主要有左旋和右旋两种:

// 左旋操作
private Node rotateLeft(Node h) {
    Node x = h.right;
    h.right = x.left;
    x.left = h;
    x.color = h.color;
    h.color = RED;
    x.size = h.size;
    h.size = size(h.left) + size(h.right) + 1;
    return x;
}

左旋操作将节点 h 的右子节点 x 提升为新的父节点,并将 h 变为 x 的左子节点。同时调整节点的颜色和子树大小。右旋操作是对称的。

插入操作

插入操作首先按照二叉搜索树的方式插入新节点,并将其标记为红色。然后通过旋转和颜色转换来修复可能破坏的红黑树性质:

// 插入操作
public void insert(T key) {
    root = insert(root, key);
    root.color = BLACK;
}

private Node insert(Node h, T key) {
    if (h == null) return new Node(key, RED, 1);

    int cmp = key.compareTo(h.key);
    if (cmp < 0) h.left = insert(h.left, key);
    else if (cmp > 0) h.right = insert(h.right, key);
    else h.key = key;

    // 修复红黑树
    if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h);
    if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
    if (isRed(h.left) && isRed(h.right)) flipColors(h);

    h.size = size(h.left) + size(h.right) + 1;
    return h;
}

插入修复主要处理三种情况:右子节点为红色而左子节点为黑色、左子节点和左子节点的左子节点均为红色、左右子节点均为红色。

删除操作

删除操作比插入更复杂,因为需要同时维护二叉搜索树和红黑树的性质:

// 删除操作
public void delete(T key) {
    if (!contains(key)) return;

    if (!isRed(root.left) && !isRed(root.right))
        root.color = RED;

    root = delete(root, key);
    if (!isEmpty()) root.color = BLACK;
}

删除操作首先检查键是否存在,然后通过递归找到要删除的节点,并进行相应的调整。如果删除的节点是红色的,不会破坏红黑树的性质;如果是黑色的,则需要通过一系列调整来恢复性质。

红黑树通过牺牲一定的平衡性(相对于 AVL 树)来换取更高效的插入和删除操作,因此在实际应用中更为常见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值