在 C++ STL 中,map 和 set 是常用的关联式容器,它们的底层实现都依赖于红黑树这一高效的数据结构。红黑树作为一种自平衡二叉搜索树,既保证了查找、插入、删除操作的时间复杂度为 O (log n),又通过特定的规则维持树的平衡,避免了普通二叉搜索树在极端情况下退化为链表的问题。本文将从 STL 源码框架分析入手,详细讲解如何基于红黑树封装实现 map 和 set 容器,帮助大家深入理解关联式容器的底层机制。下面就让我们正式开始吧!
要实现 map 和 set,首先需要理解 STL 中 map 和 set 的底层实现逻辑。SGI-STL3.0 版本中,map 和 set 的源代码主要分布在stl_map.h、stl_set.h和stl_tree.h三个头文件中,其核心设计思想是通过红黑树的泛型实现,同时支撑 map(key-value 映射)和 set(key 集合)两种场景。
STL 中 map 和 set 的实现高度依赖红黑树(rb_tree),其头文件包含关系如下:
stl_tree.h、stl_set.h和stl_multiset.hstl_tree.h、stl_map.h和stl_multimap.h这种依赖关系的本质是:set 和 map 本身不直接实现数据结构,而是通过封装红黑树,对外提供特定的接口,形成符合自身语义的容器。
template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
public:
typedef Key key_type;
typedef Key value_type; // set的value_type就是key本身
private:
// 红黑树类型定义,作为set的内部存储容器
typedef rb_tree<key_type, value_type, identity<value_type>, key_compare, Alloc> rep_type;
rep_type t; // 红黑树对象,真正存储数据
};template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
public:
typedef Key key_type;
typedef T mapped_type; // map的value(映射值)类型
typedef pair<const Key, T> value_type; // 红黑树存储的元素类型(key-value对)
private:
// 红黑树类型定义,作为map的内部存储容器
typedef rb_tree<key_type, value_type, select1st<value_type>, key_compare, Alloc> rep_type;
rep_type t; // 红黑树对象,真正存储数据
};红黑树的类模板定义是实现 map 和 set 复用的关键,其核心模板参数如下:
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc = alloc>
class rb_tree {
protected:
typedef __rb_tree_node<Value> rb_tree_node; // 红黑树节点类型
typedef rb_tree_node* link_type;
size_type node_count; // 树中节点个数
link_type header; // 头节点(哨兵节点)
public:
// 插入唯一元素(不允许重复key)
pair<iterator, bool> insert_unique(const value_type& x);
// 根据key删除元素
size_type erase(const key_type& x);
// 根据key查找元素
iterator find(const key_type& x);
};STL 通过红黑树的泛型设计,实现了 map 和 set 的复用,其关键在于以下两点:
红黑树的第二个模板参数Value指定了节点中存储的数据类型:
Value传入Key(即 set 的 value_type 就是 key),因此红黑树节点存储的是单个 key 值。Value传入pair<const Key, T>,因此红黑树节点存储的是 key-value 键值对。通过这种方式,一颗红黑树既可以作为 set 的底层存储(存储单个 key),也可以作为 map 的底层存储(存储 key-value 对),实现了数据结构的复用。
红黑树的查找、插入、删除操作都需要基于 key 进行比较,但节点存储的是Value类型(可能是 key,也可能是 key-value 对)。为了从Value中提取出 key,红黑树引入了第三个模板参数KeyOfValue—— 一个仿函数,用于从Value对象中获取 key 值。
KeyOfValue是identity<value_type>,直接返回Value本身(因为 set 的Value就是 key)。KeyOfValue是select1st<value_type>,返回pair<const Key, T>中的第一个元素(即 key)。 红黑树的第一个模板参数Key是查找、删除操作的参数类型:
Key与Value类型相同,查找(find)、删除(erase)时直接传入 key 即可。Key是 key 的类型,而Value是pair<const Key, T>,查找、删除时传入 key,红黑树通过KeyOfValue仿函数从节点的Value中提取 key 进行比较。这一设计解决了 map 中 “插入元素是 key-value 对,而查找删除是 key” 的场景差异。
下为对STL源码的框图分析:

STL 源码的模板参数命名风格其实是并不完全统一的:
Key命名;Key和T(映射值类型)命名;Key和Value命名。这种命名差异可能会给初学者带来困惑,但核心逻辑其实是一致的 —— 通过泛型参数的灵活配置,实现数据结构的复用。
基于 STL 的设计思想,我们将来模拟实现 map 和 set,核心步骤如下:
operator[]运算符,支持便捷的 key-value 访问和插入。为了简化实现并提高可读性,我们可以对 STL 源码的命名和结构做出如下调整:
K(key 类型)、T(节点存储数据类型)、KeyOfT(key 提取仿函数)。nullptr表示迭代器的end()。红黑树是 map 和 set 的底层核心,需要实现节点结构、插入(含平衡调整)、旋转、迭代器等核心功能。由于在之前的博客中已经为大家介绍过了红黑树的实现,这里就不再过多赘述了,感兴趣的话可以参考一下之前的博客:C++进阶:(七)红黑树深度解析与 C++ 实现
#include <iostream>
#include <utility>
using namespace std;
// 红黑树节点颜色枚举
enum Colour {
RED, // 红色节点
BLACK // 黑色节点
};
// 红黑树节点结构
template <class T>
struct RBTreeNode {
T _data; // 节点存储的数据(set中是key,map中是pair<const K, V>)
RBTreeNode<T>* _left; // 左孩子
RBTreeNode<T>* _right; // 右孩子
RBTreeNode<T>* _parent; // 父节点
Colour _col; // 节点颜色
// 构造函数
RBTreeNode(const T& data)
: _data(data)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED) // 新节点默认红色(减少平衡调整次数)
{}
};
// 红黑树迭代器
template <class T, class Ref, class Ptr>
struct RBTreeIterator {
typedef RBTreeNode<T> Node;
typedef RBTreeIterator<T, Ref, Ptr> Self;
Node* _node; // 当前迭代器指向的节点
Node* _root; // 红黑树根节点(用于--end()时查找最右节点)
// 构造函数
RBTreeIterator(Node* node, Node* root)
: _node(node)
, _root(root)
{}
// 前置++:迭代器指向中序遍历的下一个节点
Self& operator++() {
if (_node->_right) {
// 情况1:右子树不为空,下一个节点是右子树的最左节点
Node* leftMost = _node->_right;
while (leftMost->_left) {
leftMost = leftMost->_left;
}
_node = leftMost;
} else {
// 情况2:右子树为空,向上找第一个“当前节点是父节点左孩子”的祖先
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right) {
cur = parent;
parent = cur->_parent;
}
_node = parent; // 找到祖先或nullptr(end())
}
return *this;
}
// 前置--:迭代器指向中序遍历的前一个节点
Self& operator--() {
if (_node == nullptr) {
// 情况1:当前是end(),--后指向最右节点(中序最后一个节点)
Node* rightMost = _root;
while (rightMost && rightMost->_right) {
rightMost = rightMost->_right;
}
_node = rightMost;
} else if (_node->_left) {
// 情况2:左子树不为空,前一个节点是左子树的最右节点
Node* rightMost = _node->_left;
while (rightMost->_right) {
rightMost = rightMost->_right;
}
_node = rightMost;
} else {
// 情况3:左子树为空,向上找第一个“当前节点是父节点右孩子”的祖先
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left) {
cur = parent;
parent = cur->_parent;
}
_node = parent;
}
return *this;
}
// 解引用运算符:返回节点数据的引用
Ref operator*() {
return _node->_data;
}
// 箭头运算符:返回节点数据的指针
Ptr operator->() {
return &_node->_data;
}
// 相等运算符
bool operator==(const Self& s) const {
return _node == s._node;
}
// 不相等运算符
bool operator!=(const Self& s) const {
return _node != s._node;
}
};
// 红黑树类模板
template <class K, class T, class KeyOfT>
class RBTree {
typedef RBTreeNode<T> Node;
public:
typedef RBTreeIterator<T, T&, T*> Iterator; // 普通迭代器
typedef RBTreeIterator<T, const T&, const T*> ConstIterator; // 常量迭代器
// 构造函数
RBTree() : _root(nullptr) {}
// 析构函数
~RBTree() {
Destroy(_root);
_root = nullptr;
}
// 迭代器 begin():指向中序遍历的第一个节点(最左节点)
Iterator Begin() {
Node* leftMost = _root;
while (leftMost && leftMost->_left) {
leftMost = leftMost->_left;
}
return Iterator(leftMost, _root);
}
// 常量迭代器 begin()
ConstIterator Begin() const {
Node* leftMost = _root;
while (leftMost && leftMost->_left) {
leftMost = leftMost->_left;
}
return ConstIterator(leftMost, _root);
}
// 迭代器 end():指向nullptr(中序遍历的末尾)
Iterator End() {
return Iterator(nullptr, _root);
}
// 常量迭代器 end()
ConstIterator End() const {
return ConstIterator(nullptr, _root);
}
// 插入元素:返回pair<迭代器, bool>,bool表示是否插入成功(避免重复key)
pair<Iterator, bool> Insert(const T& data) {
// 情况1:树为空,直接创建根节点(根节点为黑色)
if (_root == nullptr) {
_root = new Node(data);
_root->_col = BLACK;
return make_pair(Iterator(_root, _root), true);
}
KeyOfT kot; // key提取仿函数
Node* parent = nullptr;
Node* cur = _root;
// 查找插入位置
while (cur) {
if (kot(cur->_data) < kot(data)) {
parent = cur;
cur = cur->_right;
} else if (kot(cur->_data) > kot(data)) {
parent = cur;
cur = cur->_left;
} else {
// 存在重复key,插入失败
return make_pair(Iterator(cur, _root), false);
}
}
// 创建新节点(默认红色),并链接到父节点
cur = new Node(data);
Node* newNode = cur;
cur->_col = RED;
if (kot(parent->_data) < kot(data)) {
parent->_right = cur;
} else {
parent->_left = cur;
}
cur->_parent = parent;
// 平衡调整:父节点为红色时需要调整(红黑树不能有连续红色节点)
while (parent && parent->_col == RED) {
Node* grandfather = parent->_parent; // 祖父节点(一定存在,因为根节点是黑色)
// 情况1:父节点是祖父节点的左孩子
if (parent == grandfather->_left) {
Node* uncle = grandfather->_right; // 叔叔节点
// 子情况1.1:叔叔节点存在且为红色(变色调整)
if (uncle && uncle->_col == RED) {
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
// 向上继续调整(祖父节点变为红色,可能与曾祖父节点冲突)
cur = grandfather;
parent = cur->_parent;
} else {
// 子情况1.2:叔叔节点不存在或为黑色(旋转+变色调整)
// 子情况1.2.1:当前节点是父节点的左孩子(右单旋)
if (cur == parent->_left) {
RotateR(grandfather); // 对祖父节点右旋
parent->_col = BLACK; // 父节点变黑
grandfather->_col = RED; // 祖父节点变红
} else {
// 子情况1.2.2:当前节点是父节点的右孩子(左旋+右旋)
RotateL(parent); // 对父节点左旋
RotateR(grandfather); // 对祖父节点右旋
cur->_col = BLACK; // 当前节点变黑
grandfather->_col = RED; // 祖父节点变红
}
break; // 调整完成,跳出循环
}
} else {
// 情况2:父节点是祖父节点的右孩子(与情况1对称)
Node* uncle = grandfather->_left; // 叔叔节点
// 子情况2.1:叔叔节点存在且为红色(变色调整)
if (uncle && uncle->_col == RED) {
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
// 向上继续调整
cur = grandfather;
parent = cur->_parent;
} else {
// 子情况2.2:叔叔节点不存在或为黑色(旋转+变色调整)
// 子情况2.2.1:当前节点是父节点的右孩子(左单旋)
if (cur == parent->_right) {
RotateL(grandfather); // 对祖父节点左旋
parent->_col = BLACK; // 父节点变黑
grandfather->_col = RED; // 祖父节点变红
} else {
// 子情况2.2.2:当前节点是父节点的左孩子(右旋+左旋)
RotateR(parent); // 对父节点右旋
RotateL(grandfather); // 对祖父节点左旋
cur->_col = BLACK; // 当前节点变黑
grandfather->_col = RED; // 祖父节点变红
}
break; // 调整完成,跳出循环
}
}
}
// 确保根节点始终为黑色
_root->_col = BLACK;
return make_pair(Iterator(newNode, _root), true);
}
// 根据key查找元素:返回迭代器
Iterator Find(const K& key) {
Node* cur = _root;
KeyOfT kot;
while (cur) {
if (kot(cur->_data) < key) {
cur = cur->_right;
} else if (kot(cur->_data) > key) {
cur = cur->_left;
} else {
// 找到key,返回对应的迭代器
return Iterator(cur, _root);
}
}
// 未找到,返回end()
return End();
}
private:
// 左旋转:以parent为旋转中心
void RotateL(Node* parent) {
Node* subR = parent->_right; // 父节点的右孩子
Node* subRL = subR->_left; // 右孩子的左子树
// 步骤1:将subRL链接到parent的右孩子
parent->_right = subRL;
if (subRL) {
subRL->_parent = parent;
}
// 步骤2:处理parent的父节点
Node* parentParent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
// 步骤3:如果parent是根节点,更新根;否则链接到祖父节点
if (parentParent == nullptr) {
_root = subR;
subR->_parent = nullptr;
} else {
if (parent == parentParent->_left) {
parentParent->_left = subR;
} else {
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
}
// 右旋转:以parent为旋转中心
void RotateR(Node* parent) {
Node* subL = parent->_left; // 父节点的左孩子
Node* subLR = subL->_right; // 左孩子的右子树
// 步骤1:将subLR链接到parent的左孩子
parent->_left = subLR;
if (subLR) {
subLR->_parent = parent;
}
// 步骤2:处理parent的父节点
Node* parentParent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
// 步骤3:如果parent是根节点,更新根;否则链接到祖父节点
if (parentParent == nullptr) {
_root = subL;
subL->_parent = nullptr;
} else {
if (parent == parentParent->_left) {
parentParent->_left = subL;
} else {
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
}
// 递归销毁红黑树
void Destroy(Node* root) {
if (root == nullptr) {
return;
}
Destroy(root->_left);
Destroy(root->_right);
delete root;
}
private:
Node* _root; // 红黑树根节点
}; set 是 key 的集合,其特点是 key 唯一且有序,key 不可修改。通过封装红黑树,并重定义KeyOfT仿函数实现。代码如下:
#include "RBTree.h"
#include <iostream>
using namespace std;
namespace bit {
template <class K>
class set {
// 自定义KeyOfT仿函数:从节点数据(K类型)中提取key
struct SetKeyOfT {
const K& operator()(const K& key) {
return key;
}
};
public:
// 迭代器类型定义(复用红黑树的迭代器)
typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;
// 普通迭代器 begin()
iterator begin() {
return _t.Begin();
}
// 普通迭代器 end()
iterator end() {
return _t.End();
}
// 常量迭代器 begin()
const_iterator begin() const {
return _t.Begin();
}
// 常量迭代器 end()
const_iterator end() const {
return _t.End();
}
// 插入元素:返回pair<iterator, bool>,bool表示是否插入成功
pair<iterator, bool> insert(const K& key) {
return _t.Insert(key);
}
// 根据key查找元素
iterator find(const K& key) {
return _t.Find(key);
}
private:
// 红黑树对象:第二个模板参数为const K,确保key不可修改
RBTree<K, const K, SetKeyOfT> _t;
};
// 测试set
void test_set() {
bit::set<int> s;
int a[] = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
for (auto e : a) {
auto ret = s.insert(e);
if (ret.second) {
cout << "插入成功:" << e << endl;
} else {
cout << "插入失败(重复key):" << e << endl;
}
}
// 遍历set(中序遍历,有序)
cout << "set遍历(有序):";
for (auto e : s) {
cout << e << " ";
}
cout << endl;
// 测试find
auto it = s.find(5);
if (it != s.end()) {
cout << "找到key:" << *it << endl;
} else {
cout << "未找到key:5" << endl;
}
// 测试key不可修改(编译报错)
// *it = 100; // 错误:set的key是const类型,不可修改
}
} map 是 key-value 的映射容器,其特点是 key 唯一且有序,key 不可修改但 value 可修改。同样通过封装红黑树实现,核心是KeyOfT仿函数和operator[]的实现。
#include "RBTree.h"
#include <iostream>
#include <string>
using namespace std;
namespace bit {
template <class K, class V>
class map {
// 自定义KeyOfT仿函数:从pair<const K, V>中提取key(first成员)
struct MapKeyOfT {
const K& operator()(const pair<const K, V>& kv) {
return kv.first;
}
};
public:
// 迭代器类型定义(复用红黑树的迭代器)
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;
// 普通迭代器 begin()
iterator begin() {
return _t.Begin();
}
// 普通迭代器 end()
iterator end() {
return _t.End();
}
// 常量迭代器 begin()
const_iterator begin() const {
return _t.Begin();
}
// 常量迭代器 end()
const_iterator end() const {
return _t.End();
}
// 插入元素:返回pair<iterator, bool>,bool表示是否插入成功
pair<iterator, bool> insert(const pair<K, V>& kv) {
return _t.Insert(kv);
}
// 根据key查找元素
iterator find(const K& key) {
return _t.Find(key);
}
// operator[]:支持map[key]访问和插入
V& operator[](const K& key) {
// 插入key-value对(value为默认构造),返回pair<iterator, bool>
pair<iterator, bool> ret = insert(make_pair(key, V()));
// 返回value的引用(可修改)
return ret.first->second;
}
private:
// 红黑树对象:节点存储pair<const K, V>,确保key不可修改
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
// 测试map
void test_map() {
bit::map<string, string> dict;
// 插入元素
dict.insert({"sort", "排序"});
dict.insert({"left", "左边"});
dict.insert({"right", "右边"});
// 使用operator[]修改value
dict["left"] = "左边(剩余)";
// 使用operator[]插入新元素(key不存在时自动插入)
dict["insert"] = "插入";
dict["string"]; // value为默认构造(空字符串)
// 遍历map(中序遍历,按key有序)
cout << "map遍历(按key有序):" << endl;
for (auto it = dict.begin(); it != dict.end(); ++it) {
// 测试key不可修改(编译报错)
// it->first += 'x'; // 错误:key是const类型
// 修改value
it->second += "x";
cout << it->first << " : " << it->second << endl;
}
// 测试find
auto it = dict.find("insert");
if (it != dict.end()) {
cout << "找到key:" << it->first << ",value:" << it->second << endl;
} else {
cout << "未找到key:insert" << endl;
}
}
}红黑树的核心是通过以下规则维持平衡的,确保所有操作的时间复杂度为 O (log n):
当插入新节点(默认红色)时,可能会破坏规则 4,因此需要通过变色或旋转 + 变色进行调整。调整的核心思路是:
map 和 set 的迭代器需要支持中序遍历(保证有序性),其核心是operator++和operator--的实现:
end()(nullptr):前一个节点是树的最右节点(中序遍历的最后一个节点)。const K,因此迭代器解引用后得到的是const K&,无法修改。pair<const K, V>,其中 key 被定义为const,因此迭代器访问it->first时是const K&,无法修改;而it->second是V&,可以修改。 map 的operator[]是最常用的接口,其实现逻辑如下:
V& operator[](const K& key) {
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}insert插入(key, V())(value 为默认构造)。insert返回已存在节点的迭代器和false,直接返回该节点的 value 引用。insert插入新节点并返回新节点的迭代器和true,返回新节点的 value 引用。这种实现支持三种用法:
dict["left"]。dict["left"] = "左边(剩余)"。dict["insert"] = "插入"。通过本文的实现,希望大家可以深入理解 map 和 set 的底层机制,掌握红黑树的核心原理和泛型编程的思想,为后续学习更复杂的容器和数据结构打下坚实基础。