目录
[一、STL 源码框架分析](#一、STL 源码框架分析)
[1.1 核心头文件依赖关系](#1.1 核心头文件依赖关系)
[1.2 关键类模板定义](#1.2 关键类模板定义)
[1.2.1 set 的类模板结构](#1.2.1 set 的类模板结构)
[1.2.2 map 的类模板结构](#1.2.2 map 的类模板结构)
[1.2.3 红黑树(rb_tree)的核心模板参数](#1.2.3 红黑树(rb_tree)的核心模板参数)
[1.3 核心设计思想:泛型复用](#1.3 核心设计思想:泛型复用)
[1.3.1 节点存储类型的灵活控制](#1.3.1 节点存储类型的灵活控制)
[1.3.2 key 提取机制:KeyOfValue 仿函数](#1.3.2 key 提取机制:KeyOfValue 仿函数)
[1.3.3 模板参数 Key 的作用](#1.3.3 模板参数 Key 的作用)
[1.4 关于源码设计的小吐槽](#1.4 关于源码设计的小吐槽)
[2.1 关键调整与优化](#2.1 关键调整与优化)
[3.1 红黑树的实现(RBTree.h)](#3.1 红黑树的实现(RBTree.h))
[3.2 set 的实现(Myset.h)](#3.2 set 的实现(Myset.h))
[3.3 map 的实现(Mymap.h)](#3.3 map 的实现(Mymap.h))
[4.1 红黑树的平衡规则与调整](#4.1 红黑树的平衡规则与调整)
[4.2 迭代器的实现原理](#4.2 迭代器的实现原理)
[4.2.1 operator++(中序下一个节点)](#4.2.1 operator++(中序下一个节点))
[4.2.2 operator--(中序前一个节点)](#4.2.2 operator--(中序前一个节点))
[4.3 key 不可修改的实现](#4.3 key 不可修改的实现)
[4.4 map 的 operator [] 实现](#4.4 map 的 operator [] 实现)
前言
在 C++ STL 中,map 和 set 是常用的关联式容器,它们的底层实现都依赖于红黑树这一高效的数据结构。红黑树作为一种自平衡二叉搜索树,既保证了查找、插入、删除操作的时间复杂度为 O (log n),又通过特定的规则维持树的平衡,避免了普通二叉搜索树在极端情况下退化为链表的问题。本文将从 STL 源码框架分析入手,详细讲解如何基于红黑树封装实现 map 和 set 容器,帮助大家深入理解关联式容器的底层机制。下面就让我们正式开始吧!
一、STL 源码框架分析
要实现 map 和 set,首先需要理解 STL 中 map 和 set 的底层实现逻辑。SGI-STL3.0 版本中,map 和 set 的源代码主要分布在stl_map.h、stl_set.h和stl_tree.h三个头文件中,其核心设计思想是通过红黑树的泛型实现,同时支撑 map(key-value 映射)和 set(key 集合)两种场景。
1.1 核心头文件依赖关系
STL 中 map 和 set 的实现高度依赖红黑树(rb_tree),其头文件包含关系如下:
- set 的实现依赖
stl_tree.h、stl_set.h和stl_multiset.h - map 的实现依赖
stl_tree.h、stl_map.h和stl_multimap.h
这种依赖关系的本质是:set 和 map 本身不直接实现数据结构,而是通过封装红黑树,对外提供特定的接口,形成符合自身语义的容器。
1.2 关键类模板定义
1.2.1 set 的类模板结构
cpp
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; // 红黑树对象,真正存储数据
};
1.2.2 map 的类模板结构
cpp
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; // 红黑树对象,真正存储数据
};
1.2.3 红黑树(rb_tree)的核心模板参数
红黑树的类模板定义是实现 map 和 set 复用的关键,其核心模板参数如下:
cpp
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);
};
1.3 核心设计思想:泛型复用
STL 通过红黑树的泛型设计,实现了 map 和 set 的复用,其关键在于以下两点:
1.3.1 节点存储类型的灵活控制
红黑树的第二个模板参数**Value指定了节点中存储的数据类型**:
- set 实例化红黑树时,
Value传入Key(即 set 的 value_type就是 key),因此红黑树节点存储的是单个 key 值。 - map 实例化红黑树时,
Value传入pair<const Key, T>,因此红黑树节点存储的是key-value 键值对。
通过这种方式,一颗红黑树既可以作为 set 的底层存储(存储单个 key),也可以作为 map 的底层存储(存储 key-value 对),实现了数据结构的复用。
1.3.2 key 提取机制:KeyOfValue 仿函数
红黑树的查找、插入、删除操作都需要基于 key 进行比较 ,但节点存储的是Value类型 (可能是 key,也可能是 key-value 对)。为了从Value中提取出 key,红黑树引入了第三个模板参数**KeyOfValue------ 一个仿函数** ,用于从Value对象中获取 key 值。
- set 对应的
KeyOfValue是identity<value_type>,直接返回Value本身 (因为 set 的Value就是 key)。 - map 对应的
KeyOfValue是select1st<value_type>,返回pair<const Key, T>中的第一个元素(即 key)。
1.3.3 模板参数 Key 的作用
红黑树的第一个模板参数**Key是查找、删除操作的参数类型**:
- 对于 set,
Key与Value类型相同,查找(find)、删除(erase)时直接传入 key 即可。 - 对于 map,
Key是 key 的类型,而**Value是pair<const Key, T>**,查找、删除时传入 key,红黑树通过KeyOfValue仿函数从节点的Value中提取 key 进行比较。
这一设计解决了 map 中 "插入元素是 key-value 对,而查找删除是 key" 的场景差异。
下为对STL源码的框图分析:
1.4 关于源码设计的小吐槽
STL 源码的模板参数命名风格其实是并不完全统一的:
- set 的模板参数用
Key命名; - map 的模板参数用
Key和T(映射值类型)命名; - 红黑树的模板参数用
Key和Value命名。
这种命名差异可能会给初学者带来困惑,但核心逻辑其实是一致的 ------ 通过泛型参数的灵活配置,实现数据结构的复用。
二、模拟实现的核心思路
基于 STL 的设计思想,我们将来模拟实现 map 和 set,核心步骤如下:
- 实现一个通用的红黑树类(RBTree),支持泛型参数配置。
- 在 map 和 set 中封装红黑树 ,通过自定义仿函数(KeyOfT)实现 key 的提取。
- 实现迭代器(iterator) ,支持中序遍历(保证 map 和 set 的有序性)。
- 解决 key 不可修改的问题(set 的 key 完全不可改,map 的 key 不可改但 value 可改)。
- 实现 map 的**
operator[]**运算符,支持便捷的 key-value 访问和插入。
2.1 关键调整与优化
为了简化实现并提高可读性,我们可以对 STL 源码的命名和结构做出如下调整:
- 红黑树的模板参数统一命名为**
K(key 类型)、T(节点存储数据类型)、KeyOfT(key 提取仿函数)**。 - 去掉 STL 中复杂的哨兵节点(header),用nullptr表示迭代器的end()。
- 红黑树的节点结构直接明确定义,避免源码中嵌套的结构体依赖。
三、完整代码实现
3.1 红黑树的实现(RBTree.h)
红黑树是 map 和 set 的底层核心,需要实现节点结构、插入(含平衡调整)、旋转、迭代器等核心功能。由于在之前的博客中已经为大家介绍过了红黑树的实现,这里就不再过多赘述了,感兴趣的话可以参考一下之前的博客:C++进阶:(七)红黑树深度解析与 C++ 实现
cpp
#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; // 红黑树根节点
};
3.2 set 的实现(Myset.h)
set 是 key 的集合,其特点是 key 唯一且有序,key 不可修改。通过封装红黑树,并重定义KeyOfT仿函数实现。代码如下:
cpp
#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类型,不可修改
}
}
3.3 map 的实现(Mymap.h)
map 是 key-value 的映射容器,其特点是 key 唯一且有序,key 不可修改但 value 可修改。同样通过封装红黑树实现,核心是KeyOfT仿函数和operator[]的实现。
cpp
#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;
}
}
}
四、核心细节解析
4.1 红黑树的平衡规则与调整
红黑树的核心是通过以下规则维持平衡的,确保所有操作的时间复杂度为 O (log n):
- 每个节点要么是红色,要么是黑色。
- 根节点是黑色。
- 所有叶子节点(NIL 节点)是黑色。
- 如果一个节点是红色,那么它的两个子节点都是黑色(无连续红色节点)。
- 从任意节点到其所有叶子节点的路径上,黑色节点的数量相同(黑高相同)。
当插入新节点(默认红色)时,可能会破坏规则 4,因此需要通过变色 或旋转 + 变色进行调整。调整的核心思路是:
- 如果叔叔节点是红色:将父节点、叔叔节点变为黑色,祖父节点变为红色,向上继续调整。
- 如果叔叔节点是黑色或不存在:通过旋转(左旋、右旋、双旋)调整树的结构,再变色维持规则。
4.2 迭代器的实现原理
map 和 set 的迭代器需要支持中序遍历(保证有序性),其核心是**operator++和operator--**的实现:
4.2.1 operator++(中序下一个节点)
- 如果当前节点的右子树不为空:下一个节点是右子树的最左节点(中序遍历右子树的第一个节点)。
- 如果当前节点的右子树为空:向上查找第一个 "当前节点是父节点左孩子" 的祖先(该祖先即为下一个节点)。
4.2.2 operator--(中序前一个节点)
- 如果当前是end()(nullptr):前一个节点是树的最右节点(中序遍历的最后一个节点)。
- 如果当前节点的左子树不为空:前一个节点是左子树的最右节点(中序遍历左子树的最后一个节点)。
- 如果当前节点的左子树为空:向上查找第一个 "当前节点是父节点右孩子" 的祖先(该祖先即为前一个节点)。
4.3 key 不可修改的实现
- set 的 key 不可修改:set 封装红黑树时,将红黑树的节点存储类型设为const K,因此迭代器解引用后得到的是const K&,无法修改。
- map 的 key 不可修改:map 的节点存储类型是pair<const K, V>,其中 key 被定义为
const,因此迭代器访问it->first时是const K&,无法修改;而it->second是V&,可以修改。
4.4 map 的 operator [] 实现
map 的**operator[]**是最常用的接口,其实现逻辑如下:
cpp
V& operator[](const K& key) {
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
- 调用insert插入**(key, V())**(value 为默认构造)。
- 如果 key 已存在,
insert返回已存在节点的迭代器和false,直接返回该节点的 value 引用。 - 如果 key 不存在,
insert插入新节点并返回新节点的迭代器和true,返回新节点的 value 引用。
这种实现支持三种用法:
- 访问已存在的 key:dict["left"]。
- 修改已存在的 key 的 value:dict["left"] = "左边(剩余)"。
- 插入新的 key-value 对:dict["insert"] = "插入"。
总结
通过本文的实现,希望大家可以深入理解 map 和 set 的底层机制,掌握红黑树的核心原理和泛型编程的思想,为后续学习更复杂的容器和数据结构打下坚实基础。