std::map用法
std::map 是 C++ 标准模板库(STL)中的一个关联容器,用于存储键值对(key-value pairs),其中每个键都是唯一的,并且按照特定的顺序(通常是升序)自动排序。std::map 通常基于红黑树实现,提供对元素的高效查找、插入和删除操作。
1. 基本特性
- 有序性:
std::map中的元素按照键的顺序自动排序,默认使用<运算符进行比较。 - 唯一键:每个键在 
std::map中必须是唯一的,如果尝试插入重复的键,则插入操作会失败。 - 关联容器:通过键快速访问对应的值,通常具有对数时间复杂度(O(log n))。
 - 可变性:可以动态地插入和删除元素。
 
2. 头文件和命名空间
要使用 std::map,需要包含头文件 <map> 并使用 std 命名空间:
1  | 
  | 
3. 声明和初始化
3.1 声明一个 std::map
1  | // 键为 int,值为 std::string 的 map  | 
3.2 初始化 std::map
可以使用初始化列表或其他方法初始化 map:
1  | map<int, string> myMap = {  | 
4. 主要操作
4.1 插入元素
有几种方法可以向 std::map 中插入元素:
4.1.1 使用 insert 函数
1  | myMap.insert(pair<int, string>(4, "Date"));  | 
4.1.2 使用下标运算符 []
1  | myMap[7] = "Grape";  | 
注意:使用 [] 运算符时,如果键不存在,会自动插入该键,并将值初始化为类型的默认值。
4.2 访问元素
4.2.1 使用下标运算符 []
1  | string fruit = myMap[1]; // 获取键为 1 的值 "Apple"  | 
注意:如果键不存在,[] 会插入该键并返回默认值。
4.2.2 使用 at 成员函数
1  | try {  | 
at 函数在键不存在时会抛出 std::out_of_range 异常,适合需要异常处理的场景。
4.2.3 使用 find 成员函数
1  | auto it = myMap.find(3);  | 
find 返回一个迭代器,指向找到的元素,若未找到则返回 map::end()。
4.3 删除元素
4.3.1 使用 erase 函数
1  | // 按键删除  | 
4.3.2 使用 clear 函数
1  | myMap.clear(); // 删除所有元素  | 
4.4 遍历 std::map
4.4.1 使用迭代器
1  | for (map<int, string>::iterator it = myMap.begin(); it != myMap.end(); ++it) {  | 
4.4.2 使用基于范围的 for 循环(C++11 及以上)
1  | for (const auto& pair : myMap) {  | 
4.5 常用成员函数
- **
size()**:返回容器中元素的数量。 - **
empty()**:判断容器是否为空。 - **
count(key)**:返回具有指定键的元素数量(对于map,返回 0 或 1)。 lower_bound(key)和 **upper_bound(key)**:返回迭代器,分别指向第一个不小于和第一个大于指定键的元素。- **
equal_range(key)**:返回一个范围,包含所有等于指定键的元素。 
5. 自定义键的排序
默认情况下,std::map 使用 < 运算符对键进行排序。如果需要自定义排序方式,可以提供一个自定义的比较函数或函数对象。
5.1 使用函数对象
1  | struct Compare {  | 
5.2 使用 lambda 表达式(C++11 及以上)
需要注意,std::map 的第三个模板参数必须是可比较类型,不能直接使用 lambda 表达式作为模板参数。不过,可以使用 std::function 或自定义结构体来包装 lambda。
1  | struct CompareLambda {  | 
6. std::map 与其他关联容器的比较
- **
std::unordered_map**:基于哈希表实现,提供平均常数时间复杂度的查找、插入和删除操作,但不保证元素的顺序。适用于对顺序无要求且需要高效查找的场景。 - **
std::multimap**:允许多个相同键的元素,其他特性与std::map类似。适用于需要存储重复键值对的场景。 
7. 性能考虑
时间复杂度
:
- 查找、插入、删除:O(log n)
 - 遍历:O(n)
 
空间复杂度:
std::map通常需要额外的空间来维护树结构,相比std::vector等序列容器,内存开销更大。
选择使用 std::map 还是其他容器,应根据具体需求和性能要求进行权衡。
8. 完整示例
以下是一个完整的示例,展示了 std::map 的基本用法:
1  | 
  | 
输出:
1  | Key 1: Apple  | 
BST实现map
1. 选择底层数据结构
std::map 通常基于平衡的二叉搜索树(如红黑树)实现,以保证操作的时间复杂度为 O(log n)。为了简化实现,本文将采用普通的二叉搜索树,即不进行自平衡处理。不过在实际应用中,为了保证性能,建议使用自平衡的树结构(例如红黑树、AVL 树)。
2. 设计数据结构
2.1 节点结构
首先,需要定义树的节点结构,每个节点包含键值对以及指向子节点的指针。
1  | 
  | 
2.2 Map 类的定义
接下来,定义 MyMap 类,包含根节点和基本操作。
1  | template <typename Key, typename T>  | 
2.3 解释
TreeNode 结构:
data: 存储键值对std::pair<Key, T>。left和right: 指向左子节点和右子节点。parent: 指向父节点,便于迭代器中查找后继节点。
MyMap 类:
构造与析构
:
- 构造函数初始化根节点为空。
 - 析构函数调用 
clear释放所有节点内存。 
插入 (
insert):
- 从根节点开始,根据键的大小确定插入左子树还是右子树。
 - 如果键已存在,更新对应的值。
 
查找 (
find):
- 按照键的大小在树中查找对应的节点。
 
删除 (
erase):
- 查找到目标节点。
 - 如果节点有两个子节点,找到中序后继节点并替换当前节点的数据,然后删除后继节点。
 - 如果节点有一个或没有子节点,直接替换节点指针并删除节点。
 
清空 (
clear):
- 使用递归方式删除所有节点。
 
迭代器
:
- 定义了嵌套的 
Iterator类,支持前置和后置递增操作。 - 迭代器通过中序遍历实现,保证键的顺序性。
 begin()返回最小的节点,end()返回nullptr。
- 定义了嵌套的 
 
3. 使用示例
下面提供一个使用 MyMap 的示例,展示插入、查找、删除和迭代操作。
1  | int main() {  | 
输出结果
1  | Map contents (in-order):  | 
4. 迭代器的详细实现
为了支持迭代器的正常使用,Iterator 类实现了以下功能:
- **解引用操作符 (
operator\*和operator->)**:- 允许访问键值对,如 
it->first和it->second。 
 - 允许访问键值对,如 
 - **递增操作符 (
operator++)**:- 前置递增(
++it)和后置递增(it++)用于移动到下一个元素。 - 通过查找当前节点的中序后继节点实现。
 
 - 前置递增(
 - **比较操作符 (
operator==和operator!=)**:- 判断两个迭代器是否指向同一个节点。
 
 
中序后继节点的查找
迭代器使用中序遍历来确保键的有序性。计算后继节点的步骤如下:
- 当前节点有右子树:
- 后继节点是右子树中的最小节点。
 
 - 当前节点没有右子树:
- 向上查找,直到找到一个节点是其父节点的左子树,此时父节点即为后继节点。
 
 
如果没有后继节点(即当前节点是最大的节点),则返回 nullptr,表示迭代器到达 end()。
5. 扩展功能
上述实现是一个基本的 Map,还可以根据需要扩展更多功能,例如:
- 支持 
const迭代器:- 定义 
const_iterator,确保在只读操作时数据不被修改。 
 - 定义 
 - 实现平衡树:
- 为了提高性能,可以实现红黑树、AVL 树等自平衡二叉搜索树,保证操作的时间复杂度为 O(log n)。
 
 - 添加更多成员函数:
- 如 
operator[]、count、lower_bound、upper_bound等,增加容器的功能性。 
 - 如 
 - 异常处理:
- 增加对异常情况的处理,例如在删除不存在的键时抛出异常等。
 
 - 迭代器的逆向遍历:
- 实现双向迭代器,支持逆序遍历(
rbegin()和rend())。 
 - 实现双向迭代器,支持逆序遍历(
 
AVL树
AVL树(Adelson-Velsky and Landis树)是一种自平衡的二叉搜索树(BST),它在插入和删除操作后通过旋转来保持树的平衡,从而确保基本操作(如查找、插入和删除)的时间复杂度保持在O(log n)。使用AVL树来实现map(键值对映射)是一个高效的选择,特别适合需要频繁查找、插入和删除操作的场景。
1. 模板化AVL树节点
首先,我们需要将AVLNode结构模板化,使其能够处理不同类型的键和值。我们假设键类型KeyType支持operator<进行比较,因为AVL树需要对键进行排序以维护其性质。
首先,我们定义AVL树的节点。每个节点包含一个键(key)、一个值(value)、节点高度(height),以及指向左子节点和右子节点的指针。
1  | 
  | 
说明:
KeyType:键的类型,需要支持比较操作(如operator<)。ValueType:值的类型,可以是任何类型。
2. 辅助函数的模板化
辅助函数同样需要模板化,以适应不同的AVLNode类型。
获取节点高度
获取节点的高度。如果节点为空,则高度为0。
1  | template <typename KeyType, typename ValueType>  | 
获取平衡因子
平衡因子(Balance Factor)是左子树高度减去右子树高度。
1  | template <typename KeyType, typename ValueType>  | 
右旋转
右旋转用于处理左子树过高的情况(例如,左左情况)。
1  | y x  | 
实现
1  | template <typename KeyType, typename ValueType>  | 
左旋转
左旋转用于处理右子树过高的情况(例如,右右情况)。
1  | x y  | 
具体实现
1  | template <typename KeyType, typename ValueType>  | 
3. AVL树的核心操作模板化
插入节点
插入操作遵循标准的二叉搜索树插入方式,然后通过旋转保持树的平衡。
1  | template <typename KeyType, typename ValueType>  | 
查找节点
按键查找节点,返回对应的值。如果键不存在,返回nullptr。
1  | template <typename KeyType, typename ValueType>  | 
获取最小值节点
用于删除节点时找到中序后继。
1  | template <typename KeyType, typename ValueType>  | 
删除节点
删除操作分为三种情况:删除节点是叶子节点、有一个子节点或有两个子节点。删除后,通过旋转保持树的平衡。
1  | template <typename KeyType, typename ValueType>  | 
4. 模板化的AVLMap类
现在,我们将所有模板化的函数集成到一个模板类AVLMap中。这个类将提供如下功能:
put(const KeyType& key, const ValueType& value):插入或更新键值对。get(const KeyType& key):查找键对应的值,返回指向值的指针,如果键不存在则返回nullptr。remove(const KeyType& key):删除指定键的键值对。inorderTraversal():中序遍历,返回有序的键值对列表。
1  | template <typename KeyType, typename ValueType>  | 
说明:
模板参数
:
KeyType:键的类型,需要支持operator<进行比较。ValueType:值的类型,可以是任意类型。内存管理
:
- 析构函数使用后序遍历释放所有动态分配的节点,防止内存泄漏。
 异常安全
:
- 当前实现没有处理异常情况。如果需要更高的异常安全性,可以进一步增强代码,例如在插入过程中捕获异常并回滚操作。
 
5. 使用示例
下面的示例展示了如何使用模板化的AVLMap类,使用不同类型的键和值。
示例 1:使用int作为键,std::string作为值
1  | 
  | 
输出:
1  | 中序遍历: (10, "十") (20, "二十") (25, "二十五") (30, "三十") (40, "四十") (50, "五十")  | 
示例 2:使用std::string作为键,double作为值
1  | 
  | 
输出:
1  | 中序遍历: ("apple", 1.99) ("banana", 0.99) ("cherry", 2.99) ("date", 3.49) ("elderberry", 5.99) ("fig", 2.49)  | 
6. 完整的通用代码
以下是模板化的AVLMap的完整实现代码,包括所有辅助函数和类定义:
1  | 
  | 
说明
- 平衡维护:在每次插入或删除后,都会更新节点的高度并计算平衡因子。如果某个节点的平衡因子超出了
[-1, 1]范围,就需要通过旋转来重新平衡树。 - 查找操作:由于AVL树的高度被保持在
O(log n),查找操作的时间复杂度为O(log n)。 - 更新操作:如果插入的键已经存在,则更新其对应的值。
 - 遍历操作:中序遍历可以按键的顺序遍历所有键值对。
 - 内存管理:确保在析构函数中正确释放所有动态分配的内存,避免内存泄漏。
 - 泛型支持(可选):为了使
AVLMap更加通用,可以将其模板化,以支持不同类型的键和值。例如: 
编译与运行:
假设保存为 avlmap.cpp,使用以下命令编译并运行:
1  | g++ -std=c++11 -o avlmap avlmap.cpp  | 
预期输出:
1  | 示例 1:int 键,std::string 值  | 
7. 注意事项与扩展
1. 键类型的要求
为了使AVLMap正常工作,键类型KeyType必须支持以下操作:
比较操作:必须定义
operator<,因为AVL树依赖于它来维护排序。如果使用自定义类型作为键,请确保定义了operator<。1
2
3
4
5
6
7
8
9
10struct CustomKey {
int id;
std::string name;
bool operator<(const CustomKey& other) const {
if (id != other.id)
return id < other.id;
return name < other.name;
}
};
2. 泛型支持与约束
在C++20之前,模板并不支持在模板参数上强制施加约束(需要依赖文档和用户理解)。从C++20起,可以使用概念(Concepts)来施加约束。
1  | 
  | 
这样,编译器会在实例化模板时检查KeyType是否满足std::totally_ordered,即是否支持所有必要的比较操作。
3. 性能优化
- 内存管理:当前实现使用递归进行插入和删除,如果树非常深,可能会导致栈溢出。可以考虑使用迭代方法或优化递归深度。
 - 缓存友好:使用自适应数据结构(如缓存友好的节点布局)可以提升性能。
 - 多线程支持:当前实现不是线程安全的。如果需要在多线程环境中使用,需要添加适当的同步机制。
 
4. 额外功能
根据需求,你可以为AVLMap添加更多功能:
- 迭代器:实现输入迭代器、中序遍历迭代器,以便支持范围基(range-based)
for循环。 - 查找最小/最大键:提供方法
findMin()和findMax()。 - 前驱/后继查找:在树中查找给定键的前驱和后继节点。
 - 支持不同的平衡因子策略:例如,允许用户指定自定义的平衡策略。
 
5. 与标准库的比较
虽然自己实现AVLMap是一项很好的学习练习,但在实际生产环境中,建议使用C++标准库中已经高度优化和测试过的容器,如std::map(通常实现为红黑树)、std::unordered_map(哈希表)等。
1  | 
  | 
std::map提供了与AVLMap类似的功能,并且经过了高度优化,适用于大多数应用场景。
红黑树
红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,它通过对节点进行颜色标记(红色或黑色)并遵循特定的规则来保证树的平衡性,从而确保基本操作(如查找、插入、删除)的时间复杂度为 **O(log n)**。红黑树广泛应用于计算机科学中,例如在实现关联容器(如 std::map、std::set)时常用到。
1. 红黑树的五大性质
红黑树通过以下 五大性质 维持其平衡性:
- 节点颜色:每个节点要么是红色,要么是黑色。
 - 根节点颜色:根节点是黑色。
 - 叶子节点颜色:所有叶子节点(NIL 节点,即空节点)都是黑色的。这里的叶子节点不存储实际数据,仅作为树的终端。
 - 红色节点限制:如果一个节点是红色的,则它的两个子节点都是黑色的。也就是说,红色节点不能有红色的子节点。
 - 黑色平衡:从任意节点到其所有后代叶子节点的路径上,包含相同数量的黑色节点。这被称为每条路径上的黑色高度相同。
 
这些性质的意义
- 性质1 和 性质2 确保节点颜色的基本规则,便于后续操作中进行颜色判断和调整。
 - 性质3 保证了所有实际节点的子节点(NIL 节点)的统一性,简化了操作逻辑。
 - 性质4 防止了连续的红色节点出现,避免导致过度不平衡。
 - 性质5 保证了树的平衡性,使得树的高度始终保持在 O(log n) 的范围内,从而确保基本操作的高效性。
 
这些性质共同作用,使得红黑树在最坏情况下也能保持较好的性能表现。
2. 红黑树的插入操作
插入操作是红黑树中常见的操作,与标准的二叉搜索树(BST)插入类似,但需要额外的步骤来维护红黑树的性质。
2.1 插入步骤概述
插入操作通常分为以下两个主要步骤:
- 标准二叉搜索树插入:根据键值比较,将新节点插入到合适的位置,初始颜色为红色。
 - 插入后的修正(Insert Fixup):通过重新着色和旋转操作,恢复红黑树的五大性质。
 
2.2 插入后的修正(Insert Fixup)
插入一个红色节点可能会破坏红黑树的性质,尤其是性质4(红色节点不能连续)。为了修复这些潜在的冲突,需要进行颜色调整和旋转操作。
修正步骤:
插入修正的过程通常遵循以下规则(以下描述假设新插入的节点 z 是红色):
- 父节点为黑色:
- 如果 z 的父节点是黑色的,那么插入操作不会破坏红黑树的性质,修正过程结束。
 
 - 父节点为红色:
- 情况1:z 的叔叔节点(即 z 的父节点的兄弟节点)也是红色。
- 将父节点和叔叔节点重新着色为黑色。
 - 将祖父节点重新着色为红色。
 - 将 z 指向祖父节点,继续检查上层节点,防止高层的性质被破坏。
 
 - 情况2:z 的叔叔节点是黑色,且 z 是其父节点的右子节点。
- 对 z 的父节点进行左旋转。
 - 将 z 指向其新的左子节点(即原父节点)。
 
 - 情况3:z 的叔叔节点是黑色,且 z 是其父节点的左子节点。
- 将父节点重新着色为黑色。
 - 将祖父节点重新着色为红色。
 - 对祖父节点进行右旋转。
 
 
 - 情况1:z 的叔叔节点(即 z 的父节点的兄弟节点)也是红色。
 
旋转操作的重要性
在修正过程中,旋转操作用于调整树的局部结构,使得红黑树的性质得以恢复。这些旋转包括左旋转和右旋转,在后续章节中将详细介绍。
插入修正的代码实现示例
以下是红黑树插入修正的一个简化版 C++ 实现:
1  | template <typename Key, typename Value>  | 
3. 红黑树的删除操作
删除操作同样重要且复杂,因为它可能破坏红黑树的多个性质。与插入类似,删除操作也需要两个主要步骤:
- 标准二叉搜索树删除:按照 BST 的规则删除节点。
 - 删除后的修正(Delete Fixup):通过重新着色和旋转操作,恢复红黑树的性质。
 
3.1 删除步骤概述
删除操作分为以下步骤:
- 定位要删除的节点:
- 如果要删除的节点 z 有两个子节点,则找到其中序后继节点 y(即 z 的右子树中的最小节点)。
 - 将 y 的值复制到 z,然后将删除目标转移到 y。此时 y 至多只有一个子节点。
 
 - 删除节点:
- 若 y 只有一个子节点 x(可能为 NIL),则用 x 替代 y 的位置。
 - 记录被删除节点的原颜色 y_original_color。
 
 - 删除修正(仅当 y_original_color 为黑色时):
- 因为删除一个黑色节点会影响路径上的黑色数量,需通过多次调整来恢复红黑树的性质。
 
 
3.2 删除后的修正(Delete Fixup)
删除后的修正较为复杂,涉及多种情况处理。以下为主要的修正步骤和可能遇到的情况:
修正步骤:
初始化:设 x 为替代被删除的节点的位置,x 可能为实际节点或 NIL 节点。
循环修正:
- 当 x 不是根节点,且 x 的颜色为黑色,进入修正循环。
 - 判断 x 是其父节点的左子节点还是右子节点,并相应地设定兄弟节点 w。
 
处理不同情况:
情况1:w 是红色的。
- 将 w 重新着色为黑色。
 - 将 x 的父节点重新着色为红色。
 - 对 x 的父节点进行左旋转或右旋转,取决于是左子节点还是右子节点。
 - 更新 w,继续修正过程。
 
情况2:w 是黑色,且 w 的两个子节点都是黑色。
- 将 w 重新着色为红色。
 - 将 x 设为其父节点,继续修正。
 
情况3:w 是黑色,且 w 的左子节点是红色,右子节点是黑色。
- 将 w 的左子节点重新着色为黑色。
 - 将 w 重新着色为红色。
 - 对 w 进行右旋转或左旋转,取决于是左子节点还是右子节点。
 - 更新 w,进入情况4。
 
情况4:w 是黑色,且 w 的右子节点是红色。
- 将 w 的颜色设为 x 的父节点颜色。
 - 将 x 的父节点重新着色为黑色。
 - 将 w 的右子节点重新着色为黑色。
 - 对 x 的父节点进行左旋转或右旋转,取决于是左子节点还是右子节点。
 - 结束修正。
 
最终步骤:将 x 设为根节点,并将其颜色设为黑色,确保根节点的颜色为黑色。
删除修正的代码实现示例
由于删除修正涉及较多的情况,以下为一个简化版的红黑树删除修正的 C++ 实现:
1  | template <typename Key, typename Value>  | 
4. 旋转操作详解
旋转操作是红黑树中用于重新平衡树的关键操作,包括左旋转和右旋转。旋转操作通过调整节点的父子关系,改变树的局部结构,从而保持红黑树的性质。
4.1 左旋转(Left Rotate)
左旋转围绕节点 x 进行,其目的是将 x 的右子节点 y 提升为 x 的父节点,x 变为 y 的左子节点,y 的左子节点 b 成为 x 的右子节点。
旋转前:
1  | x  | 
旋转后:
1  | y  | 
左旋转的代码实现:
1  | template <typename Key, typename Value>  | 
4.2 右旋转(Right Rotate)
右旋转是 左旋转 的镜像操作,围绕节点 y 进行,其目的是将 y 的左子节点 x 提升为 y 的父节点,y 变为 x 的右子节点,x 的右子节点 b 成为 y 的左子节点。
旋转前:
1  | y  | 
旋转后:
1  | x  | 
右旋转的代码实现:
1  | template <typename Key, typename Value>  | 
旋转操作的作用
通过旋转操作,可以改变树的高度和形状,确保红黑树的性质在插入和删除后得到维护。旋转不会破坏二叉搜索树的性质,仅改变节点之间的指向关系。
5.简化版红黑树实现
节点结构体
首先,我们定义红黑树节点的结构体:
1  | 
  | 
红黑树类
接下来,我们定义红黑树的主要类,包括插入、删除和遍历功能:
1  | 
  | 
简要说明
上述红黑树类包含以下主要功能:
- **插入操作 (
insert)**:- 插入新的键值对,并调用 
insertFixUp进行修正,以保持红黑树的性质。 
 - 插入新的键值对,并调用 
 - **旋转操作 (
leftRotate和rightRotate)**:- 通过旋转操作重新调整树的结构,确保树的平衡。
 
 - **修正插入后的红黑树性质 (
insertFixUp)**:- 根据红黑树的五大性质,通过重新着色和旋转来修正可能的违规情况。
 
 - **中序遍历 (
inorderTraversal)**:- 以中序遍历的方式输出树中的键,结果应为升序。
 
 
注意:为了简化示例,删除操作 (
delete) 及其修正 (deleteFixUp) 未在此实现。如果需要完整的删除功能,请参考之前的详细解释或使用标准库中的实现。
6. 红黑树与其他平衡树的比较
红黑树并非唯一的自平衡二叉搜索树,其他常见的平衡树包括 AVL 树(Adelson-Velsky和Landis树)和 Splay 树。以下是红黑树与 AVL 树的比较:
红黑树 vs AVL 树
| 特性 | 红黑树 (Red-Black Tree) | AVL 树 (AVL Tree) | 
|---|---|---|
| 平衡性 | 相对不严格,每个路径上的黑色节点相同。 | 更严格,任意节点的左右子树高度差不超过1。 | 
| 插入/删除效率 | 较快,插入和删除操作较少的旋转,适用于频繁修改的场景。 | 较慢,插入和删除可能需要多次旋转,适用于查找操作多于修改的场景。 | 
| 查找效率 | O(log n) | O(log n),常数因子更小,查找速度略快。 | 
| 实现复杂度 | 相对简单,旋转操作较少。 | 实现较复杂,需严格维护高度平衡。 | 
| 应用场景 | 操作频繁、需要快速插入和删除的场景。 | 查找操作频繁、插入和删除相对较少的场景。 | 
选择依据
- 红黑树更适用于需要频繁插入和删除操作,并且查找操作相对较多的场景,因为其插入和删除操作的调整成本较低。
 - AVL 树适用于查找操作极为频繁,而修改操作相对较少的场景,因为其高度更严格,查找效率更高。
 
7. 红黑树的应用场景
由于红黑树高效的查找、插入和删除性能,它在计算机科学中的多个领域都有广泛的应用:
- 标准库中的关联容器:
- C++ 标准库中的 
std::map和std::set通常基于红黑树实现。 - Java 的 
TreeMap和TreeSet也是基于红黑树。 
 - C++ 标准库中的 
 - 操作系统:
- Linux 内核中的调度器和虚拟内存管理使用红黑树来管理进程和内存资源。
 
 - 数据库系统:
- 一些数据库索引结构使用红黑树来提高查询效率。
 
 - 编译器设计:
- 语法分析树和符号表管理中可能使用红黑树来高效存储和查找符号。