基于红黑树封装模拟实现 STL 的 set 与 map
关联式容器是 C++ STL 的重要组成部分,其中 set 和 map 通常以红黑树作为底层数据结构。从单纯的数据结构向标准容器接口跨越,不仅仅是接口的简单封装,更需要解决泛型设计、迭代器逻辑以及 const 正确性等核心问题。
整体实现思路
在动手编码前,建议遵循以下清晰的步骤,这能避免初学者在实现过程中遇到难以排查的结构问题:
- 实现基础的红黑树节点与平衡逻辑。
- 封装
set和map框架,重点解决 KeyOfT(键值提取)问题。 - 实现迭代器,特别是中序遍历下的
++和--操作。 - 处理
const_iterator与普通迭代器的区分。 - 确保
key不可修改的特性。 - 实现
operator[]等常用接口。
泛型设计与 KeyOfT
为什么需要两个模板参数?
在定义红黑树时,我们通常会看到两个模板参数:一个是存储数据的类型 T,另一个是用于比较的键类型 K。虽然 set 中数据本身就是 K,但在 map 中数据是 pair<K, V>。为了保持红黑树底层的通用性,我们需要将数据类型定义为高度泛型的 T,而通过上层容器传入具体的 K。
查找(Find)、删除(Erase)等操作都需要使用 K 进行比较。如果只依赖 T,在 map 场景下无法直接获取 K 进行高效检索,因此必须显式引入 K 作为第一个模板参数。
仿函数提取 Key
由于 set 和 map 对 Key 的提取方式不同,我们无法在红黑树内部硬编码比较逻辑。解决方案是在封装 set 和 map 时定义一个仿函数(Functor),专门负责从数据 T 中提取 K,并将其作为模板参数传递给红黑树。
例如在 map 中,数据是 pair<K, V>,仿函数只需返回 kv.first;而在 set 中,数据即为 K,仿函数直接返回 key。
迭代器的实现
迭代器是容器的灵魂,对于红黑树这种非线性结构,其难点在于 ++ 和 -- 的中序遍历逻辑。
前置 ++ 与 --
前置 ++ 的核心逻辑:
- 右子树存在:下一个节点是当前节点的右子树中最左边的节点。
- 右子树为空:需要向上回溯到祖先节点。如果当前节点是其父节点的左孩子,则父节点即为下一个节点;如果是右孩子,则继续向上寻找,直到找到一个节点,它是该节点父节点的左孩子为止。
前置 -- 的核心逻辑:
与 ++ 相反,遵循'右根左'的顺序。
- 左子树存在:下一个节点是当前节点的左子树中最右边的节点。
- 左子树为空:向上回溯。如果当前节点是其父节点的右孩子,则父节点即为上一个节点;如果是左孩子,则继续向上寻找,直到找到一个节点,它是该节点父节点的右孩子为止。


