文章目录

      • 红黑树(Red-Black Tree)的详细讲解与实现
        • 一、红黑树的基本概念
          • 红黑树的性质:
        • 二、红黑树的操作原理
          • 插入后的修复策略:
          • 删除后的修复策略:
        • 三、手动实现红黑树

红黑树(Red-Black Tree)的详细讲解与实现

一、红黑树的基本概念

红黑树是一种自平衡二叉搜索树,它在每个节点上增加了一个额外的属性——颜色(红色或黑色),并通过一系列规则确保树的高度保持在一个合理的范围内。相比于普通的二叉搜索树,红黑树能够在最坏情况下提供接近对数时间复杂度的操作性能,非常适合用于需要频繁插入和删除元素的应用场景。

红黑树的性质:
  1. 每个节点要么是红色,要么是黑色
  2. 根节点始终为黑色
  3. 所有的叶子节点(NIL节点)都是黑色。(注意:这里的叶子节点指的是指向空指针的位置)
  4. 如果一个节点是红色,则它的两个子节点都必须是黑色。(即不存在连续两个红色节点的情况)
  5. 从任一节点到其每个叶子的所有路径都包含相同数量的黑色节点。(这条规则保证了树的高度不会过分倾斜)

这些性质共同作用的结果是,任何一条从根节点到叶子节点的路径中,红色节点的数量都不会超过黑色节点的一半。因此,即使在最坏的情况下,红黑树的高度也大约等于 (2 \times \log_2(n)),其中 (n) 表示树中的节点总数。

二、红黑树的操作原理

为了维持上述性质,在进行插入或删除操作时,我们可能需要执行一些调整步骤,如旋转和重新着色。具体来说:

  • 插入操作

    • 将新节点插入为红色节点(这样可以避免违反性质5,因为新添加的红色节点不会改变原有路径上的黑高)。
    • 检查是否违反了其他性质(特别是性质4),并根据情况采取相应的修复措施。
  • 删除操作

    • 如果被删除的节点是红色,则直接移除即可,不会影响树的整体结构。
    • 如果被删除的节点是黑色,则可能会导致某些路径上的黑高减少,这时需要通过调整来恢复平衡。
插入后的修复策略:

当插入一个新的红色节点后,可能会出现连续两个红色节点的情况,这违反了性质4。此时我们需要考虑三种不同的情形来进行修正:

  1. 叔叔节点存在且为红色

    • 解决方案:将父节点和叔叔节点变为黑色,祖父节点变为红色,然后继续向上检查祖父节点是否满足条件。如果祖父节点不是根节点,则重复此过程直到问题解决或到达根节点为止。
  2. 叔叔节点不存在或为黑色,并且当前节点位于其父节点的右侧(左旋情况)

    • 解决方案:先对父节点做一次左旋,使得当前节点成为新的父节点,再按照第三种情况进行处理。
  3. 叔叔节点不存在或为黑色,并且当前节点位于其父节点的左侧(右旋情况)

    • 解决方案:将父节点变为黑色,祖父节点变为红色,最后对祖父节点做一次右旋。完成之后,整个树就恢复了平衡。
删除后的修复策略:

删除一个黑色节点可能导致某些路径上的黑高减少,破坏了性质5。针对这种情况,我们可以通过以下几种方法之一来进行补偿:

  • 简单替换:如果要删除的节点有一个红色孩子,则可以直接用这个孩子代替它,同时将其颜色设为黑色。

  • 借用兄弟节点的颜色:如果要删除的节点没有红色孩子,但有一个黑色兄弟节点,则可以从兄弟那里“借”一点颜色给当前节点,使其暂时变成红色。然后根据具体情况决定是否需要进一步调整。

  • 重新分配黑高:如果既没有红色孩子也没有合适的兄弟节点可用,则需要在整个树中重新分配黑高的值,以确保所有路径上的黑高相等。这通常涉及到一系列复杂的旋转和重涂操作。

三、手动实现红黑树

接下来我们将基于上述理论知识,使用Java语言实现一个简单的红黑树。为了便于理解,代码中包含了详细的注释说明每一步的目的和逻辑。

// 定义红黑树节点类
class RedBlackTreeNode {
    public enum Color { RED, BLACK }

    public int key;
    public String value;
    public Color color;
    public RedBlackTreeNode left = null;
    public RedBlackTreeNode right = null;
    public RedBlackTreeNode parent = null;

    public RedBlackTreeNode(int key, String value) {
        this.key = key;
        this.value = value;
        this.color = Color.RED; // 新插入的节点默认为红色
    }
}

// 定义红黑树类
public class RedBlackTree {
    private RedBlackTreeNode root;

    // 左旋操作
    private void leftRotate(RedBlackTreeNode x) {
        RedBlackTreeNode y = x.right;
        x.right = y.left;
        if (y.left != null) {
            y.left.parent = x;
        }
        y.parent = x.parent;
        if (x.parent == null) {
            root = y;
        } else if (x == x.parent.left) {
            x.parent.left = y;
        } else {
            x.parent.right = y;
        }
        y.left = x;
        x.parent = y;
    }

    // 右旋操作
    private void rightRotate(RedBlackTreeNode y) {
        RedBlackTreeNode x = y.left;
        y.left = x.right;
        if (x.right != null) {
            x.right.parent = y;
        }
        x.parent = y.parent;
        if (y.parent == null) {
            root = x;
        } else if (y == y.parent.right) {
            y.parent.right = x;
        } else {
            y.parent.left = x;
        }
        x.right = y;
        y.parent = x;
    }

    // 插入修复函数
    private void insertFixup(RedBlackTreeNode z) {
        while (z != null && z != root && z.parent != null && z.parent.color == RedBlackTreeNode.Color.RED) {
            if (z.parent == z.parent.parent.left) {
                RedBlackTreeNode y = z.parent.parent.right;
                if (y != null && y.color == RedBlackTreeNode.Color.RED) {
                    // Case 1: Uncle is red
                    z.parent.color = RedBlackTreeNode.Color.BLACK;
                    y.color = RedBlackTreeNode.Color.BLACK;
                    z.parent.parent.color = RedBlackTreeNode.Color.RED;
                    z = z.parent.parent;
                } else {
                    if (z == z.parent.right) {
                        // Case 2: Current node is right child and uncle is black or null
                        z = z.parent;
                        leftRotate(z);
                    }
                    // Case 3: Current node is left child and uncle is black or null
                    z.parent.color = RedBlackTreeNode.Color.BLACK;
                    z.parent.parent.color = RedBlackTreeNode.Color.RED;
                    rightRotate(z.parent.parent);
                }
            } else {
                // Mirror image of the above code for right subtree
                RedBlackTreeNode y = z.parent.parent.left;
                if (y != null && y.color == RedBlackTreeNode.Color.RED) {
                    // Case 1: Uncle is red
                    z.parent.color = RedBlackTreeNode.Color.BLACK;
                    y.color = RedBlackTreeNode.Color.BLACK;
                    z.parent.parent.color = RedBlackTreeNode.Color.RED;
                    z = z.parent.parent;
                } else {
                    if (z == z.parent.left) {
                        // Case 2: Current node is left child and uncle is black or null
                        z = z.parent;
                        rightRotate(z);
                    }
                    // Case 3: Current node is right child and uncle is black or null
                    z.parent.color = RedBlackTreeNode.Color.BLACK;
                    z.parent.parent.color = RedBlackTreeNode.Color.RED;
                    leftRotate(z.parent.parent);
                }
            }
        }
        root.color = RedBlackTreeNode.Color.BLACK;
    }

    // 插入新节点
    public void insert(int key, String value) {
        RedBlackTreeNode node = new RedBlackTreeNode(key, value);
        RedBlackTreeNode y = null;
        RedBlackTreeNode x = root;

        // 寻找插入位置
        while (x != null) {
            y = x;
            if (node.key < x.key) {
                x = x.left;
            } else {
                x = x.right;
            }
        }

        // 插入新节点
        node.parent = y;
        if (y == null) {
            root = node;
        } else if (node.key < y.key) {
            y.left = node;
        } else {
            y.right = node;
        }

        // 调整树结构以保持红黑树性质
        insertFixup(node);
    }

    // 中序遍历打印树结构(用于验证正确性)
    private void inOrderTraversal(RedBlackTreeNode node) {
        if (node != null) {
            inOrderTraversal(node.left);
            System.out.println("Key: " + node.key + ", Color: " + node.color);
            inOrderTraversal(node.right);
        }
    }

    public void printTree() {
        inOrderTraversal(root);
    }
}

// 测试代码
public class Main {
    public static void main(String[] args) {
        RedBlackTree tree = new RedBlackTree();
        tree.insert(10, "Ten");
        tree.insert(20, "Twenty");
        tree.insert(30, "Thirty");
        tree.insert(15, "Fifteen");
        tree.insert(25, "Twenty-Five");

        // 打印树结构
        tree.printTree();
    }
}

这段代码实现了红黑树的基本功能,包括插入操作及其后的修复机制。通过调用 insert 方法可以向树中添加新的键值对,而 printTree 方法则用于按中序遍历输出当前树的状态,方便调试和验证算法的正确性。需要注意的是,这里省略了删除操作的具体实现,因为它相对更加复杂,涉及更多的情形判断和调整步骤。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐