红黑树 - Red-Black Tree 原理与 C# 实现

什么是红黑树

红黑树是一种自平衡二叉搜索树,通过给节点着色(红色或黑色)并遵循特定规则,保证树在插入、删除操作后仍然保持平衡,从而确保操作的时间复杂度始终为 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::multimapstd::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: TreeMapTreeSet
  • C++: std::mapstd::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 内核
  • 实现复杂:但有现成库可用

何时使用红黑树?

  • 需要有序数据结构
  • 插入删除操作频繁
  • 需要保证最坏情况性能

掌握红黑树原理,能帮你理解标准库实现,做出更好的数据结构选择!

相关推荐
sin_hielo6 分钟前
leetcode 3010
数据结构·算法·leetcode
仙俊红2 小时前
LeetCode487周赛T2,删除子数组后的最终元素
数据结构·算法
Tingjct11 小时前
【初阶数据结构-二叉树】
c语言·开发语言·数据结构·算法
C雨后彩虹11 小时前
计算疫情扩散时间
java·数据结构·算法·华为·面试
飞机和胖和黄12 小时前
考研之王道C语言第三周
c语言·数据结构·考研
达文汐12 小时前
【困难】力扣算法题解析LeetCode332:重新安排行程
java·数据结构·经验分享·算法·leetcode·力扣
一匹电信狗12 小时前
【LeetCode_21】合并两个有序链表
c语言·开发语言·数据结构·c++·算法·leetcode·stl
Gorgous—l12 小时前
数据结构算法学习:LeetCode热题100-多维动态规划篇(不同路径、最小路径和、最长回文子串、最长公共子序列、编辑距离)
数据结构·学习·算法
一条大祥脚14 小时前
26.1.26 扫描线+数论|因子反演+子序列计数|树套树优化最短路
数据结构·算法
李老师讲编程15 小时前
C++信息学奥赛练习题-杨辉三角
数据结构·c++·算法·青少年编程·信息学奥赛