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

文章目录
- 前言
- 🚀一、红黑树的介绍
- 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年真题练习中,对错题模棱两可的考点进行重点记录与内容延申。…...
OpenClaw技能开发入门:为千问3.5-27B编写自定义模块
OpenClaw技能开发入门:为千问3.5-27B编写自定义模块 1. 为什么需要自定义技能? 去年冬天,我发现自己每天早晨都要手动查询天气并发送给家人。重复的操作让我开始思考:能否让OpenClaw帮我自动完成这个任务?这就是我踏…...
为什么选择Sammy.js:轻量级JavaScript框架的终极优势解析
为什么选择Sammy.js:轻量级JavaScript框架的终极优势解析 【免费下载链接】sammy Sammy is a tiny javascript framework built on top of jQuery, Its RESTful Evented Javascript. 项目地址: https://gitcode.com/gh_mirrors/sa/sammy 在当今前端开发领域&…...
FLUX.1-dev创作实战:从输入文案到生成图片,完整流程一次跑通
FLUX.1-dev创作实战:从输入文案到生成图片,完整流程一次跑通 1. 认识FLUX.1-dev:新一代AI图像生成引擎 FLUX.1-dev是Black Forest Labs推出的开源AI图像生成模型,以其出色的真实感和高效生成能力在开发者社区中广受好评。与常见…...
如何使用4个经过验证的技巧将Android联系人备份到Mac
联系人无疑是我们智能手机上最重要的数据。一旦失去联系,我们就会与这个世界上最亲爱的人失去联系;也许他们是家人、爱人、朋友、同学、同事、学生等。因此,联系人备份对我们来说非常重要。与将iPhone联系人备份到Mac相对容易不同,…...
PyTorch 2.8镜像实操手册:Git+vim+htop+screen开发运维一体化工作流
PyTorch 2.8镜像实操手册:Gitvimhtopscreen开发运维一体化工作流 1. 镜像概述与环境准备 PyTorch 2.8深度学习镜像是一个为专业开发者打造的全功能工作环境,基于RTX 4090D 24GB显卡和CUDA 12.4进行了深度优化。这个镜像不仅预装了最新版的PyTorch框架&…...
intv_ai_mk11步骤详解:从curl验证到浏览器交互,完整闭环操作演示
intv_ai_mk11步骤详解:从curl验证到浏览器交互,完整闭环操作演示 1. 模型概述与核心能力 intv_ai_mk11是基于Llama架构的中等规模文本生成模型,专为通用文本处理任务优化。这个开箱即用的解决方案特别适合以下场景: 智能问答系…...
Flux Sea Studio 极限测试:生成8K超高清巨幅海景壁纸的技术挑战与实现
Flux Sea Studio 极限测试:生成8K超高清巨幅海景壁纸的技术挑战与实现 最近在折腾AI生成图片,发现一个挺有意思的挑战:用Flux Sea Studio这类模型,能不能做出那种能铺满整块大屏幕的、细节拉满的8K超高清壁纸?特别是海…...
【ESP32-S3】通过ROS2使用YDLIDAR X2进行SLAM、自主导航方案选择
通过ROS2使用YDLIDAR X2进行SLAM、自主导航方案选择背景一、方案总览(两种主流实现)方案A:纯透传(最简,推荐入门)方案B:Micro-ROS(标准ROS 2架构,适合完整导航࿰…...
学历作为硬实力:当代中国权力结构中知识资本的制度化逻辑与社会地位再生产机制
学历作为硬实力:当代中国权力结构中知识资本的制度化逻辑与社会地位再生产机制 作者:培风图南以星河揽胜 专栏链接:澄心观道 字数:约 14,200 字 | 阅读时长:约 52 分钟 引言:一个被广泛观察却少有深究的社会…...
RAGFlow与Dify共存方案:同一台Win11机器如何用Docker隔离部署
RAGFlow与Dify共存方案:同一台Win11机器如何用Docker隔离部署 在AI应用开发领域,RAGFlow和Dify作为两款热门工具,分别擅长知识库构建和AI应用编排。许多开发者面临一个现实挑战:如何在本地开发环境中同时运行这两个系统࿱…...
