什么是红黑树
红黑树是一种自平衡二叉搜索树,通过给节点着色(红色或黑色)并遵循特定规则,保证树在插入、删除操作后仍然保持平衡,从而确保操作的时间复杂度始终为 O(log n)。
核心特点:
- 每个节点要么是红色,要么是黑色
- 根节点是黑色
- 所有叶子节点(NIL)是黑色
- 红色节点的两个子节点必须是黑色(不能有连续的红色节点)
- 从任一节点到其叶子节点的所有路径包含相同数量的黑色节点
算法原理
红黑树的五条规则
13(B) 红黑树示例
/ \ B = 黑色节点
8(R) 17(B) R = 红色节点
/ \ \
1(B) 11(B) 25(R)
/ \
22(B) 27(B)
为什么需要这些规则?
- 规则 1-3:基础定义
- 规则 4:防止路径过长(红色节点不能连续)
- 规则 5:保证平衡性(所有路径黑色节点数相同)
旋转操作
红黑树通过旋转 和重新着色维持平衡:
左旋:
x y
/ \ / \
a y → x c
/ \ / \
b c a b
右旋:
y x
/ \ / \
x c → a y
/ \ / \
a b b c
应用场景
1. C++ STL 容器
std::map:有序键值对存储std::set:有序集合std::multimap、std::multiset
2. Java 集合框架
TreeMap:有序映射TreeSet:有序集合ConcurrentSkipListMap(部分实现)
3. Linux 内核
- 进程调度器(CFS):完全公平调度器使用红黑树管理进程
- 虚拟内存管理:管理内存区域
4. 数据库索引
- MySQL InnoDB:部分索引结构
- 内存数据库:有序索引
5. 实际应用
- 🔍 搜索引擎:倒排索引
- 📊 统计系统:有序排名
- ⏰ 定时器:按时间排序的任务队列
- 🎮 游戏开发:排行榜系统
C# 实现
节点定义
csharp
public enum NodeColor
{
Red,
Black
}
public class RedBlackNode<T> where T : IComparable<T>
{
public T Value { get; set; }
public NodeColor Color { get; set; }
public RedBlackNode<T> Left { get; set; }
public RedBlackNode<T> Right { get; set; }
public RedBlackNode<T> Parent { get; set; }
public RedBlackNode(T value)
{
Value = value;
Color = NodeColor.Red; // 新节点默认红色
}
public bool IsRed() => Color == NodeColor.Red;
public bool IsBlack() => Color == NodeColor.Black;
}
红黑树主类
csharp
public class RedBlackTree<T> where T : IComparable<T>
{
private RedBlackNode<T> root;
private readonly RedBlackNode<T> NIL; // 哨兵节点
public RedBlackTree()
{
NIL = new RedBlackNode<T>(default(T)) { Color = NodeColor.Black };
root = NIL;
}
/// <summary>
/// 插入节点
/// </summary>
public void Insert(T value)
{
var newNode = new RedBlackNode<T>(value)
{
Left = NIL,
Right = NIL,
Parent = NIL
};
RedBlackNode<T> parent = NIL;
RedBlackNode<T> current = root;
// 找到插入位置
while (current != NIL)
{
parent = current;
if (newNode.Value.CompareTo(current.Value) < 0)
current = current.Left;
else
current = current.Right;
}
newNode.Parent = parent;
if (parent == NIL)
root = newNode; // 空树
else if (newNode.Value.CompareTo(parent.Value) < 0)
parent.Left = newNode;
else
parent.Right = newNode;
// 修复红黑树性质
FixInsert(newNode);
}
/// <summary>
/// 插入后修复
/// </summary>
private void FixInsert(RedBlackNode<T> node)
{
while (node.Parent.IsRed())
{
if (node.Parent == node.Parent.Parent.Left)
{
var uncle = node.Parent.Parent.Right;
if (uncle.IsRed())
{
// Case 1: 叔叔是红色 - 重新着色
node.Parent.Color = NodeColor.Black;
uncle.Color = NodeColor.Black;
node.Parent.Parent.Color = NodeColor.Red;
node = node.Parent.Parent;
}
else
{
if (node == node.Parent.Right)
{
// Case 2: 叔叔是黑色,节点是右子 - 左旋
node = node.Parent;
LeftRotate(node);
}
// Case 3: 叔叔是黑色,节点是左子 - 右旋和重新着色
node.Parent.Color = NodeColor.Black;
node.Parent.Parent.Color = NodeColor.Red;
RightRotate(node.Parent.Parent);
}
}
else
{
// 对称情况(父节点是祖父的右子)
var uncle = node.Parent.Parent.Left;
if (uncle.IsRed())
{
node.Parent.Color = NodeColor.Black;
uncle.Color = NodeColor.Black;
node.Parent.Parent.Color = NodeColor.Red;
node = node.Parent.Parent;
}
else
{
if (node == node.Parent.Left)
{
node = node.Parent;
RightRotate(node);
}
node.Parent.Color = NodeColor.Black;
node.Parent.Parent.Color = NodeColor.Red;
LeftRotate(node.Parent.Parent);
}
}
}
root.Color = NodeColor.Black; // 根节点始终为黑色
}
/// <summary>
/// 左旋
/// </summary>
private void LeftRotate(RedBlackNode<T> x)
{
var 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;
}
/// <summary>
/// 右旋
/// </summary>
private void RightRotate(RedBlackNode<T> y)
{
var 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.Right)
y.Parent.Right = x;
else
y.Parent.Left = x;
x.Right = y;
y.Parent = x;
}
/// <summary>
/// 搜索节点
/// </summary>
public bool Search(T value)
{
return SearchNode(root, value) != NIL;
}
private RedBlackNode<T> SearchNode(RedBlackNode<T> node, T value)
{
if (node == NIL || value.CompareTo(node.Value) == 0)
return node;
if (value.CompareTo(node.Value) < 0)
return SearchNode(node.Left, value);
else
return SearchNode(node.Right, value);
}
/// <summary>
/// 中序遍历(有序输出)
/// </summary>
public List<T> InOrderTraversal()
{
var result = new List<T>();
InOrderHelper(root, result);
return result;
}
private void InOrderHelper(RedBlackNode<T> node, List<T> result)
{
if (node == NIL) return;
InOrderHelper(node.Left, result);
result.Add(node.Value);
InOrderHelper(node.Right, result);
}
}
使用示例
csharp
class Program
{
static void Main()
{
var tree = new RedBlackTree<int>();
// 插入数据
int[] values = { 13, 8, 17, 1, 11, 15, 25, 6, 22, 27 };
Console.WriteLine("插入元素:");
foreach (var value in values)
{
Console.Write($"{value} ");
tree.Insert(value);
}
Console.WriteLine("\n");
// 有序遍历
Console.WriteLine("中序遍历(有序输出):");
var sorted = tree.InOrderTraversal();
Console.WriteLine(string.Join(" ", sorted));
// 输出: 1 6 8 11 13 15 17 22 25 27
// 搜索元素
Console.WriteLine("\n搜索测试:");
Console.WriteLine($"查找 11: {tree.Search(11)}"); // True
Console.WriteLine($"查找 20: {tree.Search(20)}"); // False
// 实际应用示例:排行榜系统
var leaderboard = new RedBlackTree<int>();
leaderboard.Insert(9500);
leaderboard.Insert(8200);
leaderboard.Insert(9800);
leaderboard.Insert(7600);
Console.WriteLine("\n排行榜(分数从低到高):");
var rankings = leaderboard.InOrderTraversal();
for (int i = 0; i < rankings.Count; i++)
{
Console.WriteLine($"第 {i + 1} 名: {rankings[i]} 分");
}
}
}
性能对比
| 数据结构 | 插入 | 删除 | 查找 | 优势 |
|---|---|---|---|---|
| 红黑树 | O(log n) | O(log n) | O(log n) | 综合性能好,保证最坏情况 |
| AVL树 | O(log n) | O(log n) | O(log n) | 查找更快,但旋转次数多 |
| 普通BST | O(n) | O(n) | O(n) | 可能退化成链表 |
| 跳表 | O(log n) | O(log n) | O(log n) | 实现简单,支持并发 |
红黑树 vs AVL树:
- 红黑树:插入/删除更快(平均旋转少),适合频繁修改
- AVL树:查找更快(更严格平衡),适合读多写少
常见问题
Q: 为什么新插入的节点是红色?
A: 红色节点不影响黑色高度,修复起来更简单。如果是黑色会立即违反规则5。
Q: 红黑树和 AVL 树如何选择?
A:
- 插入删除频繁:红黑树(如 Linux 内核)
- 查询为主:AVL 树(数据库索引)
Q: 实际开发中何时自己实现红黑树?
A: 几乎不需要!使用现成的:
- C#:
SortedDictionary<K,V>、SortedSet<T> - Java:
TreeMap、TreeSet - C++:
std::map、std::set
Q: 红黑树的最大高度是多少?
A: 最大高度 ≤ 2log(n+1),保证了 O(log n) 的性能。
.NET 中的红黑树应用
csharp
// C# 内置的红黑树实现
using System.Collections.Generic;
// SortedDictionary 底层使用红黑树
var sortedDict = new SortedDictionary<int, string>
{
{ 3, "Three" },
{ 1, "One" },
{ 2, "Two" }
};
// 自动按键排序
foreach (var item in sortedDict)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
// 输出: 1: One, 2: Two, 3: Three
// SortedSet 也使用红黑树
var sortedSet = new SortedSet<int> { 5, 2, 8, 1 };
Console.WriteLine(string.Join(", ", sortedSet));
// 输出: 1, 2, 5, 8
总结
红黑树是工业级平衡树:
- ✅ 性能稳定:最坏情况仍为 O(log n)
- ✅ 插入删除高效:平均旋转次数少
- ✅ 广泛应用:STL、Java、Linux 内核
- ✅ 实现复杂:但有现成库可用
何时使用红黑树?
- 需要有序数据结构
- 插入删除操作频繁
- 需要保证最坏情况性能
掌握红黑树原理,能帮你理解标准库实现,做出更好的数据结构选择!