RB-tree(红黑树)详解

简介: RB-tree(红黑树)详解

RB-tree(红黑树)

红黑树的规则如下:

1.每个节点不是红色就是黑色

2.根节点为黑色

3.如果节点为红色,那么它的子节点必须为黑色

4.任何一个节点到NULL(树的尾端)的任何路径所包含的黑节点个数相同

简而言之就是每个路径的黑色节点数量相同

新增的节点必须为红,因为增加红色节点所要付出的代价会比黑色的要小很多,如果新增节点为黑色,那么就会打破第四条规则,整棵树都要调整,代价是相当之大的

红黑树节点的定义

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()
    :_left(nullptr)
    ,_right(nullptr)
    ,_parent(nullptr)
    ,_kv(kv)
  {}
};

红黑树的插入操作

红黑树的插入操作分为几种情况

情况一:

当cur为红、parent为红、grandparent为黑、uncle存在并且为红

操作过程:

将parent和uncle全部染成黑色,grandparent染成红色,然后把cur给grandparent继续向上判断,如果cur的parent依旧为红色,那么就要继续染色,如果cur为_root了,将根节点染成黑色。

具象图:

抽象图:

在抽象图当中,在a或者b子树当中触发了染色机制,然后一路染色上来,一直到cur也染成了红色,cur和p就造成冲突,需要继续染色。

情况二:

当cur为红,p为红,g黑,u不存在/u为黑

操作:

p为g的左孩子,cur为p的左孩子,则进行右单旋转;

相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转

最后p变为黑色,cur和g变为红色

具象图:

当uncle为空的时候

抽象图:

情况三:

当cur和p为红,g为黑,u为黑/不存在

如果parent为grandparent的左儿子,对parent左旋,再对grandparent右旋

如果parent为grandparent的右儿子,对parent右旋,再对grandparent左旋

cur染为黑色,cur和grandparent染为红色。

具象图:

抽象图:

红黑树+测试代码

#pragma once
#include"AVLTree.h"
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)
  {}
};
template<class K,class V>
class RBTree
{
  typedef RBTreeNode<K,V> Node;
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 (cur->_kv.first<kv.first)
      {
        parent = cur;
        cur = cur->_right;
      }
      else if (cur->_kv.first > kv.first)
      {
        parent = cur;
        cur = cur->_left;
      }
      else
      {
        return false;
      }
    }
    //找到插入点
    cur = new Node(kv);
    cur->_col = Red;
    //链接节点
    if (parent->_kv.first < kv.first)
    {
      parent->_right = cur;
    }
    else
    {
      parent->_left = cur;
    }
    cur->_parent = parent;
    //检查是否满足红黑树的要求
    while (parent && parent->_col == Red)
    {
      //如果父亲节点的颜色为红色,那么就有可能需要改变颜色或者旋转
      Node* grandparent = parent->_parent;
      assert(grandparent);
      assert(grandparent->_col == Black);//父亲为红色,那么祖父一定为黑色,不然之前的插入就存在问题
      if (parent == grandparent->_left)
      {
        Node* uncle = grandparent->_right;
        //情况一,uncle存在并且uncle为红色,就要继续往上染色
        if (uncle && uncle->_col == Red)
        {
          parent->_col = uncle->_col = Black;
          grandparent->_col = Red;
          cur = grandparent;
          parent = cur->_parent;
        }
        else
        {
          //uncle不存在或者uncle为黑色就会走到这里
          //情况二+情况三
          if (cur == parent->_left)
          {
            // 情况二:右单旋+变色
            //     g 
            //   p   u
            // c
            //右旋+染色
            RotateR(grandparent);
            parent->_col = Black;
            grandparent->_col = Red;
          }
          else
          {
            // 情况三:左右单旋+变色
            //     g 
            //   p   u
            //     c
            RotateL(parent);
            RotateR(grandparent);
            cur->_col = Black;
            parent->_col = grandparent->_col = Red;
          }
          break;
        }
      }
      else
      {
        Node* uncle = grandparent->_left;
        if (uncle && uncle->_col == Red)
        {
          parent->_col = uncle->_col = Black;
          grandparent->_col = Red;
          cur = grandparent;
          parent = cur->_parent;
        }
        else
        {
          if (cur == parent->_right)
          {
            RotateL(grandparent);
            parent->_col = Black;
            grandparent->_col = Red;
          }
          else
          {
            RotateR(parent);
            RotateL(grandparent);
            cur->_col = Black;
            grandparent->_col = Red;
          }
          break;
        }
      }
    }
    _root->_col = Black;
    return true;
  }
  void InOrder()
  {
    _InOrder(_root);
    cout << endl;
  }
  bool IsBalance()
  {
    if (_root == nullptr)
    {
      return true;
    }
    if (_root->_col == Red)
    {
      cout << "根节点不是黑色" << endl;
      return false;
    }
    // 黑色节点数量基准值
    int benchmark = 0;
    Node* cur = _root;
    while (cur)
    {
    if (cur->_col == Black)
    ++benchmark;
    cur = cur->_left;
    }
    return PrevCheck(_root, 0, benchmark);
  }
private:
  bool PrevCheck(Node* root, int blackNum, int& benchmark)
  {
    if (root == nullptr)
    {
      if (blackNum != benchmark)
      {
        cout << "某条黑色节点的数量不相等" << endl;
        return false;
      }
      else
      {
        return true;
      }
    }
    if (root->_col == Black)
    {
      ++blackNum;
    }
    if (root->_col == Red && root->_parent->_col == Red)
    {
      cout << "存在连续的红色节点" << endl;
      return false;
    }
    return PrevCheck(root->_left, blackNum, benchmark)
      && PrevCheck(root->_right, blackNum, benchmark);
  }
  void _InOrder(Node* root)
  {
    if (root == nullptr)
    {
      return;
    }
    _InOrder(root->_left);
    cout << root->_kv.first << ":" << root->_kv.second << endl;
    _InOrder(root->_right);
  }
  void RotateL(Node* parent)
  {
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    Node* pparent = parent->_parent;
    parent->_right = subRL;
    if (subRL)
    {
      subRL->_parent = parent;
    }
    subR->_left = parent;
    parent->_parent = subR;
    if (parent == _root)
    {
      _root = subR;
      subR->_parent = nullptr;
    }
    else
    {
      if (pparent->_left == parent)
      {
        pparent->_left = subR;
      }
      else
      {
        pparent->_right = subR;;
      }
      subR->_parent = pparent;
    }
  }
  void RotateR(Node* parent)
  {
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    parent->_left = subLR;
    Node* pparent = parent->_parent;
    //防止空指针
    if (subLR)
    {
      subLR->_parent = parent;
    }
    subL->_right = parent;
    parent->_parent = subL;
    if (parent == _root)
    {
      //如果parent就是根节点
      _root = subL;
      subL->_parent = nullptr;
    }
    else
    {
      //如果parent只是一颗子树的根节点,就还需要连接好parent
      //判断是左还是右节点
      if (pparent->_left == parent)
      {
        pparent->_left = subL;
      }
      else
      {
        pparent->_right = subL;
      }
      subL->_parent = pparent;
    }
  }
  Node* _root = nullptr;
};
void TestRBTree2()
{
  size_t N = 1000;
  srand(time(0));
  RBTree<int, int> t1;
  for (size_t i = 0; i < N; ++i)
  {
    int x = rand();
    cout << "Insert:" << x << ":" << i << endl;
    t1.Insert(make_pair(x, i));
  }
  cout << "IsBalance:" << t1.IsBalance() << endl;
}

运行结果:

目录
相关文章
|
数据可视化 关系型数据库 开发工具
开放原子训练营(第三季)inBuilder低代码开发实验室之探秘
开放原子训练营(第三季)inBuilder低代码开发实验室之探秘
342 0
开放原子训练营(第三季)inBuilder低代码开发实验室之探秘
|
10月前
|
人工智能 Serverless API
aliyun解决方案评测|主动式智能导购AI助手构建
《主动式智能导购AI助手构建》方案结合百炼大模型与函数计算,提供高效智能导购服务。然而,实际体验中发现官方教程的说明顺序有待优化,特别是关于百炼大模型服务开通及API-key的使用指引不够清晰,导致初次使用者需查阅额外资料。此外,架构设计和实践原理在部署过程中逐步展现,有助于理解,但针对生产环境的具体指导还需进一步完善以满足实际需求。为优化用户体验,建议调整文档中的步骤顺序,确保新手能更顺畅地完成部署和测试。
338 27
|
SQL 关系型数据库 MySQL
MySQL数据库中给表添加字段并设置备注的脚本编写
通过上述步骤,你可以在MySQL数据库中给表成功添加新字段并为其设置备注。这样的操作对于保持数据库结构的清晰和最新非常重要,同时也帮助团队成员理解数据模型的变化和字段的具体含义。在实际操作中,记得调整脚本以适应具体的数据库和表名称,以及字段的详细规范。
361 8
|
Cloud Native Devops 持续交付
云原生技术:引领数字化转型的新浪潮
本文深入探讨了云原生技术的概念、核心原则以及其在推动企业数字化转型中的重要作用。通过分析云原生技术的发展趋势和面临的挑战,为读者提供全面而深入的理解,旨在启发思考并指导实践。
|
存储 索引 Python
哈希表是怎么删除元素的,能直接删除吗?
哈希表是怎么删除元素的,能直接删除吗?
250 3
|
消息中间件 传感器 Cloud Native
事件驱动作为分布式异步服务架构
【6月更文挑战第25天】本文介绍事件驱动架构(EDA)是异步分布式设计的关键模式,适用于高扩展性需求。EDA提升服务韧性,支持CQRS、数据通知、开放式接口和事件流处理。然而,其脆弱性包括组件控制、数据交换、逻辑关系复杂性、潜在死循环和高并发挑战。EDA在云原生环境,如Serverless,中尤其适用。
521 2
事件驱动作为分布式异步服务架构
|
存储 Kubernetes 对象存储
Velero 系列文章(四):使用 Velero 进行生产迁移实战
Velero 系列文章(四):使用 Velero 进行生产迁移实战
|
存储 网络协议 安全
「译文」CMDB 最佳实践技术指南 -3-CMDB 应用映射 - 技术原理和最佳实践
「译文」CMDB 最佳实践技术指南 -3-CMDB 应用映射 - 技术原理和最佳实践
|
机器学习/深度学习 人工智能 机器人
「AIGC」DALL-E2详解
**DALL-E 2是OpenAI的文本到图像生成器,融合艺术与技术,通过文本编码、先验模块和图像解码创新性地将描述转化为视觉作品。它能理解抽象概念,生成多样化、高质量图像,应用于艺术、设计及媒体行业。然而,细节处理有限且涉及伦理挑战。**
686 0
|
存储 负载均衡 网络协议
在Linux中优化系统性能的实用指南
【4月更文挑战第30天】本文是关于Linux系统性能优化的指南,涵盖硬件选择、系统及软件更新、调整Swap分区、内核参数优化、使用性能分析工具、文件系统优化、网络服务优化和定期维护等方面。通过这些方法,可提升系统响应速度,降低资源消耗,延长硬件寿命。注意,优化需根据具体系统和应用需求进行。