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

news2025/2/22 11:51:55

【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);
}

结尾👍

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何修改Windows系统Ollama模型存储位置

默认情况下&#xff0c;Ollama 模型会存储在 C 盘用户目录下的 .ollama/models 文件夹中&#xff0c;这会占用大量 C 盘空间&#xff0c;增加C盘“爆红”的几率。所以&#xff0c;我们就需要修改Ollama的模型存储位置 Ollama提供了一个环境变量参数可以修改Ollama的默认存在位…

OpenAI ChatGPT在心理治疗领域展现超凡同理心,通过图灵测试挑战人类专家

近期&#xff0c;一项关于OpenAI ChatGPT在心理治疗领域的研究更是引起了广泛关注。据报道&#xff0c;ChatGPT已经成功通过了治疗师领域的图灵测试&#xff0c;其表现甚至在某些方面超越了人类治疗师&#xff0c;尤其是在展现同理心方面&#xff0c;这一发现无疑为AI在心理健康…

Netflix Ribbon:云端负载均衡利器

Netflix Ribbon&#xff1a;云端负载均衡利器 ribbon Ribbon is a Inter Process Communication (remote procedure calls) library with built in software load balancers. The primary usage model involves REST calls with various serialization scheme support. 项目地…

【Android】Android 悬浮窗开发 ( 动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

文章目录 一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后返回处理 二、悬浮窗 前台服务和通知1、前台服务 启动 悬浮窗 的必要性① 保持悬浮窗存活② 悬浮窗的要求③ 悬浮窗版本兼容 2、其它类型服务简介① 前台服务…

Python高级语法之jsonpathBeautifulSoup解析器

目录&#xff1a; 1、jsonPath的使用2、使用jsonpath解析淘票票网页3、BeautifulSoup解析器的使用4、BeautifulSoup层级选择器的使用 1、jsonPath的使用 2、使用jsonpath解析淘票票网页 3、BeautifulSoup解析器的使用 4、BeautifulSoup层级选择器的使用

工业安卓主板在智慧粮仓设备中发挥着至关重要的作用

工业安卓主板在智慧粮仓设备中发挥着至关重要的作用。以下是关于其作用的具体分析&#xff1a; 一、提供稳定可靠的运行平台 智慧粮仓设备需要长时间稳定运行&#xff0c;以实现对粮食储存环境的实时监测和精准控制。工业安卓主板采用高性能的处理器和大容量的存储空间&#…

ECMAScript6----var、let、const

ECMAScript6----var、let、const 1.var2.let3.const 1.var &#xff08;1&#xff09;在相同作用域下可重复声明 var a 20 var a 30 console.log(a) // 30&#xff08;2&#xff09;存在变量提升 console.log(a) // undefined var a 20&#xff08;3&#xff09;可修改声…

【ST-LINK未能被keil识别STM32 ST-LINK Utility出现“Can not connect to target】

针对各种品牌32MCU boot0拉高&#xff0c;boot1拉低进入系统存储器&#xff0c;对Flash先擦除在下载 针对STM32f103 通过32复位和stlink Utilit解决 https://blog.csdn.net/Donglutao/article/details/129086960 https://www.bilibili.com/video/BV1F94y1g7be/?spm_id_…

Android Http-server 本地 web 服务

时间&#xff1a;2025年2月16日 地点&#xff1a;深圳.前海湾 需求 我们都知道 webview 可加载 URI&#xff0c;他有自己的协议 scheme&#xff1a; content:// 标识数据由 Content Provider 管理file:// 本地文件 http:// 网络资源 特别的&#xff0c;如果你想直接…

python实践-实现实时语音转文字本地部署版(二)

一、技术栈 python 3.10.6 vosk 需下载对应模型&#xff08;vosk-model-cn-0.22&#xff09;模型下载慢的同学看最后的资源链接。 pyaudio keyboard 二、实现功能 本地化实现麦克风语音录入&#xff0c;实时生成文字&#xff0c;并保存至本地文档。 三、实现代码 fro…

tortoiseSVN 如何克隆项目到本地

导入项目成功&#xff0c;如下图&#xff1a;

解决“QString的split()函数分割中文“报错

在使用Qt平台的QString类里的split()函数&#xff0c;分割.txt文件里中文的字符串时&#xff0c;发现中文会乱码。     问题原因&#xff1a;中文使用UTF-16编码。     解决方法&#xff1a;将.txt文件保存为UTF-16编码&#xff0c;然后使用split()去分割对应的字符串即可。…

云平台结合DeepSeek的AI模型优化实践:技术突破与应用革新

目录 前言 一、技术架构&#xff1a;算力与算法的协同基石 1. 蓝耘平台的核心优势 2. DeepSeek的模型创新 二、应用场景&#xff1a;垂直领域的智能化落地 1. 商业领域&#xff1a;智能推荐与客服 2. 工业领域&#xff1a;质检与流程优化 3. 智慧城市与医…

蓝桥杯(B组)-每日一题(1093字符逆序)

c中函数&#xff1a; reverse(首位置&#xff0c;尾位置&#xff09; reverse(s.begin(),s.end()) 头文件&#xff1a;<algorithm> #include<iostream> #include<algorithm>//运用reverse函数的头文件 using namespace std; int main() {string s;//定义一…

jsherp importItemExcel接口存在SQL注入

一、漏洞简介 很多人说管伊佳ERP&#xff08;原名&#xff1a;华夏ERP&#xff0c;英文名&#xff1a;jshERP&#xff09;是目前人气领先的国产ERP系统虽然目前只有进销存财务生产的功能&#xff0c;但后面将会推出ERP的全部功能&#xff0c;有兴趣请帮点一下 二、漏洞影响 …

一文讲清 AIO BIO NIO的区别

引言 在 Java 编程中&#xff0c;BIO&#xff08;Blocking I/O&#xff09;、NIO&#xff08;Non-blocking I/O&#xff09;和 AIO&#xff08;Asynchronous I/O&#xff09;是三种不同的 I/O 模型&#xff0c;它们在处理输入输出操作时有着不同的机制和特点&#xff0c;但是市…

文心一言大模型的“三级跳”:从收费到免费再到开源,一场AI生态的重构实验

2025年2月&#xff0c;百度文心大模型接连抛出两枚“重磅炸弹”&#xff1a;4月1日起全面免费&#xff0c;6月30日正式开源文心大模型4.5系列。这一系列动作不仅颠覆了李彦宏此前坚持的“闭源优势论”13&#xff0c;更标志着中国AI大模型竞争进入了一个全新的阶段——从技术壁垒…

Uniapp 从入门到精通:基础篇 - 搭建开发环境

Uniapp 从入门到精通:基础篇 - 搭建开发环境 前言一、Uniapp 简介1.1 什么是 Uniapp1.2 Uniapp 的优势二、搭建开发环境前的准备2.1 安装 Node.js2.2 安装 HBuilderX三、创建第一个 Uniapp 项目3.1 打开 HBuilderX 并创建项目3.2 项目结构介绍3.3 运行项目四、配置项目4.1 配置…

CSDN文章质量分查询系统【赠python爬虫、提分攻略】

CSDN文章质量分查询系统 https://www.csdn.net/qc 点击链接-----> CSDN文章质量分查询系统 <------点击链接 点击链接-----> https://www.csdn.net/qc <------点击链接 点击链接-----> CSDN文章质量分查询系统 <------点击链接 点击链…

GPT-SoVITS更新V3 win整合包

GPT-SoVITS 是由社区开发者联合打造的开源语音生成框架&#xff0c;其创新性地融合了GPT语言模型与SoVITS&#xff08;Singing Voice Inference and Timbre Synthesis&#xff09;语音合成技术&#xff0c;实现了仅需5秒语音样本即可生成高保真目标音色的突破。该项目凭借其开箱…