数据结构进阶——红黑树 - 指南


1. 红黑树的概念


红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的

在这里插入图片描述


2. 红黑树的性质


  • 1. 每个结点不是红色就是黑色
  • 2. 根节点是黑色的
  • 3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  • 4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
  • 5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点,是红黑树特有的NIL节点)

其中,第5条性质,是为了帮大家更好的理解性质4,举个例子:下面这棵树符合红黑树的定义吗?
在这里插入图片描述
很显然,性质1,2,3都是符合的,但是性质4符合吗?所有路径上,是否有相同数量的黑色节点?是不是大眼一看感觉也符合性质4,别急,下面我们把NIL节点也带上:

在这里插入图片描述

可以看到,用蓝色线条画出的这条路径,是不是很容易被忽略?从根节点向下看,其他路径上的黑色节点数都是2个(不算NIL节点),唯独蓝色线路上的黑色节点只有1个,所以上面这棵树是不符合红黑树定义的,它不是红黑树!

满足了如上5条性质,就可以保证,最长路径不超过最短路径的两倍,但是这是为什么?感兴趣的同学可以下去思考一下,只能说,这是天才的设计,很抽象,从学习者的角度来讲,只要能使用并控制红黑树的结构,这就够了,没必要向天才看齐。


3. 红黑树节点的定义

下面我们先实现一颗kv结构的红黑树。


// 颜色定义
enum Colour
{
RED,
BLACK
};
// 红黑树节点
template<class K, class V>
  struct RBTreeNode
  {
  RBTreeNode<K, V>* _left;		// 左节点
    RBTreeNode<K, V>* _right;		// 右节点
      RBTreeNode<K, V>* _parent;		// 父节点
        pair<K, V> _kv;					// 节点保存的数据
          Colour _col;					// 节点颜色
          RBTreeNode(const pair<K, V>& kv)
            :_left(nullptr)
            , _right(nullptr)
            , _parent(nullptr)
            , _kv(kv)
            , _col(RED)
            {}
            };

4. 红黑树的插入


1. 思考,如果我们要新插入一个节点,这个节点应该是红色还是黑色?

  • 先假设我们插入黑色节点,这会导致什么?会导致一整条路径上的黑色节点数量+1,这代价是不是太大了,我们要进行很多调整结构的操作,使得树重新满足性质4。

  • 如果我们插入红色节点呢?

    • 如果新节点的父节点是黑色的,那么就结束了,不用做什么其他调整,就算插入成功了,因为这完全不会影响树的整体结构。
      在这里插入图片描述
    • 如果新节点的父节点是红色,那么为了满足性质3(红色节点的子节点必须是黑色),就需要做出一些调整。
      在这里插入图片描述
  • 单单分析到这,就发现,插入红色节点,要比插入黑色节点代价小的多,很多情况下竟然只需要变色,就可以使红黑树结构平衡,而不用调整节点间的父子关系(旋转)。

2. 确定了新节点必须是红色节点,接下来就是分情况讨论

  • 如果新节点的双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;

  • 但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论;

  • 约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点(cur不一定是新插入节点,也可能是调整上去的)。
    在这里插入图片描述

  • 如果一颗子树需要被调整,那么至少有三个节点的颜色,我们是确定的1. cur当前节点为红色。2. cur的父节点一定是红色。3. cur的爷爷节点,也就是父节点的父节点,一定是黑色。 因为调整之前,以cur为根节点的子树的上一级树必然是符合规则的,如果不符合,还调整什么?这棵树就是有问题的。

  • 所以接下来,我们讨论的实际上是叔叔节点不同颜色or存在/不存在的情况 + cur在p的左节点or右节点的情况 + p在g的左节点or右节点的情况(叔叔节点也有可能不存在)。

在这里插入图片描述

  • 将上面这些情况全部综合起来,一共有 4 * 4 = 16 种情况需要讨论u节点存在or不存在or红色or黑色,共四种 X 上图中四种节点位置状态,共四种。
  • 有同学可能会想,如果u节点是黑色,那么整个树就不满足规则4了啊,平衡被打破了啊。所以u节点要么是红色,要么不存在。这就是没有考虑到,cur可能不是新插入节点,可能是原来是黑色的,只是调整的过程中,由黑变红了。这种情况下,整棵树的黑节点数量还是平衡的。

下面的讨论中有一些情况会合并成一种情况(好编程),最终呈现出来,会小于16种。但是我们需要知道,本质上,就是对这16种情况进行处理。

2. 开始讨论

  • 情况一(叔叔红):

    • 情况1.1:pg的左节点,curp的左节点,u存在且为红(左左红)。

      • 解决方案:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
        在这里插入图片描述
    • 情况1.2:pg的右节点,curp的右节点,u存在且为红(右右红)。

      • 解决方案和情况一完全相同,这里不画图了,大家对着情况一的图,自行脑补一下。
    • 情况1.3:pg的左节点,curp的右节点,u存在且为红(左右红)。

      • 解决方案和情况一完全相同
    • 情况1.4:pg的右节点,curp的左节点,u存在且为红(右左红)。

      • 解决方案和情况一完全相同
    • 将上面所有的情况1.*,总结为一种,即叔叔为红色(叔叔红),统一解决方案:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

  • 情况二(左左黑):pg的左节点,curp的左节点,u不存在/u存在且为黑。

    • 解决方案:先对g节点进行右单旋,然后将p,g变色--p变黑,g变红。
      在这里插入图片描述
  • 情况三(右右黑):pg的右节点,curp的右节点,u不存在/u存在且为黑。

    • 解决方案: 与情况三相比,唯一的不同是,需要对g进行左单旋。变色部分完全一样,p,g变色--p变黑,g变红
  • 情况四(左右黑):pg的左节点,curp的右节点,u不存在/u存在且为黑。

    • 解决方案:先对p进行左单旋,转化为情况二(左左黑),再根据情况二进行处理。 总结一下就是,先左右双旋,再将cur变黑,g变红。
      在这里插入图片描述
  • 情况五(右左黑):pg的右节点,curp的左节点,u存在且为红。

    • 解决方案:先对p进行右单旋,转化为情况三(右右黑),再根据情况三进行处理。 总结一下就是,先进行右左双旋,再将cur变黑,g变红。
template<class K, class V>
  class RBTree
  {
  typedef RBTreeNode<K, V> Node;
    private:
    Node* _root = nullptr;
    public:
    bool Insert(const pair<K, V>& kv)
      {
      if (_root == nullptr)
      {
      _root = new Node(kv);
      _root->_col = BLACK;	// 根节点必须是黑色
      return true;
      }
      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;
        if (kv.first > parent->_kv.first)
        {
        parent->_right = cur;
        cur->_parent = parent;
        }
        else
        {
        parent->_left = cur;
        cur->_parent = parent;
        }
        // 如果父节点存在并且是红色,再处理,是黑色不用处理
        while(parent && parent->_col == RED)
        {
        Node* grandFather = parent->_parent;
        if (parent == grandFather->_left)
        {
        // 这里只是说父亲在爷爷左边,而cur有可能在父亲的左节点也有可能在右节点
        // 后面还要再做讨论
        //		g
        //	  p	  u
        //	  c
        // 父亲是爷爷的左节点,那么叔叔就是爷爷的右节点
        Node* uncle = grandFather->_right;
        // 叔叔存在,并且叔叔是红色(叔叔红)
        if (uncle && uncle->_col == RED)
        {
        // 变色
        uncle->_col = parent->_col = BLACK;
        grandFather->_col = RED;
        // 继续往上处理
        cur = grandFather;
        parent = cur->_parent;
        }
        else
        {
        if (cur == parent->_left)
        {
        // cur 在父亲左边,右单旋(左左黑)
        //		g
        //	  p
        //	c
        RotateR(grandFather);
        parent->_col = BLACK;
        grandFather->_col = RED;
        }
        else
        {
        // cur 在父亲右边,左右双旋(左右黑)
        //		g
        //	  p
        //		c
        RotateL(parent);
        RotateR(grandFather);
        grandFather->_col = RED;
        cur->_col = BLACK;
        }
        // 旋转之后直接就平衡了,直接break
        break;
        }
        }
        else // parent == grandFather->_right
        {
        // 这里只是说父亲在爷爷右边,而cur有可能在父亲的左节点也有可能在右节点
        // 后面还要再做讨论
        //		g
        //	  u	  p
        //		  c
        // 父亲是爷爷的右节点,那么叔叔就是左节点
        Node* uncle = grandFather->_left;
        // 叔叔存在,并且叔叔是红色(叔叔红)
        if (uncle && uncle->_col == RED)
        {
        // 变色
        uncle->_col = parent->_col = BLACK;
        grandFather->_col = RED;
        // 继续向上处理
        cur = grandFather;
        parent = cur->_parent;
        }
        else
        {
        if (cur == parent->_right)
        {
        // cur 在父亲右边,左单旋(右右黑)
        //		g
        //		  p
        //			c
        RotateL(grandFather);
        parent->_col = BLACK;
        grandFather->_col = RED;
        }
        else
        {
        // cur 在父亲左边,右左双旋(右左黑)
        //		g
        //		  p
        //		c
        RotateR(parent);
        RotateL(grandFather);
        grandFather->_col = RED;
        cur->_col = BLACK;
        }
        // 旋转之后直接就平衡了,直接break
        break;
        }
        }
        }
        // 不管前面如何处理,最后都要把根节点变黑
        _root->_col = BLACK;
        return true;
        }
        }

5. 红黑树的验证


红黑树的检测分为两步:

  • 检测其是否满足二叉搜索树(中序遍历是否为有序序列);
  • 检测其是否满足红黑树的性质。
template<class K, class V>
  class RBTree
  {
  typedef RBTreeNode<K, V> Node;
    private:
    Node* _root = nullptr;
    public:
    ...
    // 中序遍历
    void _InOrder(Node* root)
    {
    if (root == nullptr)
    return;
    _InOrder(root->_left);
    cout << root->_kv.first << " ";
      _InOrder(root->_right);
      }
      void InOrder()
      {
      _InOrder(_root);
      cout << endl;
      }
      // 检查
      //	1. 红色节点的子节点是否为黑色节点
      //	2. 根节点->当前节点这条路径的黑色节点的数量
      bool Check(Node* root, int blacknum, const int refVal)
      {
      if (root == nullptr)
      {
      if (blacknum != refVal)
      {
      cout << "存在黑色节点数量不相等的路径" << endl;
      return false;
      }
      return true;
      }
      // 子节点情况太多了,我们直接反向检查父节点,只要红色节点的父节点不是红色,就是符合要求的
      if (root->_col == RED && root->_parent->_col == RED)
      {
      cout << "有连续的红色节点" << endl;
      return false;
      }
      if (root->_col == BLACK)
      {
      blacknum++;
      }
      return Check(root->_left, blacknum, refVal)
      && Check(root->_right, blacknum, refVal);
      }
      // 看看红黑树是否符合要求
      bool IsBalance()
      {
      return _IsBalance(_root);
      }
      bool _IsBalance(Node* root)
      {
      if (root == nullptr)
      return true;
      if (root->_col == RED)
      return false;
      // 参考值,算出最左路径的黑节点数量
      int refVal = 0;
      Node* cur = root;
      while (cur) {
      if (cur->_col == BLACK)
      refVal++;
      cur = cur->_left;
      }
      int blacknum = 0;
      return Check(root, blacknum, refVal);
      }
      };

6. 红黑树的删除


红黑树的删除本节不做讲解,有兴趣的同学可参考:《算法导论》或者《STL源码剖析》http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html


7. 红黑树完整代码+测试


1. 完整代码

#pragma once
#include <iostream>
  enum Colour
  {
  RED,
  BLACK
  };
  using namespace std;
  template<class K, class V>
    struct RBTreeNode
    {
    RBTreeNode<K, V>* _left;
      RBTreeNode<K, V>* _right;
        RBTreeNode<K, V>* _parent;
          pair<K, V> _kv;
            Colour _col;
            RBTreeNode(const pair<K, V>& kv)
              :_left(nullptr)
              , _right(nullptr)
              , _parent(nullptr)
              , _kv(kv)
              , _col(RED)
              {}
              };
              template<class K, class V>
                class RBTree
                {
                typedef RBTreeNode<K, V> Node;
                  private:
                  Node* _root = nullptr;
                  public:
                  bool Insert(const pair<K, V>& kv)
                    {
                    if (_root == nullptr)
                    {
                    _root = new Node(kv);
                    _root->_col = BLACK;	// 根节点必须是黑色
                    return true;
                    }
                    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;
                      if (kv.first > parent->_kv.first)
                      {
                      parent->_right = cur;
                      cur->_parent = parent;
                      }
                      else
                      {
                      parent->_left = cur;
                      cur->_parent = parent;
                      }
                      // 如果父节点存在并且是红色,再处理,是黑色不用处理
                      while(parent && parent->_col == RED)
                      {
                      Node* grandFather = parent->_parent;
                      if (parent == grandFather->_left)
                      {
                      // 这里只是说父亲在爷爷左边,而cur有可能在父亲的左节点也有可能在右节点
                      // 后面还要再做讨论
                      //		g
                      //	  p	  u
                      //	  c
                      // 父亲是爷爷的左节点,那么叔叔就是爷爷的右节点
                      Node* uncle = grandFather->_right;
                      // 叔叔存在,并且叔叔是红色(叔叔红)
                      if (uncle && uncle->_col == RED)
                      {
                      // 变色
                      uncle->_col = parent->_col = BLACK;
                      grandFather->_col = RED;
                      // 继续往上处理
                      cur = grandFather;
                      parent = cur->_parent;
                      }
                      else
                      {
                      if (cur == parent->_left)
                      {
                      // cur 在父亲左边,右单旋(左左黑)
                      //		g
                      //	  p
                      //	c
                      RotateR(grandFather);
                      parent->_col = BLACK;
                      grandFather->_col = RED;
                      }
                      else
                      {
                      // cur 在父亲右边,左右双旋(左右黑)
                      //		g
                      //	  p
                      //		c
                      RotateL(parent);
                      RotateR(grandFather);
                      grandFather->_col = RED;
                      cur->_col = BLACK;
                      }
                      // 旋转之后直接就平衡了,直接break
                      break;
                      }
                      }
                      else // parent == grandFather->_right
                      {
                      // 这里只是说父亲在爷爷右边,而cur有可能在父亲的左节点也有可能在右节点
                      // 后面还要再做讨论
                      //		g
                      //	  u	  p
                      //		  c
                      // 父亲是爷爷的右节点,那么叔叔就是左节点
                      Node* uncle = grandFather->_left;
                      // 叔叔存在,并且叔叔是红色(叔叔红)
                      if (uncle && uncle->_col == RED)
                      {
                      // 变色
                      uncle->_col = parent->_col = BLACK;
                      grandFather->_col = RED;
                      // 继续向上处理
                      cur = grandFather;
                      parent = cur->_parent;
                      }
                      else
                      {
                      if (cur == parent->_right)
                      {
                      // cur 在父亲右边,左单旋(右左黑)
                      //		g
                      //		  p
                      //			c
                      RotateL(grandFather);
                      parent->_col = BLACK;
                      grandFather->_col = RED;
                      }
                      else
                      {
                      // cur 在父亲左边,右左双旋(左右黑)
                      //		g
                      //		  p
                      //		c
                      RotateR(parent);
                      RotateL(grandFather);
                      grandFather->_col = RED;
                      cur->_col = BLACK;
                      }
                      // 旋转之后直接就平衡了,直接break
                      break;
                      }
                      }
                      }
                      // 不管前面如何处理,最后都要把根节点变黑
                      _root->_col = BLACK;
                      return true;
                      }
                      // 左单旋
                      void RotateL(Node* parent)
                      {
                      Node* subR = parent->_right;
                      Node* subRL = subR->_left;
                      // 更新左右节点
                      parent->_right = subRL;
                      subR->_left = parent;
                      Node* parentParent = parent->_parent;
                      // 更新父节点
                      parent->_parent = subR;
                      if (subRL != nullptr)
                      {
                      subRL->_parent = parent;
                      }
                      // 将子树链接进整体
                      if (_root == parent)
                      {
                      _root = subR;
                      subR->_parent = nullptr;
                      }
                      else if (parentParent->_left == parent)
                      {
                      parentParent->_left = subR;
                      subR->_parent = parentParent;
                      }
                      else
                      {
                      parentParent->_right = subR;
                      subR->_parent = parentParent;
                      }
                      }
                      // 右单旋
                      void RotateR(Node* parent)
                      {
                      Node* subL = parent->_left;
                      Node* subLR = subL->_right;
                      // 更新左右节点
                      parent->_left = subLR;
                      subL->_right = parent;
                      Node* parentParent = parent->_parent;
                      // 更新父节点
                      parent->_parent = subL;
                      if (subLR != nullptr)
                      {
                      subLR->_parent = parent;
                      }
                      // 将子树链接进整体
                      if (_root == parent)
                      {
                      _root = subL;
                      subL->_parent = nullptr;
                      }
                      else if (parentParent->_left == parent)
                      {
                      parentParent->_left = subL;
                      subL->_parent = parentParent;
                      }
                      else
                      {
                      parentParent->_right = subL;
                      subL->_parent = parentParent;
                      }
                      }
                      // 中序遍历
                      void _InOrder(Node* root)
                      {
                      if (root == nullptr)
                      return;
                      _InOrder(root->_left);
                      cout << root->_kv.first << " ";
                        _InOrder(root->_right);
                        }
                        void InOrder()
                        {
                        _InOrder(_root);
                        cout << endl;
                        }
                        // 检查
                        //	1. 红色节点的子节点是否为黑色节点
                        //	2. 根节点->当前节点这条路径的黑色节点的数量
                        bool Check(Node* root, int blacknum, const int refVal)
                        {
                        if (root == nullptr)
                        {
                        if (blacknum != refVal)
                        {
                        cout << "存在黑色节点数量不相等的路径" << endl;
                        return false;
                        }
                        return true;
                        }
                        // 子节点情况太多了,我们直接反向检查父节点,只要红色节点的父节点不是红色,就是符合要求的
                        if (root->_col == RED && root->_parent->_col == RED)
                        {
                        cout << "有连续的红色节点" << endl;
                        return false;
                        }
                        if (root->_col == BLACK)
                        {
                        blacknum++;
                        }
                        return Check(root->_left, blacknum, refVal)
                        && Check(root->_right, blacknum, refVal);
                        }
                        // 看看红黑树是否符合要求
                        bool IsBalance()
                        {
                        return _IsBalance(_root);
                        }
                        bool _IsBalance(Node* root)
                        {
                        if (root == nullptr)
                        return true;
                        if (root->_col == RED)
                        return false;
                        // 参考值,算出最左路径的黑节点数量
                        int refVal = 0;
                        Node* cur = root;
                        while (cur) {
                        if (cur->_col == BLACK)
                        refVal++;
                        cur = cur->_left;
                        }
                        int blacknum = 0;
                        return Check(root, blacknum, refVal);
                        }
                        bool Find(const K& key)
                        {
                        Node* cur = _root;
                        while (cur)
                        {
                        if (cur->_kv.first < key)
                        {
                        cur = cur->_right;
                        }
                        else if (cur->_kv.first > key)
                        {
                        cur = cur->_left;
                        }
                        else
                        {
                        return true; // 找到了,返回真
                        }
                        }
                        return false; // 找不到,返回假
                        }
                        int _Height(Node* root)
                        {
                        if (root == nullptr)
                        return 0;
                        int leftHeight = _Height(root->_left);
                        int rightHeight = _Height(root->_right);
                        return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
                        }
                        int Height()
                        {
                        return _Height(_root);
                        }
                        int _Size(Node* root)
                        {
                        if (root == nullptr)
                        {
                        return 0;
                        }
                        return _Size(root->_left) + _Size(_root->_right) + 1;
                        }
                        int Size()
                        {
                        return _Size(_root);
                        }
                        };

2. 测试Insert和Find的效率

#include <iostream>
  #include <chrono>
    #include <vector>
      using namespace std;
      #include "RBTree.h"
      // 小范围测试
      void Test1()
      {
      int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
      RBTree<int, int> t;
        for (auto e : a)
        {
        t.Insert(make_pair(e, e));
        }
        t.InOrder();
        cout << "IsBalance: " << t.IsBalance() << endl;
        }
        // 大范围测试
        void Test2()
        {
        const int N = 100 * 10000;  // 测试100w条数据的插入
        vector<int> v;
          v.reserve(N);
          srand((unsigned int)time(0));
          for (size_t i = 0; i < N; i++)
          {
          v.push_back(rand() + (int)i);
          }
          RBTree<int, int> t;
            // 测试插入耗时
            auto start1 = chrono::high_resolution_clock::now();
            for (auto e : v)
            {
            t.Insert(make_pair(e, e));
            }
            auto end1 = chrono::high_resolution_clock::now();
            double insertTime = chrono::duration_cast<chrono::microseconds>(end1 - start1).count() / 1e6;
              // 测试查找耗时
              auto start2 = chrono::high_resolution_clock::now();
              if (t.Find(v[100])) cout << "Find Success!!!" << endl;
              auto end2 = chrono::high_resolution_clock::now();
              double findTime = chrono::duration_cast<chrono::microseconds>(end2 - start2).count() / 1e6;
                cout << "IsBalance: " << t.IsBalance() << endl;
                cout << "Heigh: " << t.Height() << endl;
                cout << "Insert cost: " << insertTime << " s" << endl;
                cout << "Find cost: " << findTime << " s" << endl;
                }
                int main()
                {
                Test2();
                return 0;
                }
  • Test2大范围测试的结果,数据量100w:
Find Success!!!
IsBalance: 1
Heigh: 27
Insert cost: 0.304353 s
Find cost: 0.00027 s
  • 可以看到插入和查找效率非常高。

posted @ 2025-12-10 21:48  clnchanpin  阅读(3)  评论(0)    收藏  举报