红黑树的平衡之舞:数据结构中的优雅艺术

文章目录
- 前言
- 🚀一、红黑树的介绍
- 1.1 红黑树的概念
- 1.2 红黑树的特点
- 1.3 红黑树的性质
- 🚀二、红黑树结点的定义
- 🚀三、红黑树的框架
- 🚀四、旋转操作
- 🚀五、红黑树的插入操作
- 5.1 `uncle`结点存在且为红
- 5.2 `uncle`结点不存在或者存在且为黑
- 5.3 插入操作的全部代码
- 5.4 测试插入
- 🚀六、AVL树和红黑树的性能比较
- 6.1 平衡性
- 6.2 查找操作
- 6.3 插入和删除操作
- 6.4 内存使用
- 6.5 使用场景
- 结语
前言
继上篇在平衡中追寻高度:探秘AVL树的自我调节之美,我们继续讨论红黑树的相关知识。
在计算机科学的广阔天地中,数据结构如同乐器,各具特色,共同奏响高效算法的乐章。在众多自平衡树中,红黑树以其独特的结构与高效的性能,成为了实现平衡的典范。本博文将深入探讨红黑树的原理、特点及其在实际应用中的表现,揭示这一数据结构如何在动态数据操作中保持高效与稳定。
🚀一、红黑树的介绍
1.1 红黑树的概念
红黑树是一种自平衡的二叉搜索树,其中每个节点都被标记为红色或黑色。通过这些颜色标记以及特定的规则,红黑树能够在插入和删除节点时保持平衡,从而确保基本操作的效率。
1.2 红黑树的特点
- 节点颜色:每个节点可以是红色或黑色。
- 根节点:树的根节点始终是黑色。
- 叶子节点:所有叶子节点(空节点)都是黑色。
- 红色节点限制:任何红色节点的子节点必须是黑色,避免连续的红色节点。
- 黑色高度:从任何节点到其每个叶子节点的所有路径都必须包含相同数量的黑色节点。

1.3 红黑树的性质
- 接近平衡:红黑树保证了从根到叶子节点的最长路径不会超过最短路径的两倍,因此树是接近平衡的。
- 时间复杂度:
- 查找:O(log n)
- 插入:O(log n)
- 删除:O(log n)
- 自平衡:在插入和删除操作后,红黑树通过旋转和重新着色来保持上述性质,从而确保树的高度尽量低。
这些特性使得红黑树在需要动态数据存储和高效查找的应用中非常有用,比如标准库中的std::map和std::set。
🚀二、红黑树结点的定义
template<class K, class V>
class RBTreeNode {
public:RBTreeNode<K, V>* left;RBTreeNode<K, V>* right;RBTreeNode<K, V>* _parent;pair<K, V> _kv; //结点中存的键值对color _col; //结点的颜色RBTreeNode(const& pair<K, V> kv = pair<K, V>(), Color color = RED):_kv(kv),left(nullptr),right(nullptr),_parent(nullptr),_col(color){}};
- 新节点默认颜色是
RED。这是因为,如果新插入结点的颜色是BLACK,那意味着当前路径上新增了一个黑色结点,为了保证红黑树的第5条性质,我们要对这颗红黑树其他的所有路径进行检查,可见新插入结点如果默认是BLACK,会存在着牵一发而动全身的影响。而让新插入结点默认是RED则不会出现这样的结果。假如新插入结点的parent恰好是BLACK,那这次插入就没有什么问题。如果新插入结点是parent是RED,此时需要对这颗红黑树稍作调整。
🚀三、红黑树的框架
template<class K, class V>
class RBTree {typedef RBTreeNode<K, V> Node;
public:// 成员函数...
private:Node* _root;
};
🚀四、旋转操作
- 这里我们只举例2种情况,一种是左单旋,另一种是右单旋,另外两种双旋可以观看我的前文。
// 左单旋
void RotateL(Node* parent) {Node* cur = parent->right;Node* curleft = cur->left;parent->right = curleft; cur->left = parent;if (curleft) curleft->_parent = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if (parent == _root) {_root = cur;cur->_parent = nullptr; }else {if (ppnode->left == parent) ppnode->left = cur;else ppnode->right = cur;cur->_parent = ppnode;}
}// 右单旋
void RotateR(Node* parent) {Node* cur = parent->left;Node* curright = cur->right;parent->left = curright;cur->right = parent;if (curright) curright->_parent = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if (parent == _root) {_root = cur;cur->_parent = nullptr;}else {if (ppnode->left == parent) ppnode->left = cur;else ppnode->right = cur;cur->_parent = ppnode;}
}
🚀五、红黑树的插入操作
红黑树是在二叉搜索树的基础上加上平衡限制条件,因此红黑树的插入可以分为两步:
-
按照二叉搜索树的规则插入结点。
-
检测新节点插入后,红黑树的性质是否遭到破坏。
因为新结点的默认颜色是 RED,因此:如果其双亲结点的颜色是 BLACK,没有违反红黑树任何性质,则不需要调整;但是当新插入节点的双亲结点颜色为 RED 时,就违反了性质四不能有连在一起的红色结点,此时需要对红黑树分情况来讨论:
约定:cur为当前结点,parent 为父结点,grandfather 为祖父结点,uncle 为叔叔结点。如果 parent 为红那 grandfather 一定为黑。所以当前唯一不确定的就是 uncle,主要分以下三种情况:
5.1 uncle结点存在且为红
- 由于每条路径上的黑色结点的时候,连带着
uncle和grandfather结点也要一起更新。 - 根据规则,红色结点不能连续,故需要往上继续更新。





5.2 uncle结点不存在或者存在且为黑





5.3 插入操作的全部代码
bool insert(const pr& kv) {if (!_root) {_root = new Node(kv);_root->_col = BLACK;return true;}// 定义当前节点curNode* cur = _root;Node* parent = nullptr;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; // 不可能出现相等的情况插入}// 创建新的Node实例cur,并将它插入到适当的位置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* grandfather = parent->_parent;// 父亲是祖父的左孩子if (grandfather->left == parent) {// 叔叔存在且为红if (uncle && uncle->_col == RED) {parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上处理cur = grandfather;parent = cur->_parent;}// 叔叔不存在或者存在且为黑else {if (cur == parent->left) {RotateR(grandparent);parent->_col = BLACK;grandfather->_col = RED;}else {RotateL(cur);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}}}// 父亲是祖父的右孩子else {// 叔叔存在且为红if (uncle && uncle->_col == RED) {parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上处理cur = grandfather;parent = cur->_parent;}// 叔叔不存在或者存在且为黑else {if (cur == parent->right) {RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else {RotateR(cur);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}}}}return true;
}
5.4 测试插入
红黑树的检测分为两步:
- 检测其是否满足二叉搜索树(中序遍历是否为有序序列)。
- 检测其是否满足红黑树的性质(主要是性质四和性质五)。
成员函数:
// 调用判断平衡的函数
bool IsBalance() {return IsBalance(_root);
}// 判断平衡
bool IsBalance(Node* root) {if (!root) return true;if (root->_col != BLACK) return false;// 随便找一条路径,这里我们选择最左路径int benchmark = 0;Node* cur = _root;while (cur) {if (cur->_col == BLACK) {++benchmark;}cur = cur->left;}return CheckColor(root, 0, benchmark);
}// 主要检查有无连续的红色结点
bool CheckColor(Node* root, int blacknum, int benchmark) {if (!root) {if (blacknum != benchmark) {return false;}return true;}if (root->_col == BLACK) ++blacknum;if (root->_col == RED && root->_parent && root->_parent->_col == RED) {cout << root->_kv.first << "出现连续红色节点" << endl;return false;}return CheckColor(root->left, blacknum, benchmark) && CheckColor(root->right, blacknum, benchmark);
}// 调用中序遍历
void InOrder() {return InOrder(_root);
}// 中序遍历
void InOrder(Node* root) {if (!root) return;InOrder(root->left);cout << root->_kv.first << " ";InOrder(root->right);
}
测试代码:
#include "RBTree.h"int main() {int a[] = { 1, 5, 7, 16, 25, 36, 44, 55,6, 32, 9 };RBTree<int, int> rbt;for (auto e : a) {rbt.insert(make_pair(e,e));rbt.InOrder();cout << endl;if (rbt.IsBalance()) {cout << "插入成功" << endl;}else {cout << "插入失败" << endl;}}return 0;
}

🚀六、AVL树和红黑树的性能比较
AVL树和红黑树是两种常用的自平衡二叉搜索树,各有优缺点。以下是它们在性能方面的比较:
6.1 平衡性
- AVL树:始终保持严格平衡,任何节点的两个子树高度差不超过1。这使得AVL树在查找操作时的高度更小,查找速度较快。
- 红黑树:相对宽松的平衡条件,通过颜色属性和旋转操作维护平衡。虽然高度比AVL树大,但仍然保持在O(log n)的范围内。
6.2 查找操作
- AVL树:查找操作效率更高,时间复杂度为O(log n),因为树的高度较小。
- 红黑树:查找操作的时间复杂度同样为O(log n),但由于可能较高的树高度,平均查找速度稍慢。
6.3 插入和删除操作
- AVL树:插入和删除后,树可能需要更多的旋转来恢复平衡,因此插入和删除的时间复杂度为O(log n),但常数因子较高。
- 红黑树:插入和删除操作相对简单,所需的旋转次数较少,性能上通常更快,时间复杂度为O(log n)。
6.4 内存使用
- AVL树:每个节点需要存储额外的平衡因子,内存开销相对较大。
- 红黑树:每个节点需要存储颜色信息,内存开销相对较小。
6.5 使用场景
- AVL树:适用于需要频繁查找操作的场景,如数据库索引等。
- 红黑树:适用于插入和删除操作较为频繁的场景,如 STL 中的
std::map和std::set实现。
总结
- AVL树更适合查找频繁的场合,提供较快的查找速度。
- 红黑树则在插入和删除性能上表现更好,适合对这些操作有较高频率的场景。
结语
红黑树不仅是一种数据结构,更是一种智慧的象征。它巧妙地平衡了插入、删除与查找操作之间的矛盾,为开发者在处理复杂数据时提供了强大的支持。通过对红黑树的深入理解,我们不仅能提升自身的编程能力,更能在数据处理的艺术中,找到更为优雅的解决方案。希望本篇博文能够为你在数据结构的探索之路上,带来新的启发与思考。

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!

相关文章:
红黑树的平衡之舞:数据结构中的优雅艺术
文章目录 前言🚀一、红黑树的介绍1.1 红黑树的概念1.2 红黑树的特点1.3 红黑树的性质 🚀二、红黑树结点的定义🚀三、红黑树的框架🚀四、旋转操作🚀五、红黑树的插入操作5.1 uncle结点存在且为红5.2 uncle结点不存在或者…...
angular实现list列表和翻页效果
说明:angular实现list列表和翻页效果 上一页 当前页面 下一页 效果图: step1: E:\projectgood\ajnine\untitled4\src\app\car\car.component.css .example-form-fields {display: flex;align-items: flex-start; }mat-list-item{background: antiquew…...
闯关leetcode——3285. Find Indices of Stable Mountains
大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/find-indices-of-stable-mountains/description/ 内容 There are n mountains in a row, and each mountain has a height. You are given an integer array height where height[i] represents t…...
算法【Java】—— 动态规划之斐波那契数列模型
动态规划 动态规划的思路一共有五个步骤: 状态表示:由经验和题目要求得出,这个确实有点抽象,下面的题目会带大家慢慢感受状态标识状态转移方程初始化:避免越界访问 dp 表,所以在进行填表之前我们要预先填…...
idea连接docker并构建镜像
安装docker 安装docker idea连接docker 安装docker插件 设置docker连接 设置docker.exe 这个docker.exe是为了运行docker,可以通过安装docker desktop获取 docker desktop下载地址 右键图标找到文件位置 在同级的resource中 编写Dockerfile # 使用官方 Nginx…...
百度如何打造AI原生研发新范式?
👉点击即可下载《百度AI原生研发新范式实践》资料 2024年10月23-25日,2024 NJSD技术盛典暨第十届NJSD软件开发者大会、第八届IAS互联网架构大会在南京召开。本届大会邀请了工业界和学术界的专家,优秀的工程师和产品经理,以及其它行…...
RedisTemplate类中的常用方法粗解(简单明了,预计5分钟看完)
在阅读项目代码过程中发现引用RedisTemplate 的方法操作redis时,都会有一些特定的ops ,对此好奇就查资料的情况下有了本博客。 操作之前付一张我们项目中的用到的地方的图 另外本文中的语言用到的是Java,附上试验用到的redisTemplete依赖 <…...
鸿蒙ArkTS中的布局容器组件(Column、Row、Flex、 Stack、Grid)
在鸿蒙ArkTS中,布局容器组件有很多,常见的有: ⑴ Column:(垂直布局容器):用于将子组件垂直排列。 ⑵ Row:(水平布局容器):用于将子组件水…...
显存占用 显存测试
目录 显存测试 显存占用示例 一个模型多卡占用 显存测试 import torch# 计算张量的大小(例如:每个 float 占用 4 字节) # 40GB 40 * 1024 * 1024 * 1024 字节 # 每个 float 4 字节,因此需要的 float 数量为 (40 * 1024 * 1024…...
快速入门CSS
欢迎关注个人主页:逸狼 创造不易,可以点点赞吗 如有错误,欢迎指出~ 目录 CSS css的三种引入方式 css书写规范 选择器分类 标签选择器 class选择器 id选择器 复合选择器 通配符选择器 color颜色设置 border边框设置 width/heigth 内/外边距 C…...
AcWing 1073 树的中心 树形dp (详解)
这道题目非常有新意,在过去,我们通常先访问子节点去更新父节点的状态,但是这道题我们还需要从父节点去更新子节点。 我们可以想象为向上和向下两个方向,我们任取一点,先向下走,再回来更新上面的点…...
modelscope下载Qwen2.5 72B 模型方法
conda create -n modelscope python=3.10 conda activate modelscopepip install modelscope执行这个python代码: from modelscope.hub.snapshot_download import snapshot_download# 下载模型到当前路径 model_dir = snapshot_download(...
重学SpringBoot3-整合 Elasticsearch 8.x (二)使用Repository
更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 整合 Elasticsearch 8.x (二)使用Repository 1. 环境准备1.1 项目依赖1.2 Elasticsearch 配置 2. 使用Repository的基本步骤2.1 创建实体类2.2 创…...
为什么说模拟电路的难点就在开通过程和关断过程?难在什么地方?
模拟电路中开通过程和关断过程之所以困难,主要有以下几个方面的原因: 1. 瞬态响应特性复杂 - 在开通和关断瞬间,电路中的电流和电压会发生快速变化,产生复杂的瞬态响应。这些瞬态响应可能包含过冲、下冲、振铃等现象,…...
CubeIDE BUG-project‘hello‘has no explict encoding set hello
projecthellohas no explict encoding set hello 解决方法: 点击红色处注册账号后登录,删除原本文件后重新生成即可。...
在线PDF转图片网站
https://www.ilovepdf.com/download/qgxkmbzgxt6yb3s8l9f7fc3q9606hq0bfh4b33mnrf3p7tp8l0d4qy386b5dxqwjbhq7j3j4tp20m4dnb89wA9jjw25br1gtAw42l0m1sq047nvld4qqrm8kzjplkAhw9458p4wjgbmn08g49l23c1khsggdx4A7z3m9xh19mgzdlllyA6r1/52 在线excel转图片 https://www.zamzar.c…...
ps和top的区别
时间上的区别: ps是静态查看进程而top是动态持续监控进程 功能上的区别 ps只是查看进程,top还可以监视系统性能,如平均负载,cpu和内存的消耗 ps 常用格式:ps -ef (ef简洁aux详细 System V风格和BSD 风格) | grep P…...
自动驾驶上市潮中,会诞生下一个“英伟达”吗?
站上科技创新潮头的企业总是备受资本青睐。20世纪开始,从IT到互联网,IBM、英特尔、微软、苹果等各大科技巨头,你方唱罢我登场。 近几年,人工智能成为资本市场新传奇故事的孕育之地。今年10月,英伟达市值首度突破3.5万…...
CSS 计数器:深入解析与高级应用
CSS 计数器:深入解析与高级应用 CSS 计数器是前端开发中一个强大但经常被忽视的功能。它们允许开发者创建和管理自定义的计数序列,这在处理复杂文档结构时尤其有用。本文将深入探讨 CSS 计数器的原理、用法,并展示一些高级应用示例。 什么是…...
【真题笔记】15年系统架构设计师要点总结
【真题笔记】15年系统架构设计师要点总结 分布式数据库中各种透明RAID 5IPv6 IPv4电子商务系统项目配置管理IPO图(输入加工输出图)桥接模式的UML图面向对象设计原则软件测试 在15年真题练习中,对错题模棱两可的考点进行重点记录与内容延申。…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
