C++红黑树的深入解析:从理论到实践

C++红黑树的深入解析:从理论到实践

红黑树作为一种自平衡的二叉搜索树,是计算机科学中的经典数据结构之一,广泛应用于各种需要高效查找、插入和删除操作的场景中,比如STL中的 mapset。虽然它的基本原理并不复杂,但在实现过程中,需要处理许多细节,比如颜色的调整、旋转操作等。本文将对红黑树的结构、插入、删除等操作进行详细的剖析,以帮助大家更好地理解和实现这一高效的数据结构。

一、红黑树的基本概念与规则

红黑树是一种满足特定性质的二叉搜索树。与普通的二叉搜索树不同,红黑树的每个结点都附加了一个颜色属性,且通过一些规则保证了树的平衡性。红黑树的四条基本规则如下:

每个节点的颜色要么是红色,要么是黑色。根节点是黑色的。如果一个节点是红色的,那么它的两个子节点必须是黑色的。 这就意味着红色节点不能有连续的红色节点。从任意一个节点到其所有叶子节点的路径上,必须包含相同数量的黑色节点。 这条规则保证了树的平衡性,防止树的某些路径过长。

红黑树的这些规则通过“颜色约束”来间接实现自平衡,因此每次进行插入、删除操作时,都需要确保这些规则被满足。

以上均为红黑树示例。

二、红黑树的高度与效率

红黑树的高度是它的关键特性之一。红黑树的自平衡机制确保了它的高度不会太大,这对于树的查找、插入和删除操作的效率至关重要。理论上,红黑树的高度 h 与黑色高度 bh(从根到叶子节点的路径上黑色节点的数量)之间存在如下关系:

最短路径(只有黑色节点)长度为 bh。最长路径(交替的红色和黑色节点)长度为 2 * bh。

因此,红黑树的最大高度为 2 * bh,而最小高度为 bh。因此,红黑树的高度 h 满足 h≤2×bhh \leq 2 \times bhh≤2×bh,并且由于红黑树的黑色高度是相同的,所以可以得出红黑树的高度是 O(log N),其中 N 是树中结点的数量。

由于红黑树的高度被控制在对数级别,它能够保证查找、插入、删除操作在最坏情况下的时间复杂度均为 O(log N)。

三、红黑树的结构

红黑树的基本节点结构包括以下几个部分:

enum Colour { RED, BLACK }; template<class K, class V> struct RBTreeNode { pair<K, V> _kv; // 存储键值对 RBTreeNode<K, V>* _left; // 左子树 RBTreeNode<K, V>* _right; // 右子树 RBTreeNode<K, V>* _parent; // 父节点 Colour _col; // 颜色属性 RBTreeNode(const pair<K, V>& kv) : _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED) {} }; 

每个节点不仅包含键值对 (_kv),还包含指向左子树、右子树和父节点的指针。此外,节点的颜色 (_col) 用于维护红黑树的平衡。

四、红黑树的插入操作

红黑树的插入操作需要遵循以下步骤:

按照二叉搜索树的规则插入新节点。这是插入操作的第一步,我们首先将新节点插入到树的适当位置。将新节点的颜色设置为红色。新插入的节点默认是红色的,这有助于避免违反红黑树的黑色节点数平衡。调整颜色和结构。插入新节点后,可能会破坏红黑树的平衡。具体的修复操作包括:情况1:变色操作。如果父节点和叔叔节点都是红色,则将父节点和叔叔节点都变为黑色,祖父节点变为红色,并继续往上处理。情况2:旋转和变色。如果父节点是红色,且叔叔节点是黑色或不存在,则需要进行旋转操作(单旋或双旋),并相应地调整颜色。

我们来看一段插入代码的实现:

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; } 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) { parent->_col = uncle->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = cur->_parent; } else { if (cur == parent->_left) { RotateR(grandfather); parent->_col = BLACK; grandfather->_col = RED; } else { RotateL(parent); RotateR(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } else { 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) { RotateL(grandfather); parent->_col = BLACK; grandfather->_col = RED; } else { RotateR(parent); RotateL(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } } _root->_col = BLACK; return true; } 

这段代码展示了红黑树的插入操作,包括节点插入、颜色变更和旋转操作。关键的操作是通过循环和条件判断来确保红黑树的规则不被破坏。

五、红黑树的查找操作

红黑树的查找操作和普通的二叉搜索树是类似的,我们依然按照二叉搜索树的规则进行查找。由于红黑树的平衡性,查找操作的时间复杂度为 O(log N)。

Node* Find(const K& key) { Node* cur = _root; while (cur) { if (cur->_kv.first < key) { cur = cur->_right; } else if (cur->_kv.first > key) { cur = cur->_left; } else { return cur; } } return nullptr; } 

上述代码展示了查找操作的实现,我们根据节点的键值逐步向左或向右子树移动,直到找到目标节点或树为空。

六、红黑树的删除操作

删除操作在红黑树中比插入操作更为复杂。删除一个节点后,可能会破坏红黑树的平衡,特别是当删除的节点是黑色时,需要特别注意。因此,删除操作需要执行旋转和变色操作来恢复平衡。尽管删除操作复杂,但其时间复杂度同样是 O(log N)。

由于删除操作较为复杂,本文不深入讨论其实现。如果有兴趣,可以参考《算法导论》或《STL源码剖析》中的相关章节。

七、红黑树的验证

为了验证红黑树的正确性,我们可以检查它是否满足四条基本规则。可以通过前序遍历树的每一条路径,检查节点颜色和黑色节点数量是否符合要求。

bool Check(Node* root, int blackNum, const int refNum) { if (root == nullptr) { if (refNum != blackNum) { cout << "存在黑色结点数量不相等的路径" << endl; return false; } return true; } if (root->_col == RED && root->_parent->_col == RED) { cout << root->_kv.first << "存在连续的红色结点" << endl; return false; } if (root->_col == BLACK) { blackNum++; } return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum); } 

通过这个检查函数,我们能够验证树的结构是否符合红黑树的规则。

八、总结

红黑树是一种自平衡的二叉搜索树,通过简单的规则保证树的平衡性,确保了查找、插入和删除操作的时间复杂度始终为 O(log N)。虽然红黑树的插入、删除操作相对复杂,但它的高效性和稳定性使其成为许多应用中不可或缺的数据结构。希望通过本文的详细解析,大家能够对红黑树有更深入的了解。

!!预告,下一期详细讲红黑树的变换过程

Read more

【Python】基础语法入门(一)

【Python】基础语法入门(一)

前言 Python作为一门入门门槛低、生态丰富的编程语言,Python早已成为编程初学者、数据分析从业者、后端开发者的首选工具之一。而掌握Python的第一步,就是吃透最核心的基础语法,常量与表达式、变量与类型、注释、输入输出及运算符。今天,我们就结合实例,手把手带你入门这些必备知识点,助你快速搭建Python语法框架。 一、常量与表达式 刚接触 Python 时,我们可以先把它当作一个功能强大的计算器 ,通过简单的表达式,以完成各类算术运算,比如简单的加减乘除,甚至复杂的乘方运算,都能直接通过“表达式”实现。 核心知识点: 1. 表达式与常量:形如1 + 2 * 3的算式称为“表达式”,运算结果为“表达式的返回值”;1、2、3这类固定值称为“字面值常量”,+、-、*、/则是“运算符”。 2. 运算规则:遵循“先乘除后加减”的数学逻辑,

By Ne0inhk

【Python】6 种方法轻松将 Python 脚本打包成 EXE 应用

以下是 2025–2026 年最实用的 6 种 Python 脚本打包成 Windows EXE 可执行文件 的主流方法,按易用性 × 普及度 × 实际场景排序。 排名方法/工具易用性生成文件大小启动速度运行速度反编译难度典型场景推荐指数 (★5)1PyInstaller★★★★★大(onefile 常 50–300MB)慢(几秒~几十秒)普通低绝大多数 GUI、小工具、初次尝试★★★★★2auto-py-to-exe★★★★★同 PyInstaller同上普通低零基础用户、GUI 操作打包★★★★☆3Nuitka★★★★☆中~小快明显更快(1.5–4×)中~高性能敏感、数值计算、想保护代码★★★★☆4cx_Freeze★★★★中较快普通低~中追求启动快、

By Ne0inhk
【Python 初级函数详解】—— 参数沙漠与作用域丛林的求生指南

【Python 初级函数详解】—— 参数沙漠与作用域丛林的求生指南

欢迎来到ZyyOvO的博客✨,一个关于探索技术的角落,记录学习的点滴📖,分享实用的技巧🛠️,偶尔还有一些奇思妙想💡 本文由ZyyOvO原创✍️,感谢支持❤️!请尊重原创📩!欢迎评论区留言交流🌟 个人主页 👉 ZyyOvO 本文专栏➡️Python 算法研究所 快速复习👉【Python 速览 】 —— 课前甜点,打开你的味蕾 课前导入 我们知道数学中的函数,我们输入一个数,在通过对应的映射关系得到另一个数,如下图给出了两个简单的数学函数: 什么是函数 那在Python编程中函数是什么呢? 在编程中,函数(Function) 是一段被命名、可重复使用的代码块,用于执行特定任务,它通过接收输入(参数),处理逻辑,并返回输出(结果),将复杂的程序拆分为模块化的组件,让代码更简洁、高效且易于维护。 函数的优势 在 Python 中,函数是编程的核心工具之一,它通过将代码逻辑封装为可重复使用的模块,显著提升了代码的可维护性、复用性和可读性。 避免代码重复:DRY

By Ne0inhk

Python 爬虫实战:爬取新闻网站头条与正文内容

前言 在信息爆炸的时代,新闻数据是舆情分析、行业研究、内容创作的重要素材。通过 Python 爬虫技术批量获取新闻网站的头条与正文内容,能够突破人工采集的效率瓶颈,实现结构化的数据沉淀与深度分析。本文以新浪新闻(综合类新闻平台)为核心数据源,系统讲解新闻头条列表、单篇新闻正文的爬取方法,涵盖 HTML 解析、动态内容处理、数据清洗等核心环节,同时兼顾反爬策略与合规性要求,为新闻数据的获取与应用提供完整的技术方案。 摘要 本文以新浪新闻(https://news.sina.com.cn/)为数据来源,详细阐述 Python 爬虫爬取新闻头条与正文内容的全流程。核心技术包括requests库的 HTTP 请求发送、BeautifulSoup的 HTML 结构解析、lxml的高效解析引擎、re的正则表达式数据清洗,以及针对动态加载内容的requests-html辅助处理。通过完整的代码案例,实现新浪新闻头条列表(标题、链接、发布时间、来源)的批量爬取,以及单篇新闻正文(

By Ne0inhk