跳到主要内容
C++ STL 容器:基于红黑树模拟实现 map 和 set | 极客日志
C++
C++ STL 容器:基于红黑树模拟实现 map 和 set C++ STL 容器 map 与 set 基于红黑树实现,涉及节点结构、迭代器设计、插入平衡调整及泛型封装。重点讲解如何通过 KeyOfT 仿函数适配不同存储类型,利用 const 迭代器和 pair 成员修饰确保 key 不可变而 value 可变。包含完整的红黑树类模板、set/map 封装代码及测试用例,深入剖析 STL 底层机制与源码实现细节。
Kubernet 发布于 2026/3/22 更新于 2026/4/24 1 浏览C++ STL 容器:基于红黑树模拟实现 map 和 set
前言
本文深入解析 C++ STL 中 map 与 set 容器的底层实现原理,基于红黑树数据结构进行模拟编码。在阅读本文前,建议读者先了解红黑树的基本概念与模拟实现,这将有助于理解后续封装细节。
一、了解 STL 库中的 map/set 的底层
map 和 set 是通过红黑树封装实现的。STL 库中通过泛型模板设计,让 map 和 set 使用同一份类模板 rb_tree,但传入不同的模板参数实例化出不同的类型。
观察 STL 库中的 set 定义,它封装了一个 rb_tree 对象 rep_type,传入了 key_type 和 value_type,这两个的类型都是 Key 类型的。这是因为 set 只存储 key,但在泛型设计中需要统一接口。
对于 map,value_type 是 pair<const Key, T> 类型。map 实际存储的是键值对,但为了适配红黑树的通用节点结构,将 pair 作为存储内容。
红黑树节点定义中使用模板参数 Value 来定义节点中实际存储的内容。例如 map 传入 <Key, pair<Key,Value>>,红黑树节点就存储 pair;set 传入 <Key, Key>,节点就存储 Key。
第一个模板参数 Key 的存在是为了支持 find、erase 等函数。这些操作需要直接通过 Key 查找,如果仅靠存储类型,map 无法直接从 pair 中提取 Key 进行泛型比较,因此必须显式传入 Key 类型以便上层封装调用。
template <class Key ,class Compare = less<Key>,class Alloc= alloc>
class set {
public :
typedef Key key_type;
typedef Key value_type;
private :
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 pair<const Key, T> value_type;
private :
typedef rb_tree<key_type, value_type, select1st<value_type>, key_compare, Alloc> rep_type;
rep_type t;
};
二、红黑树的迭代器 为了实现 map/set 的遍历,我们需要模拟实现红黑树的迭代器。迭代器封装了节点的指针,模拟指针行为。
改进红黑树模板参数为 template<typename K, typename T>,其中 K 表示 Key,T 表示节点存储的实际类型(Type)。
节点结构体中增加 _data 成员变量存储 T 类型数据。
迭代器类模板 __RBTree_iterator 封装节点指针 _node,重载 operator*、operator->、operator++、operator-- 等操作符。
operator++ 逻辑:若右子树存在,则找右子树的最左节点;否则向上寻找父节点,直到当前节点是其父节点的左孩子。
operator-- 逻辑:若左子树存在,则找左子树的最右节点;否则向上寻找父节点,直到当前节点是其父节点的右孩子。
template <typename T>
struct RBTreeNode {
T _data;
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
Colour _col;
};
template <typename T,typename Ptr,typename Ref>
struct __RBTree_iterator {
typedef RBTreeNode<T> Node;
typedef __RBTree_iterator<T, Ptr, Ref> Self;
typedef __RBTree_iterator<T, T*, T&> Iterator;
Node* _node;
__RBTree_iterator(Node* root):_node(root){}
__RBTree_iterator(const Iterator& it):_node(it._node){}
Ref operator *(){ return _node->_data; }
Ptr operator ->(){ return &_node->_data; }
bool operator !=(const Self& it){ return _node != it._node; }
Self& operator ++() {
Node* cur = _node;
Node* parent = cur->_parent;
if (cur->_right) {
Node* RightMin = cur->_right;
while (RightMin->_left) RightMin = RightMin->_left;
_node = RightMin;
} else {
while (parent && cur == parent->_right) {
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this ;
}
Self& operator --() {
Node* cur = _node;
Node* parent = cur->_parent;
if (cur->_left) {
Node* LeftMax = cur->_left;
while (LeftMax->_right) LeftMax = LeftMax->_right;
_node = LeftMax;
} else {
while (parent && cur == parent->_left) {
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this ;
}
};
三、红黑树的插入 插入时需要找到合适的位置。由于红黑树节点存储的是 T 类型,而我们需要根据 Key 进行比较,因此引入 KeyOfT 仿函数。
KeyOfT 是一个类模板,重载 operator(),用于从存储的数据中提取 Key。
插入时利用 kot(data) 获取 Key 进行比较,从而适配 map 和 set 的不同存储结构。
保持原有的旋转和变色逻辑不变,确保红黑树性质。
template <typename K,typename T,typename KeyOfT>
class RBTree {
typedef RBTreeNode<T> Node;
KeyOfT kot;
bool Insert (const T& data) {
if (_root == nullptr ) {
_root = new Node (data);
_root->_col = BLACK;
return true ;
}
return true ;
}
};
四、map/set 的封装
set 的封装
命名空间避免冲突,模板参数仅需 K。
实现 SetKeyOfT 仿函数,直接返回 data。
迭代器需添加 typename 关键字以告知编译器这是类型而非变量。
注意 Set 的 key 不可修改,因此迭代器返回 const 引用。
namespace wzx {
template <typename K>
class set {
private :
struct SetKeyOfT {
const K& operator () (const K& data) { return data; }
};
public :
typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;
iterator begin () { return _t .begin (); }
iterator end () { return _t .end (); }
bool Insert (const K& key) { return _t .Insert (key); }
private :
RBTree<K, K, SetKeyOfT> _t ;
};
}
map 的封装
模板参数 K, V。
实现 MapKeyOfT 仿函数,返回 kv.first。
存储类型为 pair<const K, V>,确保 key 不可变。
namespace wzx {
template <typename K,typename V>
class map {
private :
struct MapKeyOfT {
const K& operator () (const pair<K, V>& kv) { return kv.first; }
};
public :
typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;
iterator begin () { return _t .begin (); }
iterator end () { return _t .end (); }
bool Insert (const pair<K, V> kv) { return _t .Insert (kv); }
private :
RBTree<K, pair<K, V>, MapKeyOfT> _t ;
};
}
五、测试 编写测试代码验证基本功能,包括插入、遍历以及 key/value 的可修改性检查。
#include <iostream>
#include "MyMap.h"
#include "MySet.h"
using namespace std;
int main () {
wzx::set<int > s;
s.Insert (5 ); s.Insert (2 ); s.Insert (9 ); s.Insert (1 );
wzx::set<int >::iterator it = s.begin ();
while (it != s.end ()) {
cout << *it << ' ' ;
++it;
}
cout << endl;
wzx::map<int ,int > m;
m.Insert (make_pair (3 ,3 )); m.Insert (make_pair (1 ,1 ));
wzx::map<int ,int >::iterator mit = m.begin ();
while (mit != m.end ()) {
cout << mit->first << ':' << mit->second << endl;
++mit;
}
return 0 ;
}
六、学习 STL 库中的实现
set 如何保证 key 不被修改 STL 中 set 的普通迭代器实际上是由 const 迭代器重命名而来的。这意味着即使使用普通迭代器访问,也无法修改 key 的值。
map 如何保证 key 不被修改 map 中通过将 pair 的 key 成员定义为 const K,即 pair<const K, V>。这样在编译期就限制了 key 的修改,同时允许 value 被修改。
八、set 的改进
红黑树的 const 迭代器 通过模板参数控制 operator* 返回引用的类型。const 迭代器返回 const T&,普通迭代器返回 T&。
template <typename T,typename Ptr,typename Ref>
struct __RBTree_iterator {
typedef __RBTree_iterator<T, T*, T&> iterator;
typedef __RBTree_iterator<T,const T*,const T&> const_iterator;
};
改进 set 将 set 的 iterator 定义为 const_iterator,并实现 const 版本的 begin/end。
namespace wzx {
template <typename K>
class set {
public :
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
const_iterator begin () const { return _t .begin (); }
const_iterator end () const { return _t .end (); }
};
}
九、map 的改进
存储类型改为 pair<const K, V>。
添加 const 迭代器支持。
实现 operator[],内部调用 insert,返回 value 引用。
V& operator [](const K& key) {
pair<iterator,bool > kv = Insert (make_pair (key,V ()));
return kv.first->second;
}
十、完善 set 的 Insert 修改 Insert 返回值以匹配 STL 标准,返回 pair<iterator,bool>。由于 set 的 iterator 底层是 const 迭代器,需处理类型转换问题,通过构造函数重载完成浅拷贝。
十一、源代码
MyMap.h #pragma once
#include "RBTree.h"
namespace wzx {
template <typename K,typename V>
class map {
private :
struct MapKeyOfT {
const K& operator () (const pair<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>::const_iterator const_iterator;
iterator begin () { return _t .begin (); }
iterator end () { return _t .end (); }
const_iterator begin () const { return _t .begin (); }
const_iterator end () const { return _t .end (); }
V& operator [](const K& key) {
pair<iterator,bool > kv = Insert (make_pair (key,V ()));
return kv.first->second;
}
pair<iterator,bool > Insert (const pair<const K, V> kv) { return _t .Insert (kv); }
private :
RBTree<K, pair<const K, V>, MapKeyOfT> _t ;
};
}
MySet.h #pragma once
#include "RBTree.h"
namespace wzx {
template <typename K>
class set {
private :
struct SetKeyOfT {
const K& operator () (const K& data) { return data; }
};
public :
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
iterator begin () { return _t .begin (); }
iterator end () { return _t .end (); }
const_iterator begin () const { return _t .begin (); }
const_iterator end () const { return _t .end (); }
pair<iterator,bool > Insert (const K& key) { return _t .Insert (key); }
private :
RBTree<K, K, SetKeyOfT> _t ;
};
}
RBTree.h #pragma once
#include <iostream>
using namespace std;
enum Colour { RED, BLACK };
template <typename T>
struct RBTreeNode {
T _data;
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 <typename T,typename Ptr,typename Ref>
struct __RBTree_iterator {
typedef RBTreeNode<T> Node;
typedef __RBTree_iterator<T, Ptr, Ref> Self;
typedef __RBTree_iterator<T, T*, T&> Iterator;
Node* _node;
__RBTree_iterator(Node* root):_node(root){}
__RBTree_iterator(const Iterator& it):_node(it._node){}
Ref operator *(){ return _node->_data; }
Ptr operator ->(){ return &_node->_data; }
bool operator !=(const Self& it){ return _node != it._node; }
Self& operator ++();
Self& operator --();
};
template <typename K,typename T,typename KeyOfT>
class RBTree {
typedef RBTreeNode<T> Node;
public :
RBTree ():_root(nullptr ){}
typedef __RBTree_iterator<T, T*, T&> iterator;
typedef __RBTree_iterator<T,const T*,const T&> const_iterator;
iterator begin () {
Node* cur = _root;
while (cur->_left) cur = cur->_left;
return iterator (cur);
}
iterator end () { return iterator (nullptr ); }
const_iterator begin () const {
Node* cur = _root;
while (cur->_left) cur = cur->_left;
return const_iterator (cur);
}
const_iterator end () const { return const_iterator (nullptr ); }
KeyOfT kot;
pair<iterator,bool > Insert (const T& data) {
if (_root == nullptr ) {
_root = new Node (data);
_root->_col = BLACK;
return make_pair (iterator (_root),true );
}
Node* parent = nullptr ;
Node* cur = _root;
while (cur) {
if (kot (data)>kot (cur->_data)) { parent = cur; cur = cur->_right; }
else if (kot (data)<kot (cur->_data)) { parent = cur; cur = cur->_left; }
else { return make_pair (iterator (cur),false ); }
}
cur = new Node (data);
cur->_col = RED;
Node* newnode = cur;
cur->_parent = parent;
if (kot (cur->_data)>kot (parent->_data)) parent->_right = cur;
else parent->_left = cur;
return make_pair (iterator (newnode),true );
}
private :
Node* _root;
void RotateL (Node* parent) ;
void RotateR (Node* parent) ;
};
总结 本文详细讲解了 C++ STL 中 map 和 set 基于红黑树的模拟实现过程。从底层数据结构设计、迭代器封装、插入平衡调整到泛型封装技巧,涵盖了源码级实现的关键点。特别是针对 key 不可修改的约束机制以及 insert 接口的完善,提供了完整的参考代码,适合希望深入理解 C++ 容器源码的开发者阅读。
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online