【C++第二十章】红黑树

【C++第二十章】红黑树

红黑树介绍🧐

  红黑树是一种自平衡的二叉搜索树,通过颜色标记和特定规则保持树的平衡性,从而在动态插入、删除等操作中维持较高的效率。它的最长路径不会超过最短路径的两倍,它的查找效率比AVL树更慢(对于CPU来说可以忽略不计),但是它不会像AVL树那样花费更大的代价去实现严格平衡(旋转)。

1.红黑树与AVL树🔎

特性红黑树AVL树
平衡标准通过颜色规则约束,允许一定不平衡严格平衡(左右子树高度差≤1)
插入/删除效率旋转次数少,调整频率低可能需要多次旋转,调整频率高
查询效率平均稍慢(高度≤2log(n+1))更快(高度严格为O(log n))
适用场景频繁插入/删除(如内存数据库、STL Map)查询密集(如字典、静态数据集)
实现复杂度较复杂(需处理颜色标记和多种情况)相对简单(仅维护平衡因子)

2.设计思想🔎

  1. 平衡与效率的折衷
    红黑树通过放宽平衡条件(允许局部不平衡),减少插入/删除时的调整次数,适合写多读少的场景。AVL树追求绝对平衡,适合读多写少的场景。
  2. 模拟B树结构
    红黑树可视为一种“二叉化”的B树(如2-3-4树),每个节点隐含多个键值,通过颜色标记合并逻辑,减少树的高度。
  3. 工业界应用
    • 红黑树:Java的 TreeMap、C++的 std::map、Linux内核调度器。
    • AVL树:Windows内核对象管理、需要快速查找的静态数据库。

  所以,若需要高频插入/删除,我们可以选择红黑树,若需要快速查询,不怎么变动数据,选择AVL树。

3.红黑树的性质🔎

  1. 每个节点不是红色就是黑色。
  2. 根节点一定是黑色的。
  3. 如果一个节点是红色的,则它的两个孩子节点是黑色的。(不能出现连续的红色节点)。
  4. 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。(每条路径黑色节点数量相同)。
  5. 每个叶子节点都是黑色的(这里的叶子节点指的是空指针节点,也称NIL节点)

  其中,路径的条数是从根节点到NIL节点来计算的,并且NIL节点的黑色也要统计到每条路径的黑色节点数量中。红黑树的最长和最短路径不一定存在。

1


红黑树插入实现🧐

  红黑树的平衡方式为:直接变色、旋转变色,所以我们还是要用到AVL树中的旋转代码。

1.红黑树的节点定义🔎

enum Color //定义颜色枚举,增加可读性
{
	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; //这里使用的是KV模型
	Color _col; //颜色
	RBTreeNode(const pair<K, V>& kv)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED) //默认设置为红色,如果默认为黑色,则每次插入必定违反性质4
	{}
};

2.左单旋、右单旋🔎

  与AVL树中一摸一样的方法。

//左单旋
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) //subRL不为空才处理
     subRL->_parent = parent;

 if (_root == parent) //当parent就是整个树的根,直接改变
 {
     _root = subR;
     subR->_parent = nullptr;
 }
 else
 {
     //如果不是,结点在父左边就将左边置为subR,否则右边
     if (parentParent->_left == parent)
     {
         parentParent->_left = subR;
     }
     else
     {
         parentParent->_right = subR;
     }
     subR->_parent = parentParent; //父节点也要改变
 }
}

//右单旋
void RotateR(Node* parent)
{
 Node* subL = parent->_left;
 Node* subLR = subL->_right;

 parent->_left = subLR;
 if (subLR) //subLR不为空才处理
     subLR->_parent = parent;
 //放在subL下面,不然parentParent可能为空
 Node* parentParent = parent->_parent; //记录传来结点的父节点,后面判断是否为整个树的根

 subL->_right = parent;
 parent->_parent = subL;

 if (_root == parent) //当parent就是整个树的根,直接改变
 {
     _root = subL;
     subL->_parent = nullptr;
 }
 else
 {
     //如果不是,结点在父左边就将左边置为subL,否则右边
     if (parentParent->_left == parent)
     {
         parentParent->_left = subL;
     }
     else
     {
         parentParent->_right = subL;
     }
     subL->_parent = parentParent; //subL父节点也要改变
 }
}

3.插入🔎

  我们在节点定义时就将默认颜色设置为红色,是因为将默认颜色设置为黑色的话,那么一个正常的红黑树突然插入一个黑色必定违反性质4,则每次都需要调整。


而插入红色时我们进行分析得:

1.当插入节点的父亲节点为黑色时,没有违反任何性质,不需要处理

2.当插入节点的父亲节点为红色时,违反性质3,需要处理


由此再次对红黑树分情况讨论:

我们首先约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点


情况一:cur为红,p为红,g为黑,u存在且为红

解决方法:

  将p,u改为黑,g改为红,如果g是根节点,那么再改为黑,如果不是根节点且违反性质4,那么将g看为cur,再次进行调整。
image-20250220162857523


情况二:cur为红,p为红,g为黑,u不存在或者u存在且为黑

解决方法:

  如果p是g的左孩子,cur为p的左孩子,则进行右单旋;如果p是g的右孩子,cur为p的右孩子,则进行左单旋。然后进行p、g变色,p变黑,g变红。

image-20250220163422304


情况三:cur为红,p为红,g为黑,u不存在或者存在且为黑。

解决方法:

  如果p是g的左孩子,cur为p的右孩子,则进行左单旋;如果p是g的右孩子,cur为p的左孩子,则进行右单旋。然后转换为情况二处理。
image-20250220170256043


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;
  cur->_parent = parent;
}
else
{
  parent->_left = cur;
  cur->_parent = parent;
}

// 父亲为红色,需要处理
while (parent && parent->_col == RED)
{
  Node* grandfather = parent->_parent; 
  if (parent == grandfather->_left)
  {
      Node* uncle = grandfather->_right;
      // 叔节点存在且为红(颜色翻转)
      if (uncle && uncle->_col == RED)、
      {
          //    g
          //   p u
          //  c
          //变色
          parent->_col = uncle->_col = BLACK; // 父叔变黑
          grandfather->_col = RED; // 祖父变红

          //继续往上处理
          cur = grandfather;
          parent = cur->_parent;
      }
      // 叔节点不存在或为黑(需要旋转)
      else
      {
			// 当前节点是父的左孩子
          if (cur == parent->_left)
          {
              //     g
              //    p
              //   c
              RotateR(grandfather);
              parent->_col = BLACK; // 原父节点变黑
              grandfather->_col = RED; //祖父变红
          }
          // 当前节点是父的右孩子
          else
          {
              //     g
              //    p
              //     c
              RotateL(parent); // 先左旋父节点转换为情况二
              RotateR(grandfather);
              cur->_col = BLACK; //当前节点变黑
              grandfather->_col = RED; //祖父变红
          }
          break; // 旋转后子树平衡,退出循环
      }
  }
  // 父节点是祖父的右孩子
  else
  {
      //    g
      //   u p
      //      c
      Node* uncle = grandfather->_left;
      // 叔节点存在且为红
      if (uncle && uncle->_col == RED)
      {
          parent->_col = uncle->_col = BLACK;
          grandfather->_col = RED;

          cur = grandfather;
          parent = cur->_parent;
      }
      // 叔节点不存在或为黑
      else
      {
          // 当前节点是父的右孩子
          if (cur == parent->_right)
          {
              //     g
              //      p
              //       c
              RotateL(grandfather); // 左旋祖父节点
              parent->_col = BLACK;
              grandfather->_col = RED;
          }
          // 当前节点是父的左孩子
          else
          {
              //     g
              //       p
              //     c
              RotateR(parent); // 先右旋父节点转换为情况二
              RotateL(grandfather); // 再左旋祖父节点
              grandfather->_col = RED;
              cur->_col = BLACK;
          }
          break;
      }
  }
}
_root->_col = BLACK; //直接根变黑,不在需要考虑根的颜色问题

return true;
}

4.平衡检测🔎

//根节点到当前结点的黑色数量
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()
{
 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);
}

结尾👍

  以上便是红黑树的介绍和插入分析,如果有疑问或者建议都可以私信笔者交流,大家互相学习,互相进步!🌹

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/973131.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker+Dify部署DeepSeek-r1本地知识库

安装配置Docker Desktop 软件下载 Docker Desktop版本:4.38.0.181591 Docker Desktop下载地址:Docker: Accelerated Container Application Development 或者从这里下载:DockerDesktop-4.38.0.181591资源-CSDN文库 点击图下所示位置,下载windows-AMD64版本软件 启用Hy…

读书笔记:要点提炼《基于大模型的RAG应用开发与优化——构建企业级LLM应用》(严灿平)

文章目录 一、大模型基础与演进1.1 大模型时代与生成式 AI 爆发1.2 大模型应用的纵深演进及实际局限 二、RAG 基础概念与必要性2.1 RAG 的理论基础与应用动机2.2 简单 RAG 场景示例解析 三、RAG 应用技术架构3.1 经典架构与业务流程设计3.1.1 RAG 应用的整体流程与模块划分3.1.…

推荐几款较好的开源成熟框架

一. 若依&#xff1a; 1. 官方网站&#xff1a;https://doc.ruoyi.vip/ruoyi/ 2. 若依SpringBootVueElement 的后台管理系统&#xff1a;https://gitee.com/y_project/RuoYi-Vue 3. 若依SpringBootVueElement 的后台管理系统&#xff1a;https://gitee.com/y_project/RuoYi-Cl…

深入解析NoSQL数据库:从文档存储到图数据库的全场景实践

title: 深入解析NoSQL数据库:从文档存储到图数据库的全场景实践 date: 2025/2/19 updated: 2025/2/19 author: cmdragon excerpt: 通过电商、社交网络、物联网等12个行业场景,结合MongoDB聚合管道、Redis Stream实时处理、Cassandra SSTable存储引擎、Neo4j路径遍历算法等42…

Linux高并发服务器开发 第十九天(线程 进程)

目录 1.进程组和会话 2.守护进程 2.1守护进程daemon概念 2.2创建守护进程 3.线程 3.1线程的概念 3.2线程内核三级映射 3.3线程共享 3.4线程优缺点 4.线程控制原语 4.1获取线程id 4.2创建线程 4.3循环创建N个子线 4.4子线程传参地址&#xff0c;错误示例 4.5线程…

图论:tarjan 算法求解强连通分量

题目描述 有一个 n n n 个点&#xff0c; m m m 条边的有向图&#xff0c;请求出这个图点数大于 1 1 1 的强连通分量个数。 输入格式 第一行为两个整数 n n n 和 m m m。 第二行至 m 1 m1 m1 行&#xff0c;每一行有两个整数 a a a 和 b b b&#xff0c;表示有一条…

UE5.3 C++ TArray系列(一)

一.TArray概述 它们就相当于C动态数组Vector&#xff0c;但是被UE封装了&#xff0c;懂得都懂反射嘛&#xff0c;要不一不小心就被回收了。 它真的非常常见&#xff0c;我所用的容器中&#xff0c;它绝对排名第一&#xff0c;第二是TMap。 同类好理解&#xff0c;我平时也常用…

【微中子代理踩坑-前端node-sass安装失败】

微中子代理踩坑-前端node-sass安装失败-windows 1.npm版本2.python2.73.安装Visual Studio 1.npm版本 当前使用node版本13.12.0 2.python2.7 安装python2.7.9并配置环境变量 3.安装Visual Studio 安装Visual Studio 我是直接勾选了3个windows的sdk,然后就好了 最后 npm in…

一文了解PLM项目管理系统

PLM项目管理系统详解 PLM项目管理系统的定义和功能 PLM&#xff08;Product Lifecycle Management&#xff0c;产品生命周期管理&#xff09;项目管理系统是一种全面管理产品从概念设计、开发、制造、服务到最终退役全过程的软件系统。该系统通过集成产品数据、工作流程、人员…

Pycharm+CodeGPT+Ollama+Deepseek

首先&#xff0c;体验截图&#xff1a; 接着&#xff1a; 1、下载Ollama&#xff1a; Download Ollama on macOS 2、下载模型 以1.5b为例&#xff0c;打开命令行&#xff0c;输入: ollama run deepseek-r1:1.5b 3、Pycharm安装Code GPT插件 打开PyCharm&#xff0c;找到文…

Netty入门详解

引言 Netty 是一个基于 Java 的高性能、异步事件驱动的网络应用框架&#xff0c;用于快速开发可维护的高性能网络服务器和客户端。它提供了一组丰富的 API&#xff0c;使得开发人员能够轻松地处理各种网络协议&#xff0c;如 TCP、UDP 等&#xff0c;并且支持多种编解码方式&a…

【C++】优先级队列宝藏岛

> &#x1f343; 本系列为初阶C的内容&#xff0c;如果感兴趣&#xff0c;欢迎订阅&#x1f6a9; > &#x1f38a;个人主页:[小编的个人主页])小编的个人主页 > &#x1f380; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 > ✌️ &#x1f91e; &#x1…

【分布式理论11】分布式协同之分布式事务(一个应用操作多个资源):从刚性事务到柔性事务的演进

文章目录 一. 什么是分布式事务&#xff1f;二. 分布式事务的挑战三. 事务的ACID特性四. CAP理论与BASE理论1. CAP理论1.1. 三大特性1.2. 三者不能兼得 2. BASE理论 五. 分布式事务解决方案1. 两阶段提交&#xff08;2PC&#xff09;2. TCC&#xff08;Try-Confirm-Cancel&…

Linux-C/C++《C++/1、C++基础》(C++语言特性、面向对象等)

这里主要介绍概念为主&#xff0c;主要介绍 C与 C 语言中常用的不同点&#xff0c;和一些新的变化。其中不会去说指针、数据类型、变量类型、判断和循环等这些知识&#xff0c;这些和C 语言基本是一样使用的。我们主要学习 C的面向对象编程&#xff0c;对学习 Qt 有很大的帮助。…

我国首条大型无人机城际低空物流航线成功首航

首航震撼开场&#xff1a;羊肉 “飞” 越 540 公里 在夜色的笼罩下&#xff0c;榆阳马合通用机场的跑道上&#xff0c;一架大型固定翼无人机蓄势待发&#xff0c;机身被灯光照亮&#xff0c;宛如一只即将展翅翱翔的钢铁巨鸟。它的货舱里&#xff0c;满满装载着新鲜的榆林羊肉&a…

python-leetcode 38.翻转二叉树

题目&#xff1a; 给定一个二叉树的根节点root,检查它是否轴对称。 方法一&#xff1a;递归 如果一个树的左子树与右子树镜像对称&#xff0c;那么这个树是对称的。 互为镜像的条件&#xff1a;他们的两个根结点具有相同的值&#xff0c;每棵树的右子树都与另一个树的左子树…

锐浪报表 Grid++Report 通用软件,通用模板个性打印

为提高软件打印报表的适应性&#xff0c;再软件中&#xff0c;使用统一的模板&#xff0c;实现个性化的调整。实现不同用的需求&#xff0c;本人作了以下尝试&#xff1a; 一、表头的选择 二、明细表格的高度的调整 // 明细表标题行高GridppReport_7.DetailGrid.ColumnTitle.H…

Plant Simulation培训教程-AGV配送物流仿真模块

原创 知行 天理智能科技 2024年12月31日 07:00 浙江 又到年终盘点的时候了&#xff0c;在这里我把之前录制的Plant Simulation培训教程-AGV配送物流仿真模块分享出来&#xff0c;有需要的可以直接联系我。 模型路径通过Marker点搭建&#xff0c;适用于磁条导航、二维码导航等…

【Python 专题】数据结构 树

LeetCode 题目104. 二叉树的最大深度(gif 图解)方法一:后序遍历(DFS)方法二:层序遍历(BFS)872. 叶子相似的树(DFS 遍历)1448. 统计二叉树中好节点的数目(DFS 遍历)437. 路径总和 III(前缀和 + DFS 回溯)1372. 二叉树中的最长交错路径(DFS)236. 二叉树的最近公共…

基于射频开关选择的VNA校准设计

活动发起人小虚竹 想对你说&#xff1a; 这是一个以写作博客为目的的创作活动&#xff0c;旨在鼓励大学生博主们挖掘自己的创作潜能&#xff0c;展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴&#xff0c;那么&#xff0c;快来参加吧&#xff01…