红黑树(Red-Black Tree)是一种自平衡二叉搜索树,由Rudolf Bayer于1972年发明。它通过对节点着色(红色或黑色)并维护五条核心性质,确保任意路径的长度不超过其他路径的两倍,从而保证了查找、插入、删除操作的最坏时间复杂度均为O(log n)。红黑树是Java中TreeMap、TreeSet以及JDK 8后HashMap链表转树的底层实现,是工业级代码中最常用的平衡树结构。
五条核心性质
- 每个节点要么是红色,要么是黑色
- 根节点永远是黑色
- 所有叶子节点(NIL节点)都是黑色
- 红色节点的两个子节点必须是黑色(不存在连续红节点)
- 从任一节点到其每个叶子节点的所有简单路径上,黑色节点数量相同(黑高一致)
性质4和5共同保证了树的高度上限为 $2\log_2(n+1)$,从而确保了操作的对数级复杂度。
Java完整实现
/**
* 红黑树实现 - 自平衡二叉搜索树
* 支持动态插入、删除与有序遍历,保证最坏O(log n)操作效率
* @param <T> 元素类型,需实现Comparable接口
*/
public class RedBlackTree<T extends Comparable<T>> {
/** 定义颜色常量 */
private static final boolean RED = true;
private static final boolean BLACK = false;
/**
* 树节点内部类
* 包含数据、颜色、左右子节点和父节点引用
*/
private class Node {
T value;
boolean color;
Node left, right, parent;
Node(T value) {
this.value = value;
this.color = RED; // 新插入节点默认为红色
this.left = this.right = this.parent = null;
}
}
private Node root;
private int size;
public RedBlackTree() {
this.root = null;
this.size = 0;
}
/**
* 判断节点颜色,NIL节点视为黑色
*/
private boolean isRed(Node node) {
return node != null && node.color == RED;
}
/**
* 左旋操作:以node为支点逆时针旋转
* 适用于修复"右右"或"右左"型不平衡
* @param node 旋转支点
*/
private void rotateLeft(Node node) {
Node rightChild = node.right;
node.right = rightChild.left;
if (rightChild.left != null) {
rightChild.left.parent = node;
}
rightChild.parent = node.parent;
if (node.parent == null) {
root = rightChild;
} else if (node == node.parent.left) {
node.parent.left = rightChild;
} else {
node.parent.right = rightChild;
}
rightChild.left = node;
node.parent = rightChild;
}
/**
* 右旋操作:以node为支点顺时针旋转
* 适用于修复"左左"或"左右"型不平衡
* @param node 旋转支点
*/
private void rotateRight(Node node) {
Node leftChild = node.left;
node.left = leftChild.right;
if (leftChild.right != null) {
leftChild.right.parent = node;
}
leftChild.parent = node.parent;
if (node.parent == null) {
root = leftChild;
} else if (node == node.parent.right) {
node.parent.right = leftChild;
} else {
node.parent.left = leftChild;
}
leftChild.right = node;
node.parent = leftChild;
}
/**
* 插入新元素
* 先执行标准BST插入,再自底向上修复红黑性质
* @param value 待插入的值
*/
public void insert(T value) {
Node newNode = new Node(value);
Node parent = null;
Node current = root;
// 标准BST查找插入位置
while (current != null) {
parent = current;
int cmp = value.compareTo(current.value);
if (cmp < 0) {
current = current.left;
} else if (cmp > 0) {
current = current.right;
} else {
return; // 值已存在,不重复插入
}
}
newNode.parent = parent;
if (parent == null) {
root = newNode;
} else if (value.compareTo(parent.value) < 0) {
parent.left = newNode;
} else {
parent.right = newNode;
}
size++;
// 修复因插入红色节点可能破坏的红黑性质
fixInsert(newNode);
root.color = BLACK; // 确保根节点始终为黑色
}
/**
* 插入修复:处理连续红色节点的冲突
* 分三种情况:叔叔节点为红、叔叔为黑且呈三角形、叔叔为黑且呈直线
* @param node 新插入的红色节点
*/
private void fixInsert(Node node) {
while (isRed(node.parent)) {
if (node.parent == node.parent.parent.left) {
Node uncle = node.parent.parent.right;
// 情况1:叔叔节点为红色,直接变色即可
if (isRed(uncle)) {
node.parent.color = BLACK;
uncle.color = BLACK;
node.parent.parent.color = RED;
node = node.parent.parent;
} else {
// 情况2:叔叔为黑,当前节点是右孩子(三角形)
if (node == node.parent.right) {
node = node.parent;
rotateLeft(node);
}
// 情况3:叔叔为黑,当前节点是左孩子(直线)
node.parent.color = BLACK;
node.parent.parent.color = RED;
rotateRight(node.parent.parent);
}
} else {
// 对称情况:父节点是右孩子
Node uncle = node.parent.parent.left;
if (isRed(uncle)) {
node.parent.color = BLACK;
uncle.color = BLACK;
node.parent.parent.color = RED;
node = node.parent.parent;
} else {
if (node == node.parent.left) {
node = node.parent;
rotateRight(node);
}
node.parent.color = BLACK;
node.parent.parent.color = RED;
rotateLeft(node.parent.parent);
}
}
}
}
/**
* 查找元素
* @param value 目标值
* @return 存在返回true
*/
public boolean contains(T value) {
Node current = root;
while (current != null) {
int cmp = value.compareTo(current.value);
if (cmp == 0) return true;
current = (cmp < 0) ? current.left : current.right;
}
return false;
}
/**
* 中序遍历输出有序序列
*/
public void inorderTraversal() {
System.out.print("中序遍历结果: ");
inorderHelper(root);
System.out.println();
}
private void inorderHelper(Node node) {
if (node != null) {
inorderHelper(node.left);
System.out.print(node.value + "(" + (node.color == RED ? "R" : "B") + ") ");
inorderHelper(node.right);
}
}
/**
* 获取树的高度(用于验证平衡性)
*/
public int height() {
return heightHelper(root);
}
private int heightHelper(Node node) {
if (node == null) return 0;
return 1 + Math.max(heightHelper(node.left), heightHelper(node.right));
}
public int size() {
return size;
}
/**
* 主方法:演示红黑树的动态平衡特性
*/
public static void main(String[] args) {
RedBlackTree<Integer> tree = new RedBlackTree<>();
int[] data = {50, 30, 70, 20, 40, 60, 80, 10, 25, 35, 45};
System.out.println("依次插入: " + java.util.Arrays.toString(data));
for (int num : data) {
tree.insert(num);
System.out.println("插入 " + num + " 后树高: " + tree.height());
}
tree.inorderTraversal();
System.out.println("\n查找测试:");
System.out.println("contains(25): " + tree.contains(25));
System.out.println("contains(100): " + tree.contains(100));
System.out.println("\n总节点数: " + tree.size() + ", 树高: " + tree.height());
System.out.println("理想最小高度: " + (int)(Math.log(tree.size()) / Math.log(2)) +
", 红黑树保证高度 ≤ 2*理想高度");
}
}
复杂度分析
| 操作 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 |
|---|---|---|---|
| 查找 | O(log n) | O(log n) | O(1) |
| 插入 | O(log n) | O(log n) | O(1) |
| 删除 | O(log n) | O(log n) | O(1) |
红黑树的平衡性弱于AVL树(允许高度差两倍),但插入和删除所需的旋转次数更少(最多3次旋转),因此写操作更频繁的场景下性能更优。
与AVL树的对比
| 特性 | 红黑树 | AVL树 |
|---|---|---|
| 平衡条件 | 黑高一致,高度差≤2倍 | 左右子树高度差≤1 |
| 查找效率 | 略低 | 更优 |
| 插入旋转 | ≤2次 | ≤2次 |
| 删除旋转 | ≤3次 | O(log n)次 |
| 适用场景 | 插入删除频繁 | 查询频繁 |
工业级应用
- Java集合框架:TreeMap、TreeSet基于红黑树实现有序映射/集合
- HashMap优化:JDK 8中当链表长度超过8时转为红黑树,将查询复杂度从O(n)降至O(log n)
- Linux内核:CFS调度器使用红黑树管理进程vruntime,实现O(log n)的任务选择
- 数据库索引:部分数据库使用红黑树作为内存索引结构