Java数据结构,红黑树(Red-Black Tree)的详细讲解与实现
红黑树是一种自平衡二叉搜索树,它在每个节点上增加了一个额外的属性——颜色(红色或黑色),并通过一系列规则确保树的高度保持在一个合理的范围内。这些性质共同作用的结果是,任何一条从根节点到叶子节点的路径中,红色节点的数量都不会超过黑色节点的一半。因此,即使在最坏的情况下,红黑树的高度也大约等于(2\times\log_2(n)),其中(n)表示树中的节点总数。需要注意的是,这里省略了删除操作的具体实现
文章目录
-
-
- 红黑树(Red-Black Tree)的详细讲解与实现
-
- 一、红黑树的基本概念
-
- 红黑树的性质:
- 二、红黑树的操作原理
-
- 插入后的修复策略:
- 删除后的修复策略:
- 三、手动实现红黑树
-
红黑树(Red-Black Tree)的详细讲解与实现
一、红黑树的基本概念
红黑树是一种自平衡二叉搜索树,它在每个节点上增加了一个额外的属性——颜色(红色或黑色),并通过一系列规则确保树的高度保持在一个合理的范围内。相比于普通的二叉搜索树,红黑树能够在最坏情况下提供接近对数时间复杂度的操作性能,非常适合用于需要频繁插入和删除元素的应用场景。
红黑树的性质:
- 每个节点要么是红色,要么是黑色。
- 根节点始终为黑色。
- 所有的叶子节点(NIL节点)都是黑色。(注意:这里的叶子节点指的是指向空指针的位置)
- 如果一个节点是红色,则它的两个子节点都必须是黑色。(即不存在连续两个红色节点的情况)
- 从任一节点到其每个叶子的所有路径都包含相同数量的黑色节点。(这条规则保证了树的高度不会过分倾斜)
这些性质共同作用的结果是,任何一条从根节点到叶子节点的路径中,红色节点的数量都不会超过黑色节点的一半。因此,即使在最坏的情况下,红黑树的高度也大约等于 (2 \times \log_2(n)),其中 (n) 表示树中的节点总数。
二、红黑树的操作原理
为了维持上述性质,在进行插入或删除操作时,我们可能需要执行一些调整步骤,如旋转和重新着色。具体来说:
-
插入操作:
- 将新节点插入为红色节点(这样可以避免违反性质5,因为新添加的红色节点不会改变原有路径上的黑高)。
- 检查是否违反了其他性质(特别是性质4),并根据情况采取相应的修复措施。
-
删除操作:
- 如果被删除的节点是红色,则直接移除即可,不会影响树的整体结构。
- 如果被删除的节点是黑色,则可能会导致某些路径上的黑高减少,这时需要通过调整来恢复平衡。
插入后的修复策略:
当插入一个新的红色节点后,可能会出现连续两个红色节点的情况,这违反了性质4。此时我们需要考虑三种不同的情形来进行修正:
-
叔叔节点存在且为红色:
- 解决方案:将父节点和叔叔节点变为黑色,祖父节点变为红色,然后继续向上检查祖父节点是否满足条件。如果祖父节点不是根节点,则重复此过程直到问题解决或到达根节点为止。
-
叔叔节点不存在或为黑色,并且当前节点位于其父节点的右侧(左旋情况):
- 解决方案:先对父节点做一次左旋,使得当前节点成为新的父节点,再按照第三种情况进行处理。
-
叔叔节点不存在或为黑色,并且当前节点位于其父节点的左侧(右旋情况):
- 解决方案:将父节点变为黑色,祖父节点变为红色,最后对祖父节点做一次右旋。完成之后,整个树就恢复了平衡。
删除后的修复策略:
删除一个黑色节点可能导致某些路径上的黑高减少,破坏了性质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 方法则用于按中序遍历输出当前树的状态,方便调试和验证算法的正确性。需要注意的是,这里省略了删除操作的具体实现,因为它相对更加复杂,涉及更多的情形判断和调整步骤。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)