【高阶数据结构】红黑树 - 教程

1. 什么是红黑树

红黑树并不是一个完全平衡的二叉树(像AVL树那样),而是一种近似平衡的二叉搜索树。它通过一套简单的规则和调整操作,确保树的高度始终维持在 O(log n) 级别,从而保证了搜索、插入、删除等操作的高效性。

二叉树高度:[log n,n-1]
平衡二叉树 / AVL 树、 红黑树高度:O(log n)

它通过以下5条规则来维持这种平衡:

  1. 每个节点不是红色就是黑色。
  2. 根节点是黑色的。
  3. 如果一个节点是红色的,则它的两个子节点都是黑色的。(即:不能有连续的红色节点
  4. 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
  5. 每个叶子节点(NIL节点,空节点)都是黑色的。(此规则在实现中通常作为隐含条件)

规则3和规则4是重中之重,插入操作的所有调整都是为了维护这两条规则。
其实可以看出,这5条规则导致:红黑树最长路径最多是最短路径的两倍。

2. 红黑树的模拟实现

2.1 插入

插入操作的整体思路:

插入新节点时,我们遵循一个核心原则:先按二叉搜索树规则插入,再通过调整来恢复红黑树性质。

为什么新插入的节点默认为红色?

  • 如果插入黑色节点,会立即违反规则4,因为它所在路径的黑色节点数会增加1,修复起来非常困难,需要调整整棵树。
  • 如果插入红色节点,可能违反规则3(如果父节点也是红色),但不会违反规则4。违反规则3是一个局部问题,更容易通过局部调整来修复。

因此,插入过程可以简化为:解决因插入红色节点而导致的“双红”问题。

这一段其实和AVL树的插入一样。

bool Insert(const pair<K, V>& kv)
  {
  if (_root == nullptr)
  {
  _root = new Node(kv);
  _root->_col = BLACK;
  return true;
  }
  else
  {
  Node* parent = nullptr;
  Node* cur = _root;
  //找到节点所处的位置
  while (cur)
  {
  if (kv.first > cur->_kv.first)
  {
  parent = cur;
  cur = cur->_right;
  }
  else if (kv.first < cur->_kv.first)
    {
    parent = cur;
    cur = cur->_left;
    }
    else
    {
    return false;
    }
    }
    //生成节点
    cur = new Node(kv);
    cur->_col = RED;
    //连接节点
    cur->_parent = parent;
    if (cur->_kv.first > parent->_kv.first)
    {
    parent->_right = cur;
    }
    else
    {
    parent->_left = cur;
    }
    //通过调整来恢复红黑树性质
    //...
    }
    }

2.1.1 情况1:叔叔节点U是红色

这是最简单的情况。

处理策略:重新着色

操作步骤:

  1. 将父节点P和叔叔节点U都变为黑色。
  2. 将祖父节点G变为红色。
  3. 把祖父节点G当作新的当前节点C,继续向上检查

为什么这样做?

  • 步骤1解决了P和C之间的“双红”问题。
  • 步骤2保证了以G为根的子树黑色高度不变(因为黑色节点只是从P和U转移到了G)。
  • 步骤3是因为G现在变成了红色,如果G的父节点也是红色,就会产生新的“双红”问题,需要继续向上处理。如果调整后,最顶部的节点是黑色,则不用向上处理,插入结束(接下来的情况二就是这样)。

特殊情况:如果G是根节点,在最后需要将其重新变为黑色(规则2)。

注意:只有是出现双红的情况,祖父节点G一定是黑色,因为P是红色。

在这里插入图片描述

2.1.2 情况2:叔叔节点U是黑色或为空(NIL)

这种情况需要旋转来解决。旋转的目的是为了提升C节点,同时保持二叉搜索树的性质。

情况2又分为两个子情况,取决于当前节点C是父节点P的左孩子还是右孩子。

(1)情况2a:直线型 (LL / RR)
  • LL型:P是G的左孩子,C是P的左孩子。
  • RR型:P是G的右孩子,C是P的右孩子。

处理策略:一次旋转 + 重新着色

操作步骤(以LL型为例):

  1. 对祖父节点G进行一次右旋。
  2. 重新着色:将原来的父节点P变为黑色,原来的祖父节点G变为红色。
  3. 调整结束,无需继续向上(p为黑色)
    在这里插入图片描述

为什么这样做?

  • 右旋使得P成为了新的局部根节点。
  • 着色后,P(黑色)隔开了C(红色)和G(红色),彻底解决了“双红”问题。
  • 旋转和着色后,该子树的黑色高度保持不变。

另附RR型示意图:
在这里插入图片描述

(2)情况2b:折线型 (LR / RL)
  • LR型:P是G的左孩子,C是P的右孩子。
  • RL型:P是G的右孩子,C是P的左孩子。

处理策略:两次旋转 + 重新着色

操作步骤(以LR型为例):

  1. 对父节点P进行一次左旋。这一步将LR型转换成了LL型。
  2. 对祖父节点G进行一次右旋。
  3. 重新着色:将当前节点C变为黑色,原来的祖父节点G变为红色。
  4. 调整结束,无需继续向上(cur为黑色)
    在这里插入图片描述

为什么这样做?

  • 第一次旋转(左旋)将结构标准化。左旋后,其实已经就是情况2a了
  • 第二次旋转(右旋)将C提升到局部根节点的位置。
  • 着色后,C(黑色)成为了新的局部根,隔开了两边的红色节点,彻底解决了问题。

另附RL型示意图:
在这里插入图片描述

2.1.3 代码实现

左右单旋转的代码如下:
左右单旋转的详细讲解,可以看我的关于AVL树的博客:详细请点击<——

private:
void RotateL(Node* parent)
{
Node* ppnode = parent->_parent;
Node* cur = parent->_right;
Node* curleft = cur->_left;
parent->_right = curleft;
if (curleft)
{
curleft->_parent = parent;
}
cur->_left = parent;
parent->_parent = cur;
if (ppnode == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
cur->_parent = ppnode;
if (ppnode->_left == parent)
{
ppnode->_left = cur;
}
else
{
ppnode->_right = cur;
}
}
}
void RotateR(Node* parent)
{
Node* ppnode = parent->_parent;
Node* cur = parent->_left;
Node* curright = cur->_right;
parent->_left = curright;
if (curright)
{
curright->_parent = parent;
}
cur->_right = parent;
parent->_parent = cur;
if (ppnode == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = cur;
cur->_parent = ppnode;
}
else
{
ppnode->_right = cur;
cur->_parent = ppnode;
}
}
}

红黑树插入的代码实现:

bool Insert(const pair<K, V>& kv)
  {
  if (_root == nullptr)
  {
  _root = new Node(kv);
  _root->_col = BLACK;
  return true;
  }
  else
  {
  Node* parent = nullptr;
  Node* cur = _root;
  while (cur)
  {
  if (kv.first > cur->_kv.first)
  {
  parent = cur;
  cur = cur->_right;
  }
  else if (kv.first < cur->_kv.first)
    {
    parent = cur;
    cur = cur->_left;
    }
    else
    {
    return false;
    }
    }
    cur = new Node(kv);
    cur->_col = RED;
    cur->_parent = parent;
    if (cur->_kv.first > parent->_kv.first)
    {
    parent->_right = cur;
    }
    else
    {
    parent->_left = cur;
    }
    while (parent && parent->_col == RED)//满足条件就不断向上调整
    {
    Node* grandfather = parent->_parent;
    if (parent == grandfather->_left)//parent在grandfather的左边的情况
    {
    Node* uncle = grandfather->_right;//uncle叔叔节点对应在grandfather右边
    if (uncle && uncle->_col == RED)//处理uncle存在且颜色为红色的情况
    {
    parent->_col = uncle->_col = BLACK;
    grandfather->_col = RED;
    cur = grandfather;
    parent = cur->_parent;
    _root->_col = BLACK;//保证红黑树的根节点始终为黑色
    }
    else//处理uncle不存在,或者uncle存在且颜色为黑色的情况
    {
    if (cur == parent->_left)
    {
    RotateR(grandfather);
    parent->_col = BLACK;
    grandfather->_col = RED;
    }
    else //cur = parent->_right
    {
    RotateL(parent);
    RotateR(grandfather);
    cur->_col = BLACK;
    grandfather->_col = RED;
    }
    break;
    }
    }
    else //parent == grandfather->_right//parent在grandfather的右边的情况
    {
    Node* uncle = grandfather->_left;//uncle叔叔节点对应在grandfather左边
    if (uncle && uncle->_col == RED)//处理uncle存在且颜色为红色的情况
    {
    parent->_col = uncle->_col = BLACK;
    grandfather->_col = RED;
    cur = grandfather;
    parent = cur->_parent;
    _root->_col = BLACK;//保证红黑树的根节点始终为黑色
    }
    else//处理uncle不存在,或者uncle存在且颜色为黑色的情况
    {
    if (cur == parent->_right)
    {
    RotateL(grandfather);
    parent->_col = BLACK;
    grandfather->_col = RED;
    }
    else //cur = parent->_left
    {
    RotateR(parent);
    RotateL(grandfather);
    cur->_col = BLACK;
    grandfather->_col = RED;
    }
    break;
    }
    }
    }
    return true;
    }
    }

2.2 IsBalance

bool CheckColour(Node* root, int blacksum, int leftpathblack)
{
// 基准情况:到达叶子节点
if (root == nullptr) {
// 检查性质5:所有路径黑色节点数相同
if (blacksum != leftpathblack) {
cout << "每条路径上的黑色节点的数目不同" << endl;
return false;
}
return true;
}
Node* parent = root->_parent;
// 检查性质4:没有连续的红节点
if (parent && parent->_col == RED && root->_col == RED) {
cout << root->_kv.first << ':' << "出现两个连续的红节点" << endl;
  return false;
  }
  // 统计当前路径的黑色节点数
  if (root->_col == BLACK)
  blacksum++;
  // 递归检查左右子树
  return CheckColour(root->_left, blacksum, leftpathblack) &&
  CheckColour(root->_right, blacksum, leftpathblack);
  }
  bool IsBalance(Node* root)
  {
  if (root == nullptr) return true;
  // 检查性质2:根节点必须是黑色
  if (root->_col != BLACK) {
  cout << "根节点颜色错误不为黑色" << endl;
  return false;
  }
  // 计算最左路径的黑色节点数量作为基准
  int leftpathblack = 0;
  Node* cur = root;
  while (cur) {
  if (cur->_col == BLACK)
  leftpathblack++;
  cur = cur->_left;
  }
  return CheckColour(root, 0, leftpathblack);
  }
  bool IsBalance()
  {
  return IsBalance(_root);
  }

这段代码是用于检查红黑树是否平衡的函数实现。我来详细解释每个部分的功能:

整体结构

  • IsBalance(): 公开接口,调用内部实现。
  • IsBalance(Node* root): 检查根节点和计算左路径黑色节点数。
  • checkColour(): 递归检查红黑树性质。

函数详解

  1. IsBalance(Node* root)

功能

  • 检查根节点是否为黑色(红黑树性质2)。
  • 计算从根节点到最左叶子节点的黑色节点数量,作为基准值。
  • 调用 checkColour 进行递归检查。
  1. CheckColour(Node* root, int blacksum, int leftpathblack)

功能

  • 性质4检查:确保没有连续的红节点。
  • 性质5检查:确保所有路径的黑色节点数相同。
  • 递归遍历整棵树。

检查的红黑树性质

  1. 性质2:根节点是黑色(在 IsBalance 中检查)。
  2. 性质4:红色节点的子节点必须是黑色(在 CheckColour 中检查)。
  3. 性质5:从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点(在 CheckColour 中检查)。

2.3 Height

int Height()
{
return Height(_root);
}
int Height(Node* root)
{
if (root == nullptr)
{
return 0;
}
int heightL = Height(root->_left);
int heightR = Height(root->_right);
return  heightL > heightR ? heightL + 1 : heightR + 1;
}

3. 红黑树与AVL树的对比

选择AVL树的场景:

  • 查找频率远高于插入删除
  • 数据相对有序或接近有序
  • 涉及磁盘I/O等慢速存储
    • I/O成本极高,减少查找次数是首要目标 → 选高度更低的AVL树

选择红黑树的场景:

  • 频繁插入删除操作
  • 数据随机分布
  • 纯内存操作
    • I/O成本可忽略,减少数据移动和缓存失效更重要 → 选旋转更少的红黑树

现实世界的例子:

适合AVL树的场景:

  1. 维基百科全文索引(构建一次,查询海量)
  2. 编译器符号表(构建相对少,查找频繁)
  3. 只读配置数据库

适合红黑树的场景:

  1. Linux进程调度(频繁插入删除进程)
  2. 数据库事务管理(频繁更新)
  3. 实时游戏状态管理

为啥有序的数的插入更适合AVL树?

根本原因:

  1. 调整策略不同
// AVL树:局部调整
if (平衡因子 > 1) {
旋转调整();  // 只影响局部子树
}
// 红黑树:可能传播调整  
while (父节点是红色 && 违反规则) {
颜色变换();
if (仍然违反规则) {
旋转调整();  // 可能向上传播
}
}
  1. 有序数据的特殊性

有序数据插入会形成"链式"结构,这正是:

  • AVL树擅长处理的:通过单次旋转就能恢复平衡
  • 红黑树不擅长的:需要复杂的颜色变换和可能的多次调整

4. 源代码

RBTree.h

#pragma once
#include <iostream>
  using namespace std;
  enum Colour
  {
  RED,
  BLACK
  };
  template<typename K, typename V>
    struct RBTreeNode
    {
    pair<K, V> _kv;
      RBTreeNode<K, V>* _left;
        RBTreeNode<K, V>* _right;
          RBTreeNode<K, V>* _parent;
            Colour _col;
            RBTreeNode(const pair<K, V> kv)
              :_kv(kv)
              ,_left(nullptr)
              ,_right(nullptr)
              ,_parent(nullptr)
              ,_col(RED)
              {}
              };
              template<typename K, typename V>
                class RBTree
                {
                typedef RBTreeNode<K, V> Node;
                  public:
                  RBTree()
                  :_root(nullptr)
                  {}
                  bool Insert(const pair<K, V>& kv)
                    {
                    if (_root == nullptr)
                    {
                    _root = new Node(kv);
                    _root->_col = BLACK;
                    return true;
                    }
                    else
                    {
                    Node* parent = nullptr;
                    Node* cur = _root;
                    while (cur)
                    {
                    if (kv.first > cur->_kv.first)
                    {
                    parent = cur;
                    cur = cur->_right;
                    }
                    else if (kv.first < cur->_kv.first)
                      {
                      parent = cur;
                      cur = cur->_left;
                      }
                      else
                      {
                      return false;
                      }
                      }
                      cur = new Node(kv);
                      cur->_col = RED;
                      cur->_parent = parent;
                      if (cur->_kv.first > parent->_kv.first)
                      {
                      parent->_right = cur;
                      }
                      else
                      {
                      parent->_left = cur;
                      }
                      while (parent && parent->_col == RED)
                      {
                      Node* grandfather = parent->_parent;
                      if (parent == grandfather->_left)
                      {
                      Node* uncle = grandfather->_right;
                      if (uncle && uncle->_col == RED)
                      {
                      parent->_col = uncle->_col = BLACK;
                      grandfather->_col = RED;
                      cur = grandfather;
                      parent = cur->_parent;
                      _root->_col = BLACK;
                      }
                      else
                      {
                      if (cur == parent->_left)
                      {
                      RotateR(grandfather);
                      parent->_col = BLACK;
                      grandfather->_col = RED;
                      }
                      else //cur = parent->_right
                      {
                      RotateL(parent);
                      RotateR(grandfather);
                      cur->_col = BLACK;
                      grandfather->_col = RED;
                      }
                      break;
                      }
                      }
                      else //parent == grandfather->_right
                      {
                      Node* uncle = grandfather->_left;
                      if (uncle && uncle->_col == RED)
                      {
                      parent->_col = uncle->_col = BLACK;
                      grandfather->_col = RED;
                      cur = grandfather;
                      parent = cur->_parent;
                      _root->_col = BLACK;
                      }
                      else
                      {
                      if (cur == parent->_right)
                      {
                      RotateL(grandfather);
                      parent->_col = BLACK;
                      grandfather->_col = RED;
                      }
                      else //cur = parent->_left
                      {
                      RotateR(parent);
                      RotateL(grandfather);
                      cur->_col = BLACK;
                      grandfather->_col = RED;
                      }
                      break;
                      }
                      }
                      }
                      return true;
                      }
                      }
                      bool CheckColour(Node* root, int blacksum, int leftpathblack)
                      {
                      if (root == nullptr)
                      {
                      if (blacksum != leftpathblack)
                      {
                      cout << "每条路径上的黑色节点的数目不同" << endl;
                      return false;
                      }
                      return true;
                      }
                      Node* parent = root->_parent;
                      if (parent && parent->_col == RED && root->_col == RED)
                      {
                      cout << root->_kv.first << ':' << "出现两个连续的红节点" << endl;
                        return false;
                        }
                        if (root->_col == BLACK)
                        blacksum++;
                        return CheckColour(root->_left, blacksum, leftpathblack) &&
                        CheckColour(root->_right, blacksum, leftpathblack);
                        }
                        bool IsBalance()
                        {
                        return IsBalance(_root);
                        }
                        bool IsBalance(Node* root)
                        {
                        if (root == nullptr)
                        return true;
                        if (root->_col != BLACK)
                        {
                        cout << "根节点颜色错误不为黑色" << endl;
                        return false;
                        }
                        int leftpathbalck = 0;
                        Node* cur = root;
                        while (cur)
                        {
                        if (cur->_col == BLACK)
                        leftpathbalck++;
                        cur = cur->_left;
                        }
                        return CheckColour(root, 0,  leftpathbalck);
                        }
                        int Height()
                        {
                        return Height(_root);
                        }
                        int Height(Node* root)
                        {
                        if (root == nullptr)
                        {
                        return 0;
                        }
                        int heightL = Height(root->_left);
                        int heightR = Height(root->_right);
                        return  heightL > heightR ? heightL + 1 : heightR + 1;
                        }
                        private:
                        void RotateL(Node* parent)
                        {
                        _rotatesum++;
                        Node* ppnode = parent->_parent;
                        Node* cur = parent->_right;
                        Node* curleft = cur->_left;
                        parent->_right = curleft;
                        if (curleft)
                        {
                        curleft->_parent = parent;
                        }
                        cur->_left = parent;
                        parent->_parent = cur;
                        if (ppnode == nullptr)
                        {
                        _root = cur;
                        cur->_parent = nullptr;
                        }
                        else
                        {
                        cur->_parent = ppnode;
                        if (ppnode->_left == parent)
                        {
                        ppnode->_left = cur;
                        }
                        else
                        {
                        ppnode->_right = cur;
                        }
                        }
                        }
                        void RotateR(Node* parent)
                        {
                        _rotatesum++;
                        Node* ppnode = parent->_parent;
                        Node* cur = parent->_left;
                        Node* curright = cur->_right;
                        parent->_left = curright;
                        if (curright)
                        {
                        curright->_parent = parent;
                        }
                        cur->_right = parent;
                        parent->_parent = cur;
                        if (ppnode == nullptr)
                        {
                        _root = cur;
                        cur->_parent = nullptr;
                        }
                        else
                        {
                        if (ppnode->_left == parent)
                        {
                        ppnode->_left = cur;
                        cur->_parent = ppnode;
                        }
                        else
                        {
                        ppnode->_right = cur;
                        cur->_parent = ppnode;
                        }
                        }
                        }
                        private:
                        Node* _root;
                        public:
                        int _rotatesum = 0;
                        };
posted @ 2026-01-30 15:43  gccbuaa  阅读(1)  评论(0)    收藏  举报