- design-seckill.md: 秒杀系统设计 - design-shorturl.md: 短链接系统设计 - design-lbs.md: LBS附近的人系统设计 - design-im.md: 即时通讯系统设计 - design-feed.md: 社交信息流系统设计 Each document includes: - Requirements analysis and data volume assessment - Technical challenges - System architecture design - Database design - Caching strategies - Scalability considerations - Practical project experience - Alibaba P7 level additional points Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
18 KiB
18 KiB
红黑树 (Red-Black Tree)
数据结构原理
什么是红黑树?
红黑树是一种自平衡的二叉搜索树,通过在每个节点上增加一个颜色属性(红色或黑色)来保证树的平衡性。它能够在 O(log n) 时间内完成查找、插入和删除操作,是平衡二叉搜索树的一种。
红黑树的特性
- 节点颜色:每个节点要么是红色,要么是黑色
- 根节点:根节点总是黑色
- 叶子节点:所有叶子节点(NIL 节点)都是黑色
- 红色节点:红色节点的子节点必须是黑色
- 路径长度:从任一节点到其每个叶子节点的所有路径都包含相同数量的黑色节点
红黑树的平衡保证
红黑树通过维护这些性质,确保:
- 树的高度最多为 2log(n+1)
- 最坏情况下时间复杂度为 O(log n)
- 插入和删除操作的最坏时间复杂度为 O(log n)
图解说明
红黑树结构示例:
●(13) // 黑色节点
/ \
○(8) ●(17)
/ \ / \
●(1) ●(11) ○(15) ●(25)
/ \ / \ / \
NIL NIL NIL NIL NIL NIL
节点表示
颜色表示:
● - 黑色节点
○ - 红色节点
[NIL] - 黑色叶子节点
平衡性质说明
- 性质1:每个节点是红色或黑色
- 性质2:根节点是黑色
- 性质3:所有叶子节点都是黑色(NIL 节点)
- 性质4:红色节点的子节点都是黑色
- 性质5:从任一节点到其每个叶子节点的所有路径包含相同数量的黑色节点
Java 代码实现
节点类定义
enum Color {
RED, BLACK
}
class RedBlackNode<T extends Comparable<T>> {
T key;
Color color;
RedBlackNode<T> left, right, parent;
public RedBlackNode(T key) {
this.key = key;
this.color = Color.RED; // 新节点默认为红色
this.left = this.right = this.parent = null;
}
}
红黑树实现
public class RedBlackTree<T extends Comparable<T>> {
private RedBlackNode<T> root;
private RedBlackNode<T> NIL; // 哨兵节点
public RedBlackTree() {
NIL = new RedBlackNode<>(null);
NIL.color = Color.BLACK;
root = NIL;
}
// 左旋操作
private void leftRotate(RedBlackNode<T> x) {
RedBlackNode<T> y = x.right;
x.right = y.left;
if (y.left != NIL) {
y.left.parent = x;
}
y.parent = x.parent;
if (x.parent == NIL) {
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(RedBlackNode<T> y) {
RedBlackNode<T> x = y.left;
y.left = x.right;
if (x.right != NIL) {
x.right.parent = y;
}
x.parent = y.parent;
if (y.parent == NIL) {
root = x;
} else if (y == y.parent.left) {
y.parent.left = x;
} else {
y.parent.right = x;
}
x.right = y;
y.parent = x;
}
// 插入操作
public void insert(T key) {
RedBlackNode<T> newNode = new RedBlackNode<>(key);
newNode.left = NIL;
newNode.right = NIL;
newNode.color = Color.RED;
// 标准二叉搜索树插入
RedBlackNode<T> y = NIL;
RedBlackNode<T> x = root;
while (x != NIL) {
y = x;
if (newNode.key.compareTo(x.key) < 0) {
x = x.left;
} else {
x = x.right;
}
}
newNode.parent = y;
if (y == NIL) {
root = newNode;
} else if (newNode.key.compareTo(y.key) < 0) {
y.left = newNode;
} else {
y.right = newNode;
}
// 修复红黑树性质
insertFixUp(newNode);
}
// 插入后的修复
private void insertFixUp(RedBlackNode<T> z) {
while (z.parent.color == Color.RED) {
if (z.parent == z.parent.parent.left) {
RedBlackNode<T> y = z.parent.parent.right;
if (y.color == Color.RED) {
// 情况1:叔节点是红色
z.parent.color = Color.BLACK;
y.color = Color.BLACK;
z.parent.parent.color = Color.RED;
z = z.parent.parent;
} else {
if (z == z.parent.right) {
// 情况2:叔节点是黑色,z 是右孩子
z = z.parent;
leftRotate(z);
}
// 情况3:叔节点是黑色,z 是左孩子
z.parent.color = Color.BLACK;
z.parent.parent.color = Color.RED;
rightRotate(z.parent.parent);
}
} else {
// 对称情况
RedBlackNode<T> y = z.parent.parent.left;
if (y.color == Color.RED) {
z.parent.color = Color.BLACK;
y.color = Color.BLACK;
z.parent.parent.color = Color.RED;
z = z.parent.parent;
} else {
if (z == z.parent.left) {
z = z.parent;
rightRotate(z);
}
z.parent.color = Color.BLACK;
z.parent.parent.color = Color.RED;
leftRotate(z.parent.parent);
}
}
}
root.color = Color.BLACK;
}
// 查找操作
public RedBlackNode<T> search(T key) {
RedBlackNode<T> current = root;
while (current != NIL && key.compareTo(current.key) != 0) {
if (key.compareTo(current.key) < 0) {
current = current.left;
} else {
current = current.right;
}
}
return current;
}
// 中序遍历
public void inOrderTraversal() {
inOrderTraversal(root);
}
private void inOrderTraversal(RedBlackNode<T> node) {
if (node != NIL) {
inOrderTraversal(node.left);
System.out.print(node.key + (node.color == Color.RED ? "R" : "B") + " ");
inOrderTraversal(node.right);
}
}
// 删除操作(简化版)
public void delete(T key) {
RedBlackNode<T> z = search(key);
if (z == NIL) {
return;
}
// 实际的删除实现需要复杂的修复逻辑
deleteFixUp(z);
}
// 删除后的修复(简化版)
private void deleteFixUp(RedBlackNode<T> x) {
// 实际实现需要处理多种情况
// 这里简化处理,实际面试中需要详细实现
}
}
完整的删除操作实现
// 删除节点
public void delete(T key) {
RedBlackNode<T> z = search(key);
if (z == NIL) {
return;
}
RedBlackNode<T> y = z;
RedBlackNode<T> x;
Color yOriginalColor = y.color;
if (z.left == NIL) {
x = z.right;
transplant(z, z.right);
} else if (z.right == NIL) {
x = z.left;
transplant(z, z.left);
} else {
y = minimum(z.right);
yOriginalColor = y.color;
x = y.right;
if (y.parent == z) {
x.parent = y;
} else {
transplant(y, y.right);
y.right = z.right;
y.right.parent = y;
}
transplant(z, y);
y.left = z.left;
y.left.parent = y;
y.color = z.color;
}
if (yOriginalColor == Color.BLACK) {
deleteFixUp(x);
}
}
// 替换节点
private void transplant(RedBlackNode<T> u, RedBlackNode<T> v) {
if (u.parent == NIL) {
root = v;
} else if (u == u.parent.left) {
u.parent.left = v;
} else {
u.parent.right = v;
}
v.parent = u.parent;
}
// 查找最小节点
private RedBlackNode<T> minimum(RedBlackNode<T> node) {
while (node.left != NIL) {
node = node.left;
}
return node;
}
// 删除修复
private void deleteFixUp(RedBlackNode<T> x) {
while (x != root && x.color == Color.BLACK) {
if (x == x.parent.left) {
RedBlackNode<T> w = x.parent.right;
if (w.color == Color.RED) {
// 情况1:兄弟节点是红色
w.color = Color.BLACK;
x.parent.color = Color.RED;
leftRotate(x.parent);
w = x.parent.right;
}
if (w.left.color == Color.BLACK && w.right.color == Color.BLACK) {
// 情况2:兄弟节点是黑色,且两个子节点都是黑色
w.color = Color.RED;
x = x.parent;
} else {
if (w.right.color == Color.BLACK) {
// 情况3:兄弟节点是黑色,左子是红色,右子是黑色
w.left.color = Color.BLACK;
w.color = Color.RED;
rightRotate(w);
w = x.parent.right;
}
// 情况4:兄弟节点是黑色,右子是红色
w.color = x.parent.color;
x.parent.color = Color.BLACK;
w.right.color = Color.BLACK;
leftRotate(x.parent);
x = root;
}
} else {
// 对称情况
RedBlackNode<T> w = x.parent.left;
if (w.color == Color.RED) {
w.color = Color.BLACK;
x.parent.color = Color.RED;
rightRotate(x.parent);
w = x.parent.left;
}
if (w.right.color == Color.BLACK && w.left.color == Color.BLACK) {
w.color = Color.RED;
x = x.parent;
} else {
if (w.left.color == Color.BLACK) {
w.right.color = Color.BLACK;
w.color = Color.RED;
leftRotate(w);
w = x.parent.left;
}
w.color = x.parent.color;
x.parent.color = Color.BLACK;
w.left.color = Color.BLACK;
rightRotate(x.parent);
x = root;
}
}
}
x.color = Color.BLACK;
}
时间复杂度分析
操作时间复杂度
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 查找 | O(log n) | 平衡二叉搜索树查找 |
| 插入 | O(log n) | 查找插入位置 + 修复平衡 |
| 删除 | O(log n) | 查找删除位置 + 修复平衡 |
| 中序遍历 | O(n) | 遍历所有节点 |
| 最值查找 | O(log n) | 到达叶子节点 |
空间复杂度
- O(n) - 存储 n 个节点
- 递归栈空间:O(log n)
性能对比
| 特性 | 红黑树 | AVL 树 | 二叉搜索树 |
|---|---|---|---|
| 平衡性 | 相对平衡 | 严格平衡 | 可能不平衡 |
| 查找效率 | O(log n) | O(log n) | O(log n) ~ O(n) |
| 插入效率 | O(log n) | O(log n) | O(log n) ~ O(n) |
| 删除效率 | O(log n) | O(log n) | O(log n) ~ O(n) |
| 旋转次数 | 较少 | 较多 | 不需要 |
| 适用场景 | 插入频繁 | 查询密集 | 数据有序 |
实际应用场景
1. Java 集合框架
java.util.TreeMap:使用红黑树实现java.util.TreeSet:基于 TreeMap 实现
// TreeMap 使用示例
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("apple", 1);
treeMap.put("banana", 2);
treeMap.put("orange", 3);
// 自动排序
for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
2. Linux 内核
- ** Completely Fair Scheduler (CFS)**:使用红黑树管理进程调度
- 内存管理:管理虚拟内存区域
- 文件系统:ext3、ext4 使用红黑树
// Linux 内核中的红黑树使用示例
struct rb_root root = RB_ROOT;
struct my_data *data = rb_entry(node, struct my_data, rb_node);
3. 数据库索引
- B 树索引:底层使用类似红黑树的结构
- 范围查询:中序遍历支持范围查询
- 排序操作:自动维护有序性
// 数据库索引示例
public class DatabaseIndex {
private RedBlackTree<String, List<Record>> index;
public void insert(String key, Record record) {
index.put(key, Collections.singletonList(record));
}
public List<Record> rangeQuery(String start, String end) {
// 利用红黑树的中序遍历特性
return index.rangeSearch(start, end);
}
}
4. 网络路由
- 路由表管理:IP 地址范围查找
- 防火墙规则:规则匹配和优先级排序
// 路由表示例
public class RoutingTable {
private RedBlackTree<String, Route> routes;
public Route findRoute(String ip) {
// 查找匹配的路由规则
return routes.get(ip);
}
}
与其他平衡树的对比
| 特性 | 红黑树 | AVL 树 | B 树 | B+ 树 |
|---|---|---|---|---|
| 平衡因子 | 相对宽松 | 严格平衡 | 多路 | 多路 |
| 查找效率 | O(log n) | O(log n) | O(log n) | O(log n) |
| 插入效率 | O(log n) | O(log n) | O(log n) | O(log n) |
| 旋转次数 | 较少 | 频繁 | 分裂/合并 | 分裂/合并 |
| 内存使用 | 较少 | 较少 | 较大 | 较大 |
| 适用场景 | 插入频繁 | 查询密集 | 磁盘存储 | 磁盘存储 |
| 实现复杂度 | 中等 | 中等 | 复杂 | 复杂 |
选择红黑树的理由
- 性能平衡:插入、删除、查找都是 O(log n)
- 实现相对简单:比 AVL 树简单,比 B 树简单
- 插入性能好:插入时旋转次数少于 AVL 树
- 内存效率高:不需要像 B 树那样维护多路指针
- 广泛应用:Java 标准库采用,证明其可靠性
不同场景的最佳选择
- 内存数据结构:红黑树、AVL 树
- 磁盘数据存储:B 树、B+ 树
- 频繁插入:红黑树
- 频繁查询:AVL 树
- 范围查询:B+ 树(磁盘)、红黑树(内存)
常见面试问题
Q1: 红黑树和 AVL 树有什么区别?什么时候选择红黑树?
答: 主要区别:
- 平衡程度:AVL 树严格平衡,红黑树相对宽松
- 旋转频率:AVL 树旋转更频繁,红黑树较少
- 查找性能:AVL 树查找更快(树更矮)
- 插入性能:红黑树插入更快(旋转少)
选择红黑树的情况:
- 插入操作比查询操作频繁
- 需要保证最坏情况下的性能
- 不需要特别严格的平衡
- 需要实现简单、高效的平衡树
Q2: 红黑树的插入操作如何保证平衡?
答: 插入操作后通过以下方式修复平衡:
- 确定问题节点:从插入节点开始向上遍历
- 处理三种情况:
- 情况1:叔节点是红色,通过重新着色解决
- 情况2:叔节点是黑色,且节点是右孩子,通过左旋调整
- 情况3:叔节点是黑色,且节点是左孩子,通过右旋和着色解决
Q3: 为什么 Java 的 TreeMap 使用红黑树而不是 AVL 树?
答:
- 性能考虑:红黑树插入和删除性能更好
- 平衡性要求:TreeMap 需要保证整体性能,而不是最优查找
- 实现复杂度:红黑树实现相对简单
- 历史原因:早期 Java 版本选择,后续保持兼容性
Q4: 红黑树的时间复杂度是如何保证的?
答: 通过以下性质保证:
- 路径长度限制:任何路径的黑色节点数量相同
- 最长路径限制:最长路径不超过最短路径的 2 倍
- 树高控制:树高 h ≤ 2log₂(n+1)
- 操作效率:每次操作最多 O(log n) 次旋转
Q5: 如何验证红黑树的正确性?
答: 验证五条性质:
- 每个节点是红色或黑色
- 根节点是黑色
- 所有叶子节点是黑色
- 红色节点的子节点是黑色
- 所有路径的黑色节点数量相同
// 验证红黑树正确性的方法
public boolean isRedBlackTree(RedBlackNode<T> node) {
if (node == NIL) return true;
// 验证颜色
if (node.color != Color.RED && node.color != Color.BLACK) {
return false;
}
// 验证红色节点的子节点
if (node.color == Color.RED) {
if (node.left.color == Color.RED || node.right.color == Color.RED) {
return false;
}
}
// 验证路径长度
int leftBlackHeight = getBlackHeight(node.left);
int rightBlackHeight = getBlackHeight(node.right);
if (leftBlackHeight != rightBlackHeight) {
return false;
}
// 递归验证子树
return isRedBlackTree(node.left) && isRedBlackTree(node.right);
}
// 计算黑色高度
private int getBlackHeight(RedBlackNode<T> node) {
int height = 0;
while (node != NIL) {
if (node.color == Color.BLACK) {
height++;
}
node = node.left;
}
return height;
}
Q6: 红黑树如何处理大规模数据?
答: 对于大规模数据,红黑树的处理策略:
- 内存考虑:大规模数据可能导致内存问题
- 性能优化:
- 批量插入时可以暂时忽略平衡
- 使用延迟修复策略
- 替代方案:
- 对于磁盘数据,使用 B 树或 B+ 树
- 对于分布式数据,使用分布式数据结构
Q7: 红黑树的删除操作有哪些情况?
答: 删除操作主要处理以下情况:
- 删除红色节点:直接删除,不影响平衡
- 删除黑色节点:需要修复,分为多种情况
- 替代节点是红色:着色为黑色
- 替代节点是黑色:
- 兄弟节点是红色
- 兄弟节点是黑色,且子节点都是黑色
- 兄弟节点是黑色,且有一个红色子节点