在数据结构领域,二叉搜索树(BST)凭借 O (log n) 的平均时间复杂度成为查找、插入和删除操作的优选结构。但它有个致命缺陷:当输入数据有序时,会退化为链表,时间复杂度骤降至 O (n)。为解决这一问题,计算机科学家设计了多种自平衡二叉搜索树,红黑树(Red-Black Tree)便是其中应用最广泛的一种。它通过巧妙的着色规则和有限的旋转操作,在保证平衡的同时将维护成本降到最低,成为 STL 容器、Linux 内核等工业级系统的核心数据结构。本文将从底层原理到工程实践,全面剖析红黑树的工作机制。

一、红黑树的核心特性与平衡原理

红黑树是一种自平衡二叉搜索树,其每个节点除了存储键值、左右子节点和父节点指针外,还额外包含一个颜色属性(红色或黑色)。通过严格遵循以下 5 条性质,红黑树实现了结构平衡:

  1. 颜色约束:每个节点要么是红色,要么是黑色。
  2. 根节点规则:根节点必须是黑色。
  3. 叶子节点规则:所有叶子节点(NIL 节点,即空节点)都是黑色。
  4. 连续红色禁止:如果一个节点是红色,则它的两个子节点必须是黑色(不存在连续的红色节点)。
  5. 黑色平衡规则:从任意节点到其所有后代叶子节点的路径中,包含的黑色节点数量相同(称为 "黑高")。

平衡原理深度解析

这些特性共同构建了红黑树的 "平衡能力":

  • 性质 4 避免了 "红色链" 的无限延伸,限制了树的倾斜程度;
  • 性质 5 保证了所有路径的 "黑高" 一致,而红色节点仅能穿插在黑色节点之间;
  • 由此可推导出关键结论:红黑树中最长路径的长度不会超过最短路径的 2 倍(最短路径全为黑色节点,最长路径为黑红交替)。

这一结论直接确保了红黑树的高度始终为 O (log n),从而保证了所有操作的最坏时间复杂度为 O (log n)。

二、红黑树的节点结构与基础定义

2.1 节点结构设计

红黑树的节点需要存储 5 类信息:键值、颜色、左子节点、右子节点和父节点。为简化边界条件处理(如空节点的颜色判断),通常将所有空节点统一指向一个哨兵节点(NIL 节点),该节点永久为黑色,且左右子节点和父节点均指向自身。

enum Color { RED, BLACK };// 节点结构定义
struct Node {int key;          // 键值Color color;      // 节点颜色Node *left;       // 左子节点Node *right;      // 右子节点Node *parent;     // 父节点// 构造函数:新节点默认红色(插入时优化)Node(int k) : key(k), color(RED), left(nullptr), right(nullptr), parent(nullptr) {}
};// 哨兵节点定义(全局唯一)
Node *NIL_NODE = new Node(0);
NIL_NODE->color = BLACK;
NIL_NODE->left = NIL_NODE;
NIL_NODE->right = NIL_NODE;
NIL_NODE->parent = NIL_NODE;

2.2 关键设计细节

  • 哨兵节点的作用:将所有空指针替换为 NIL_NODE,避免在操作中频繁判断nullptr,统一边界处理逻辑;
  • 新节点默认红色:插入红色节点仅可能违反性质 4(连续红色),而插入黑色节点会直接违反性质 5(黑高不一致),前者修复成本更低;
  • 父节点指针的必要性:红黑树的旋转和修复操作需要回溯到祖先节点,必须通过父指针实现向上遍历。

三、红黑树的核心操作:插入与修复

3.1 插入流程

红黑树的插入操作分为两步:

  1. 按 BST 规则插入:找到合适位置插入新节点(默认红色);
  2. 修复红黑树性质:通过旋转和变色消除对红黑树性质的破坏。
插入核心代码(BST 部分
void insert(Node *&root, int key) {Node *z = new Node(key);Node *y = NIL_NODE;  // 记录x的父节点Node *x = root;// 查找插入位置while (x != NIL_NODE) {y = x;if (z->key < x->key) {x = x->left;} else {x = x->right;}}// 确定新节点的父节点z->parent = y;if (y == NIL_NODE) {root = z;  // 树为空,新节点成为根} else if (z->key < y->key) {y->left = z;} else {y->right = z;}// 初始化新节点的子节点为哨兵z->left = NIL_NODE;z->right = NIL_NODE;z->color = RED;  // 新节点默认红色// 修复红黑树性质insertFixup(root, z);
}

3.2 插入后的修复操作(insertFixup)

插入红色节点后,可能违反的性质为:

  • 性质 2(根节点为红色):仅当插入的是第一个节点时可能发生;
  • 性质 4(连续红色节点):当父节点为红色时发生。

修复过程通过循环处理,直到上述性质均被满足。根据 "叔节点"(父节点的兄弟节点)的颜色,分为 3 种情况:

情况 1:叔节点为红色
  • 场景:新节点(z)的父节点(p)为红色,叔节点(u)为红色;
  • 破坏性质:仅可能违反性质 4(连续红色);
  • 修复逻辑
    1. 将父节点(p)和叔节点(u)改为黑色;
    2. 将祖父节点(g)改为红色;
    3. 令 z = g,继续向上修复(祖父节点可能与它的父节点形成连续红色)。
// 情况1:叔节点为红色
case 1:z->parent->parent->left->color = BLACK;  // 叔节点变黑z->parent->parent->right->color = BLACK; // 父节点变黑z->parent->parent->color = RED;          // 祖父节点变红z = z->parent->parent;                   // 向上追溯break;
情况 2:叔节点为黑色,且新节点是右孩子
  • 场景:父节点(p)为红色,叔节点(u)为黑色,z 是右孩子;
  • 破坏性质:性质 4(连续红色);
  • 修复逻辑
    1. 对父节点(p)执行左旋,将 z 转为左孩子;
    2. 转化为情况 3 处理(统一修复逻辑)。
// 情况2:叔节点为黑色,z是右孩子
case 2:z = z->parent;              // z指向父节点leftRotate(root, z);        // 左旋父节点// 转化为情况3
情况 3:叔节点为黑色,且新节点是左孩子
  • 场景:父节点(p)为红色,叔节点(u)为黑色,z 是左孩子;
  • 破坏性质:性质 4(连续红色);
  • 修复逻辑
    1. 对祖父节点(g)执行右旋;
    2. 交换父节点(p)和祖父节点(g)的颜色;
    3. 修复完成(无需继续向上追溯)。
// 情况3:叔节点为黑色,z是左孩子
case 3:z->parent->color = BLACK;   // 父节点变黑z->parent->parent->color = RED;  // 祖父节点变红rightRotate(root, z->parent->parent);  // 右旋祖父节点z = root;  // 退出循环break;

3.3 旋转操作的实现

旋转是红黑树维护平衡的核心手段,分为左旋和右旋,作用是改变节点间的父子关系而不破坏 BST 性质。

左旋操作(leftRotate)
void leftRotate(Node *&root, Node *x) {Node *y = x->right;  // y是x的右子节点x->right = y->left;  // 将y的左子树转为x的右子树if (y->left != NIL_NODE) {y->left->parent = x;  // 更新y左子树的父节点}y->parent = x->parent;  // 更新y的父节点// 处理x是根节点的情况if (x->parent == NIL_NODE) {root = y;} else if (x == x->parent->left) {x->parent->left = y;  // x是左孩子时,y替代x的位置} else {x->parent->right = y; // x是右孩子时,y替代x的位置}y->left = x;  // x成为y的左孩子x->parent = y;  // 更新x的父节点
}
右旋操作(rightRotate)

右旋与左旋对称,核心是将左子节点提升为父节点,此处不再赘述。

四、红黑树的删除操作与修复

删除操作是红黑树中最复杂的部分,核心挑战是如何在删除节点后维持红黑树的 5 条性质。删除流程分为 3 步:

  1. 按 BST 规则删除节点:找到待删除节点,根据节点的子节点数量(0、1、2)执行不同删除逻辑;
  2. 记录关键信息:跟踪 "替代节点"(实际被移除的节点)及其原始颜色;
  3. 修复红黑树性质:若删除的是黑色节点,需通过修复流程恢复平衡。

4.1 BST 删除逻辑

  • 叶子节点(无孩子):直接删除;
  • 单孩子节点:用子节点替代该节点;
  • 双孩子节点:找到中序后继(右子树最小节点),复制其值到当前节点,再删除后继节点(转化为前两种情况)。

4.2 删除后的修复操作(deleteFixup)

删除节点后,若被删除节点是黑色,会破坏性质 5(黑高不一致),此时需要通过 "双重黑色" 标记来修复。"双重黑色" 表示该路径上缺少一个黑色节点,需要通过以下 4 种情况消除:

情况 1:兄弟节点为红色
  • 场景:当前节点(x)为双重黑色,兄弟节点(s)为红色;
  • 修复逻辑
    1. 将兄弟节点(s)改为黑色,父节点(p)改为红色;
    2. 对父节点(p)执行左旋;
    3. 转化为其他情况(兄弟节点变为黑色)。
情况 2:兄弟节点为黑色,且兄弟的两个孩子均为黑色
  • 场景:x 为双重黑色,s 为黑色,s 的左右孩子均为黑色;
  • 修复逻辑
    1. 将兄弟节点(s)改为红色;
    2. 令 x = p(将双重黑色上移);
    3. 继续向上修复。
情况 3:兄弟节点为黑色,左孩子红色,右孩子黑色
  • 场景:x 为双重黑色,s 为黑色,s 的左孩子红、右孩子黑;
  • 修复逻辑
    1. 将 s 的左孩子改为黑色,s 改为红色;
    2. 对 s 执行右旋;
    3. 转化为情况 4。
情况 4:兄弟节点为黑色,右孩子为红色
  • 场景:x 为双重黑色,s 为黑色,s 的右孩子为红色;
  • 修复逻辑
    1. 将 s 的颜色改为 p 的颜色;
    2. 将 p 改为黑色,s 的右孩子改为黑色;
    3. 对 p 执行左旋;
    4. 令 x = root(修复完成)。

4.3 修复操作的核心目标

  • 消除 "双重黑色" 标记,恢复路径上的黑高平衡;
  • 避免出现连续红色节点,维持性质 4;
  • 通过最少的旋转和变色操作完成修复,降低时间开销。

五、红黑树与 AVL 树的深度对比

特性红黑树AVL 树
平衡标准颜色规则 + 黑高平衡左右子树高度差≤1(严格平衡)
旋转次数插入最多 2 次,删除最多 3 次插入最多 2 次,删除可能 O (log n)
空间开销存储颜色(1bit)存储平衡因子(通常需 2-4bit)
查找性能平均 O (log n),最坏 O (2log n)严格 O (log n)(高度更优)
插入 / 删除性能更优(旋转少)较差(严格平衡导致旋转频繁)
适用场景插入删除频繁的场景(如 STL 容器)查找频繁的场景(如数据库索引)

红黑树的工业级优势:在大多数实际场景中,红黑树的综合性能优于 AVL 树。虽然 AVL 树的高度更优,但红黑树的旋转操作更少,维护成本更低,尤其适合插入删除频繁的动态数据场景。

六、红黑树的实际应用场景

  1. C++ STL 容器std::mapstd::setstd::multimap等关联容器底层均采用红黑树实现,利用其 O (log n) 的插入、删除和查找性能;
  2. Linux 内核
    • 进程调度:CFS(完全公平调度器)用红黑树管理进程运行队列,快速找到下一个待调度进程;
    • 虚拟内存管理:VM_area_struct 结构体用红黑树组织,高效管理内存区域;
  3. Java 集合框架TreeMapTreeSet底层基于红黑树,提供有序映射和集合功能;
  4. 数据库:部分数据库(如 MongoDB)的索引结构采用红黑树,平衡查询和更新性能;
  5. 编译器实现:语法分析阶段的符号表常用红黑树存储变量和函数信息,支持快速查找和插入。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ClickHouse从入门到企业级实战全解析课程简介

【课程简介】你是否正在面临这些挑战&#xff1f;海量数据的分析查询慢如蜗牛&#xff0c;报表一等就是几小时&#xff1f;想构建实时数仓&#xff0c;却不知如何高效处理 Kafka 等流式数据&#xff1f;对 ClickHouse 的众多 MergeTree 引擎感到困惑&#xff0c;不知如何选型&a…

【新启航】从人工偏差到机械精度:旋转治具让三维扫描重构数据重复精度提升至 ±0.01mm

在三维扫描重构领域&#xff0c;传统人工操作方式受限于人为因素干扰&#xff0c;数据重复精度难以保证&#xff0c;无法满足高精度工业检测与逆向工程需求。旋转治具凭借先进的机械设计与自动化控制技术&#xff0c;将三维扫描重构数据重复精度提升至 0.01mm&#xff0c;实现从…

《汇编语言:基于X86处理器》第13章 复习题和编程练习

本篇记录了《汇编语言&#xff1a;基于X86处理器》第13章 复习题和编程练习的学习笔记。13.6 复习题1.当汇编过程被高级语言程序调用时&#xff0c;主调程序与被调过程是否应使用相同的内存模式?答&#xff1a;主调程序与被调过程使用的内存模式必须相同。2.C 和 C程序调用汇编…

SpringAI智能航空助手实战<Demo>

我们将如何将我们得传统业务进行智能化的改造>>>1.将我们传统的航空票务系统 我们之前通过按钮的方式来完成 现在我们通过智能对话的方式完成 >现在我们通过对话的方式来完成 整个智能化的改造 传统应用如何进行智能化改造 我们把我们的项目通过Spring-ai 来接入A…

windows git安装步骤

1&#xff0c;从官网下载安装包&#xff1a;gitg官网 进行安装 2&#xff0c;配置git环境&#xff1a; git config --global user.name "Your Name" git config --global user.email "Your Email"3&#xff0c;生成 SSH Key&#xff1a; ssh-keygen -t r…

使用chroma和LlamaIndex做RAG增强

RAG 原理&#xff1a;通过 “检索&#xff08;从知识库获取相关信息&#xff09;→ 增强&#xff08;将信息作为上下文输入模型&#xff09;→ 生成&#xff08;模型基于上下文回答&#xff09;” 三步&#xff0c;解决大模型知识时效性、领域局限性问题。 接下来将完成这么一个…

2025 最应避免的摄影陷阱以及解决方案

你有没有想过&#xff0c;当你拍完了一个完美的场景后&#xff0c;却发现画面模糊、光线不足&#xff0c;或者更糟的是&#xff0c;存储卡中的文件丢失了&#xff1f;这些问题可能会发生在任何人身上&#xff0c;无论是业余爱好者、专业人士还是最好的摄影师。当珍贵的记忆变成…

python类--python011

面向对象编程中的类的概念、属性使用、继承和类的改造问题等。7.1 初识类在软件编程中&#xff0c;面向过程和面向对象是两种主要的编程方法。面向过程的编程强调通过函数来实现特定的功能&#xff0c;具有灵活性&#xff0c;但在复杂系统中往往导致代码重复&#xff0c;维护困…

Python函数篇:从零到精通

一、函数1.1 为什么有函数我们对于一个项目时&#xff0c;会有上千甚至上万条代码&#xff0c;当我们要使用到某个函数时&#xff0c;例如我需要计算一个求和代码&#xff0c;获得求和的值来服务我们的项目&#xff0c;那我们可能会这样#计算1&#xff5e;100的和 theSun 0 fo…

QT项目之记事本

本文用QT实现记事本功能。一、成品展示1.界面主要元素&#xff1a;1.标题为MyNoteBook&#xff1b;2.相应图标为&#xff1a;打开文件&#xff0c;保存&#xff0c;退出&#xff1b;3.右下角标注光标所在行列&#xff0c;默认编码方式为UTF-8&#xff1b;4.鼠标所在图标位置时会…

【软件测试】性能测试 —— 工具篇 JMeter 介绍与使用

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录1. JMeter 的介绍2. JMeter 安装、配置、搭建2.1 前置条件 —— Java环境搭建2.2 JMeter 下载2.3 JMeter 安装…

二十二、Mybatis-快速入门程序

入门程序大概步骤叙述&#xff1a; 步骤一&#xff1a;创建springboot工程并且数据库提前创建表步骤二&#xff1a;创建springboot工程对Mybatis相关依赖注意打勾步骤三&#xff1a;编写查找方法步骤四&#xff1a;编写测试方法项目目录结构与数据库以及代码&#xff1a; 项目目…

Blender模拟结构光3D Scanner(一)外参数匹配

如何使用Blender模拟FPP(Fringe Projection Profilometry) 原理的结构光3D传感器&#xff1f;主要包含的工作有&#xff1a;1&#xff09;相机、投影仪定位与内外参数匹配&#xff1b;2&#xff09;投影仪投射指定Pattern图像&#xff1b;3&#xff09;被测物体材质属性配置等&…

LangChain是如何实现RAG多轮问答的

目录引言一、LangChain实现RAG多轮问答核心机制1. 对话历史管理&#xff08;Memory&#xff09;2. 问题重写&#xff08;Query Rewriting&#xff09;3. 检索增强生成&#xff08;RAG Core&#xff09;4. 链式工作流&#xff08;Chain&#xff09;二、关键设计特点三、完整示例…

DAY 44 预训练模型

知识点回顾&#xff1a; 预训练的概念常见的分类预训练模型图像预训练模型的发展史预训练的策略预训练代码实战&#xff1a;resnet18 一、预训练的概念 我们之前在训练中发现&#xff0c;准确率最开始随着epoch的增加而增加。随着循环的更新&#xff0c;参数在不断发生更新。 所…

Java Stream API 中常用方法复习及项目实战示例

在最近的练手项目中&#xff0c;对于stream流的操作愈加频繁&#xff0c;我也越来越感觉stream流在处理数据是的干净利落&#xff0c;因此写博客用来记录最近常用的方法以便于未来的复习。map() 方法map()是一个中间操作&#xff08;intermediate operation&#xff09;&#x…

从零开始手搓一个GPT大语言模型:从理论到实践的完整指南(一)

现在人工智能飞速发展时代&#xff0c;LLM绝对可以算是人工智能领域得一颗明珠&#xff0c;也是现在许多AI项目落地得必不可少得一个模块&#xff0c;可以说&#xff0c;不管你之前得研究领域是AI得哪个方向&#xff0c;现在都需要会一些LLM基础&#xff0c;在这个系列&#xf…

Redis ubuntu下载Redis的C++客户端

1. 安装 redis-plus-plus C 操作 Redis 的库有很多&#xff0c;这里选择使用 redis-plus-plus&#xff0c;这个库的功能强大&#xff0c;使用简单。 Github 地址&#xff1a;GitHub - sewenew/redis-plus-plus: Redis client written in C 访问不了Github 地址的可以使用Ste…

nm命令和nm -D命令参数

出现这种差异的原因在于&#xff1a;动态库中的符号分为两种类型&#xff1a; 常规符号表&#xff08;regular symbol table&#xff09;&#xff1a;通常用于静态链接和调试&#xff0c;默认不包含在动态库中&#xff08;除非显式保留&#xff09;。动态符号表&#xff08;dyn…

Windows下cuda的安装和配置

今天开始做一个cuda教程。由于本人主要在windows下使用visual studio进行开发&#xff0c;因此这里讲一下windows下的cuda开发环境。 下载cuda_toolkit 从网站https://developer.nvidia.com/cuda-toolkit中下载&#xff0c;先选择Download Now,然后跳转到如下页面&#xff1a…