封装红黑树实现map和set容器
- 封装红黑树实现map和set容器
- github地址
- 前言
- 一、改造红黑树
-
- [1. 分析源码](#1. 分析源码)
-
- 红黑树(rb_tree)泛型设计思想解析
- [set & map 实例化 rb_tree 时的差异](#set & map 实例化 rb_tree 时的差异)
- [为什么红黑树需要两个模板参数(Key & Value)?](#为什么红黑树需要两个模板参数(Key & Value)?)
- [2. 改造实现代码整体框架](#2. 改造实现代码整体框架)
- [二、KeyOfT 萃取器修改 insert 中的 比较逻辑](#二、KeyOfT 萃取器修改 insert 中的 比较逻辑)
-
- 红黑树架构设计
- SetKeyOfT
- MapKeyOfT
- 传入不同的仿函数实例化红黑树分析
- [KeyOfT 对红黑树中insert的修改](#KeyOfT 对红黑树中insert的修改)
- 三、迭代器与相关重载实现
-
- [1. 自定义树的iterator类](#1. 自定义树的iterator类)
-
- [由 普通迭代器构造 const 迭代器](#由 普通迭代器构造 const 迭代器)
- [2. operator*](#2. operator*)
- [3. operator->](#3. operator->)
- [4. operator!=](#4. operator!=)
- [5. operator==](#5. operator==)
- [6. operator++](#6. operator++)
- [7. operator--](#7. operator--)
- 四、红黑树的iterator以及const_itreator的封装
-
- [1. 封装iterator与const_iterator](#1. 封装iterator与const_iterator)
- [2. begin 与 const_begin](#2. begin 与 const_begin)
- [3. end 与 const_end](#3. end 与 const_end)
- 五、实现Key不支持修改与容器的迭代器
-
- [1. set 借助 const 迭代器实现 Key 不支持修改](#1. set 借助 const 迭代器实现 Key 不支持修改)
- [2. map 实现 Key 不支持修改](#2. map 实现 Key 不支持修改)
- [3. 封装 map 的迭代器](#3. 封装 map 的迭代器)
- 六、改造insert实现operator[]
-
- [1. operator[] 的介绍](#1. operator[] 的介绍)
- [2. operator[]和insert行为的哲学](#2. operator[]和insert行为的哲学)
- [3.红黑树中 insert 函数的修改](#3.红黑树中 insert 函数的修改)
- [4. map中 operator[] 的实现](#4. map中 operator[] 的实现)
- [4. set 中 insert 的实现](#4. set 中 insert 的实现)
-
- [实现 insert](#实现 insert)
- [由树的普通迭代器构造 const 迭代器 详解](#由树的普通迭代器构造 const 迭代器 详解)
- 七、完整代码实现
- 八、结语
封装红黑树实现map和set容器
github地址
前言
前文链接:手搓红黑树详解
在前文《手搓红黑树详解》中,我们已经实现了一棵完整的红黑树。
本篇将进一步改造这棵红黑树,使它能够支撑 STL 风格的 map 与 set 容器。
STL 中的 map 和 set 虽然都基于红黑树实现,但两者的数据结构并不相同:
set只保存键key;map保存键值对pair<const K, V>。
为了兼容两种场景,标准库采用了泛型模板和萃取器 (KeyOfT) 的设计思想,使红黑树在编译期即可灵活适配不同容器。
本文将带你一步步封装出一个可运行的 map 与 set,深入理解它们与红黑树之间的联系,以及 STL 容器设计的核心思路。
一、改造红黑树
1. 分析源码
set 与 map 的底层都是红黑树,但是我们不能直接使用一棵普通的红黑树套进去,因为 set 和 map 中所存储的数据类型是不一样的。
- set 中是单个值 key
- map 中是一个 pair 类型。那么我们应该如何解决呢?我们来参考一下 STL 库中的写法。
SGI-STL30 版本源代码,map 和 set 相关的实现代码分布在 map、set、stl_map.h、stl_set.h、stl_tree.h 等几个头文件 中。set 和 map 的实现结构框架核心部分截取出来如下:
cpp
// stl_set.h
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; // red-black tree representing set
};
// stl_map.h
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; // red-black tree representing map
};
// stl_tree.h
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc = alloc>
class rb_tree
{
protected:
typedef void* void_pointer;
typedef __rb_tree_node_base* base_ptr;
typedef __rb_tree_node<Value> rb_tree_node;
typedef rb_tree_node* link_type;
protected:
size_type node_count; // keeps track of size of tree
link_type header;
};
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{
typedef __rb_tree_node<Value>* link_type;
Value value_field;
};
- 图解分析如下:

红黑树(rb_tree)泛型设计思想解析
通过上图对框架的分析,我们可以看到 SGI STL 源码中 rb_tree 用了一个巧妙的泛型思想实现
- 它不直接写死"仅支持key 搜索 "或"仅支持key/value 搜索 ",而是通过红黑树第二个模板参数
Value灵活控制: - 红黑树节点(
_rb_tree_node)中实际存储的数据类型 ,由Value决定。这样一颗红黑树既可以实现 key 搜索场景的 set,也可以实现 key/value 搜索场景的 map。-
set 实例化 rb_tree 时,第二个模板参数给的是 key,
-
map 实例化 rb_tree 时,第二个模板参数给的是 pair。
-
set & map 实例化 rb_tree 时的差异
-
set 实例化时的场景:
- 当
set实例化rb_tree时,给第二个模板参数传入纯 key 类型 (如:set<int>中,Value就是int) - 红黑树节点直接存储 key,自然适配"仅按 key 搜索、不重复存储"的需求
- 当
-
map 实例化时的场景:
- 当
map实例化rb_tree时,给第二个模板参数传入键值对类型pair<const Key,T> (如:map<int,string>中,Value是pair<const int, string>) - 红黑树节点存储完整的键值对pair,从而支持"按 key 关联 value "的搜索逻辑
- 当

注意事项 :关于
value_type的特殊含义
- 源码里的模板参数常用
T代表"节点存储的数据类型 (即这里的Value)rb_tree内部定义的value_type,并非我们日常说的"key/value里的value",而是红黑树节点实际存储的数据的类型 (对set是key类型,对map是pair<const Key,T>类型)
为什么红黑树需要两个模板参数(Key & Value)?
既然 rb_tree 第二个模板参数 Value已经控制了红黑树节点中存储的数据类型,为什么还要传第一个模板参数 Key 呢?
尤其是 set,两个模板参数均为K,这是为什么呢?
核心原因在于 find/erase 等操作的参数需求:
-
对 set 和 map 来说 :
find /erase函数的参数是Key类型(按key查找、删除),而非完整的节点数据(Value 类型) -
对
set而言:Key和Value类型相同(节点存key,操作也用 key),两个模板参数看似冗余,但是这样做主要是为了和map容器保持统一的接口
-
对
map而言:Key(操作入参类型)和Value(节点存储的键值对类型)完全不同 ------map插入的是pair对象,但查找 、删除 只用Key
因此 :Key 模板参数的意义是为 find/erase 等函数提供形参类型 ,让一份红黑树模板代码通过不同的实例化,能统一支撑 set(Key 与Value 同类型)和map (Key 与 Value 不同类型)的存储场景。
简而言之:
SGI STL中的红黑树 通过模板参数的分层职责 (Value控制节点存储类型,Key控制操作入参类型),使rb_tree成为一套"万能骨架",向上完美适配set(纯key场景)和map(key/value 场景),体现了泛型编程的灵活性与复用性
2. 改造实现代码整体框架
结点颜色的枚举类
- 我们定义一个枚举类,枚举值表示结点的颜色
cpp
enum Colour {
Red,
Black
};
红黑树的结点定义
- 红黑树结点为模版实现
cpp
// 由于 set 和 map 底层存储的数据不一样, set 存储 key,map 存储 pair<key, value> (key-value)
// 所以红黑树结点存储的数据类型不是写死的, 而是写成一个模板参数 T
// 红黑树代码模板被实例化出两份,一份 _data 存储 K 类型,一份 _data 存储 pair<K, V> 类型
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
Colour _col;
T _data;
// 新结点默认是红色的
RBTreeNode(const T& data)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_data(data)
,_col(Red)
{ }
};
-
搜索树常用于存储键值,方便查找关键字 ,这里我们使用
T _data来存储我们的实际数据- 在 set 中 T 为 Key
- 在 map 中T为 pair<K, V>
-
结点中的成员变量 :采用三叉链的方式实现
RBTreeNode<T>* _left;:指向左孩子的指针RBTreeNode<T>* _right;:指向右孩子的指针RBTreeNode<T>* _parent;:指向父节点的指针
-
默认构造函数
RBTreeNode(const T& data):- 将三个指针初始化为
nullptr,初始化结点颜色为 Red - 使用形参
data初始化类内的_data成员
- 将三个指针初始化为
-
结点采用
struct设计,默认权限为public,方便下文的RBTree类访问成员
红黑树架构设计
cpp
template<class K, class T, class KeyOfT>
class RBTree
{
private:
typedef RBTreeNode<T> Node;
RBTreeNode<T>* _root = nullptr;
public:
// ...迭代器和相关成员函数 实现
}
Node* _root = nullptr:初始时根节点为空typedef RBTreeNode<T> Node;:结点类型重定义 ,简化书写- 模板参数设置为
template<class K, class T, class KeyOfT>,后文解释
二、KeyOfT 萃取器修改 insert 中的 比较逻辑
由于红黑树(RBTree)采用泛型设计 ,无法直接判断 模板参数 T 具体是单纯的键类型K(如:set的场景),还是键值对类型
pair<K,V>(如:map 的场景)
- 这会导致一个问题:在
insert的逻辑里进行"节点值大小比较"时,默认的比较规则无法满足需求- 默认比较规则 :
set使用Key进行比较,map使用std::pair预设的比较规则进行比较
- 默认比较规则 :
set的Key可以完成预期比较,而std::pair的默认支持的是key和value一起参与比较,不符合我们仅比较Key的比较逻辑
为解决这个问题,我们在 map 和 set 这两个容器层 ,分别实现了仿函数 MapKeyofT 和 SetKeyofT ,并将它们传递给红黑树的KeyOfT模板参数
这样,红黑树内部 就能通过上层容器 的 KeyOfT 仿函数 :通过 KeyOfT 仿函数取出 T 类型对象中的 key,再进行比较
- 先从 T 类型对象中提取出 key,再用这个 key 进行比较,从而实现"仅按 key 排序/插入"的逻辑。
红黑树架构设计
- 模板参数设置为
template<class K, class T, class KeyOfT>,方便通过仿函数KeyOfT取出需要进行比较的Key值
cpp
template<class K, class T, class KeyOfT>
class RBTree
{
private:
typedef RBTreeNode<T> Node;
RBTreeNode<T>* _root = nullptr;
public:
// ...迭代器和相关成员函数 实现
}
SetKeyOfT
cpp
// m_set.h
template<class K>
class set
{
// 萃取器
struct SetKeyOfT
{
const K& operator()(const K& key) const
{
return key;
}
};
RBTree<K, K, SetKeyOfT> _tree;
public:
// ...
}
MapKeyOfT
cpp
// m_map.h
template<class K, class V>
class map
{
private:
// 萃取器 把 T 对象中的 key 取出来
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv) const
{
return kv.first;
}
};
RBTree<K, pair<K, V>, MapKeyOfT> _tree;
public:
// ...
};
传入不同的仿函数实例化红黑树分析

使用 萃取器 KeyOfT 取出 Key 的设计相当巧妙
set和map的设计,_data分别是Key和pair- 如果是
set中的Key, 可以直接比较,如果是map,标准库中的pair<K, V>的比较不支持仅比较K
因此设计了仿函数萃取器 KeyOfT ,用于取出 T 中的 Key 来进行比较
- 如果是
set返回 K,如果是map中的pair<K, V>,取出 K 进行比较 - 可以认为这里是
set迁就了map,因为set的 K 本身就可以直接比较,map的pair<K, V>需要借助KeyOfT取出Key进行比较
KeyOfT完全是为了map设计的 ,
set的SetKeyOfT仅仅是为了保持统一的接口风格
KeyOfT 对红黑树中insert的修改
需要修改的地方如下:
- 红黑树在插入节点时的比较逻辑 需要修改
- 之前红黑树搜索插入位置时,进行的是直接比较
- 现在需要使用仿函数KeyOfT,先提取出类型中的Key再进行比较
- 例如 :
if (data < curNode->_data))修改为if (kot(data) < kot(curNode->_data)),仅比较关键字Key,需要再套上一层仿函数对象- kot为定义出的仿函数对象
cpp
bool insert(const T& data)
{
// 先走二叉搜索树的插入逻辑
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = Black; // 性质 根节点是黑色的
return true;
}
// _root 不为空时,二叉搜索树的逻辑
Node* parent = nullptr;
Node* curNode = _root;
// 先找空,找到一个可以插入的位置
KeyOfT kot;
while (curNode)
{
if (kot(data) < kot(curNode->_data))
{
parent = curNode;
curNode = curNode->_left;
}
else if (kot(data) > kot(curNode->_data))
{
parent = curNode;
curNode = curNode->_right;
}
// 搜索树中不允许有重复的值 对于已有值,不插入
else
return false;
}
// while 循环结束后,代表找到了可以插入的位置
// 找到位置了,但父节点不知道 新结点比自己大还是比自己小
curNode = new Node(data);
if (kot(curNode->_data) < kot(parent->_data))
parent->_left = curNode;
else
parent->_right = curNode;
curNode->_parent = parent;
// ... 仅以上代码 有部分修改,其余代码和红黑树的插入一致
}
三、迭代器与相关重载实现
这里的 iterator 的实现思路与 list 的 iterator 框架一致:
- 用一个类封装 "结点指针"
- 再通过重载运算符 ,让迭代器能像指针一样完成访问和移动 行为(如:
*it、++it、it->等行为)
1. 自定义树的iterator类
cpp
template<class T, class Ptr, class Ref>
struct __TreeIterator
{
typedef RBTreeNode<T> Node;
typedef __TreeIterator<T, Ptr, Ref> Self;
typedef __TreeIterator<T, T*, T&> Iterator; // 该类型始终为常量迭代器
Node* _node;
__TreeIterator(Node* node)
:_node(node)
{ }
// 由 普通迭代器构造 const 迭代器
__TreeIterator(const Iterator& it)
:_node(it._node)
{ }
};
- 这里树的
iterator的实现思路与list的iterator实现思路一致,模板参数的控制思路也一致: - 构造函数
__TreeIterator(Node* node)使用结点的指针构造出一个迭代器类型
由 普通迭代器构造 const 迭代器
cpp
typedef __TreeIterator<T, Ptr, Ref> Self;
typedef __TreeIterator<T, T*, T&> Iterator; // 该类型始终为普通迭代器迭代器
__TreeIterator(const Iterator& it)
:_node(it._node)
{ }
- 注意这里:
typedef __TreeIterator<T, T*, T&> Iterator中的模板参数给的是<T, T*, T&>,那么类型Iterator始终为普通迭代器 - 形参
const Iterator& it为普通迭代器的常引用,既可以接受普通迭代器,也可以接收 const 迭代器 ,该构造函数完成的功能有两个:- 由 普通迭代器构造 const 迭代器 :当
it接收到的实参是 普通迭代器 时,完成由普通迭代器构造 const 迭代器 - 由 const 迭代器 的拷贝构造 :当
it接收到的实参是 const 迭代器 时,完成 const 迭代器 的拷贝构造
- 由 普通迭代器构造 const 迭代器 :当
2. operator*
cpp
Ref operator*() const
{
if (_node == nullptr)
assert(false);
return _node->_data;
}
- 防止访问空结点 :访问空结点时
assert(false) *it需要访问到结点中的数据,引用返回,因此返回结点中的数据return _node->_data
3. operator->
cpp
Ptr operator->() const
{
if (_node == nullptr)
assert(false);
return &(_node->_data);
}
- 防止访问空结点 :访问空结点时
assert(false) it->是通过指针访问数据的行为 ,因此返回结点中的地址return &(_node->_data)
4. operator!=
cpp
bool operator!=(const Self& s) const
{
return _node != s._node;
}
5. operator==
cpp
bool operator==(const Self& s) const
{
return _node == s._node;
}
6. operator++
关于
operator++的讲解见链接:https://sanqiucoder.blog.csdn.net/article/details/151373721?spm=1001.2014.3001.5502
- 前置++
cpp
Self& operator++()
{
// 右树不为空,就访问右树的最左结点
if (_node->_right)
{
// 找右树的最左节点(最小节点)
Node* subLeft = _node->_right;
while (subLeft->_left)
subLeft = subLeft->_left;
_node = subLeft;
}
// 右树为空时分两种情况讨论
else
{
Node* cur = _node;
Node* parent = cur->_parent;
// 找孩子是父亲左的那个祖先节点,就是下一个要访问的节点
// 当前结点 是 父亲的 右时,才继续向上找
while (parent && cur == parent->_right)
{
cur = cur->_parent;
parent = parent->_parent;
}
// 有两种情况会结束循环
// 1. cur == parent->_left 时, 下一个访问的就是 cur 的 父亲,直接 _node = parent
// 2. parent 为 空 时,cur 此时为根节点, 且 cur 的右为空,此时中序遍历结束
_node = parent;
}
return *this;
}
- 后置++
cpp
Self operator++(int)
{
Self tmp(*this);
++*this;
return tmp;
}
7. operator--
关于
operator--的讲解见链接:https://sanqiucoder.blog.csdn.net/article/details/151373721?spm=1001.2014.3001.5502
- 前置--
cpp
Self& operator--()
{
// -- 就是 ++ 反过来
// 左子树不为空,就去找左树的最大结点
if (_node->_left)
{
Node* subRight = _node->_left;
while (subRight->_right)
subRight = subRight->_right;
_node = subRight;
}
else
{
// 孩子是父亲的右的那个节点
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
- 后置--
cpp
Self operator--(int)
{
Self tmp(*this);
--*this;
return tmp;
}
四、红黑树的iterator以及const_itreator的封装
1. 封装iterator与const_iterator
在树的层面实现迭代器:
cpp
template<class K, class T, class KeyOfT>
class RBTree
{
private:
typedef RBTreeNode<T> Node;
RBTreeNode<T>* _root = nullptr;
public:
// 同一个类模板 传不同的参数,实例化出不同的类型
typedef __TreeIterator<T, T*, T&> iterator;
typedef __TreeIterator<T, const T*, const T&> const_iterator;
// ... begin end 等迭代器
}
在实现迭代器时,通过传不同的模板参数,实例化出不同的迭代器类型,即可控制是普通迭代器还是 const 版本的迭代器
typedef __TreeIterator<T, T*, T&> iterator:传入普通 T 类型,实例化出普通迭代器typedef __TreeIterator<T, const T*, const T&> const_iterator:传入const T类型,实例化出const迭代器
2. begin 与 const_begin
实现如下:
cpp
iterator begin()
{
Node* leftMin = _root;
while (leftMin && leftMin->_left)
leftMin = leftMin->_left;
return iterator(leftMin);
}
// const 版本
const_iterator begin() const
{
Node* leftMin = _root;
while (leftMin && leftMin->_left)
leftMin = leftMin->_left;
return const_iterator(leftMin);
}
begin()返回中序遍历的第一个位置 ,因此找树的最左节点即可- 最终返回一个迭代器对象,用树的最左结点的指针构造迭代器对象返回
- 普通对象 调用普通
begin(),返回iterator对象 const对象 调用const begin(),返回const_iterator对象
- 普通对象 调用普通
3. end 与 const_end
cpp
iterator end()
{
return iterator(nullptr);
}
const_iterator end() const
{
return const_iterator(nullptr);
}
end()返回最后一个位置的下一个位置 ,这里我们用空结点来表示- 最终返回一个迭代器对象,用
nullptr构造空迭代器对象返回- 普通对象 调用普通
end(),返回iterator对象 const对象 调用const end(),返回const_iterator对象
- 普通对象 调用普通
五、实现Key不支持修改与容器的迭代器
- map 和 set 的迭代器的实现 ,本质是对红黑树迭代器的适当封装 ,我们对其进行封装即可
- map 和 set 的普通迭代器 ,本质是对红黑树中 普通迭代器 的适当封装
- map 和 set 的 const 迭代器 ,本质是对红黑树中 const 迭代器 的适当封装
1. set 借助 const 迭代器实现 Key 不支持修改
我们先来观察 STL 中源码中的实现:截取部分如下
cpp
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; // red-black tree representing set
public:
// 两种迭代器
typedef typename rep_type::const_iterator iterator;
typedef typename rep_type::const_iterator const_iterator;
}
- 可以看到,源码中普通迭代器和const迭代器 的实现均为
const_iterator,源码中借助了const迭代器实现普通迭代器,我们仿照其实现即可
cpp
template<class 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 _tree.begin();
}
const_iterator end() const
{
return _tree.end();
}
// ...
}
注意 set 的普通迭代器和const迭代器都是 const_iterator,因此只需提供 const 版本的begin和end函数即可
- const版本的begin和end函数,普通对象和const对象都可以调用
分析 STL 中的实现如下:
- 因为
set的普通迭代器和const迭代器均为const_iterator,所以STL中只实现了const版本的begin和end函数,普通对象 和const对象都可以调用const函数,都返回const_iterator
cpp
typedef typename rep_type::const_iterator iterator;
typedef typename rep_type::const_iterator const_iterator;
// 库里的写法,由 typedef 得知,set 中的 iterator 实际是 const_iterator
iterator begin() const { return t.begin(); }
iterator end() const { return t.end(); }
2. map 实现 Key 不支持修改
cpp
template<class K, class V>
class map
{
private:
RBTree<K, pair<const K, V>, MapKeyOfT> _tree; // 将 pair 中的 K 设置为 const K 类型
public:
// ...
}
map实现的是key不能修改,value能修改,我们可以在pair<key, value>的存储层解决这个问题 ,存储时,直接存储key为常量的pair<const K, V>。map中存储的树类型 为:RBTree<K, pair<const K, V>, MapKeyOfT> _tree;即可实现map的key不能修改,value能修改
3. 封装 map 的迭代器
- map 和 set 的普通迭代器 ,本质是对红黑树中 普通迭代器 的适当封装
- map 和 set 的 const 迭代器 ,本质是对红黑树中 const 迭代器 的适当封装
cpp
template<class K, class V>
class map
{
private:
RBTree<K, pair<const K, V>, MapKeyOfT> _tree;
public:
// ...
// key 不能修改, value 能修改
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
// ... begin 和 end 等函数
}
-
实现
map的key不能修改,value能修改是在pair的存储层增加了const来解决这个问题 ,因此begin和end常规实现即可- 普通 map 对象,调用树的普通迭代器
- const map 对象,调用树的 const 迭代器
-
map封装树的迭代器:- 将树中的普通迭代器封装为 map 的普通迭代器 :
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator
- 将树中的 const迭代器封装为 map 的const迭代器 :
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator
- 将树中的普通迭代器封装为 map 的普通迭代器 :
begin与end函数
map的Key不能修改是在 pair 的存储层 实现的,因此需要分别实现begin和 end 函数的普通版本 和const对象版本
cpp
// 普通 map 对象,调用树的普通迭代器
iterator begin()
{
return _tree.begin();
}
iterator end()
{
return _tree.end();
}
const_iterator begin() const // const map 对象,调用树的普通迭代器
{
return _tree.begin();
}
const_iterator end() const
{
return _tree.end();
}
六、改造insert实现operator[]
1. operator[] 的介绍

在 map 中,operator[] 可以实现插入 + 修改的功能 。既然有插入,那么就必定要用到 insert 接口。在 STL 库中,insert 的接口的返回值是一个 pair 类型:
- 如果插入成功 ,返回
<插入的该数据对应的迭代器, true>; - 如果插入失败 ,返回
<已经存在的该数据对应的迭代器, false>
2. operator[]和insert行为的哲学
map 中operator[]的行为等效如下:
cpp
mapped_type& operator[] (const key_type& k); // 参考文档中的声明
pair<iterator,bool> insert (const value_type& val); // 参考文档中 insert 的声明
// operator[] 的行为等效如下
(*((this->insert(make_pair(mapped_type()))).first))
(*((this->insert()).first)) // 将 make_pair 函数去掉后如下
// 对以上代码做拆解,可得如下结果
(*((insert()).first)) // 这里本质是对 insert 返回的 pair 中的 first 进行解引用
// 由insert的返回值为 pair ,将insert替换为pair,可以得到 以下结果
(* ( (pair<iterator,bool>).first) ) // 本质是对 insert 返回的迭代器进行解引用
insert之所以这么设计,就是为了实现 operator[]时可以复用insert
- 因此我们需要将红黑树中
insert的返回值修改为返回pair
3.红黑树中 insert 函数的修改
- 仅对 insert 函数中原有的 return 语句进行了修改
cpp
std::pair<iterator, bool> insert(const T& data)
{
// 先走二叉搜索树的插入逻辑
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = Black; // 性质 根节点是黑色的
//return true;
return make_pair(iterator(_root), true);
}
// _root 不为空时,二叉搜索树的逻辑
Node* parent = nullptr;
Node* curNode = _root;
// 先找空,找到一个可以插入的位置
// 使用 萃取器 取出 Key 设计相当巧妙
// 标准库中的 pair 不能直接比较大小,我们设计了 一个萃取器 用于取出 T 中的 Key
// 可以认为这里是 set 迁就了 map ,因为set的 K 可以直接比较,map 的pair<K, V> 需要取出 Key
// 这是 map 和 set 的设计,_data 可能是 K,也可能是 pair
// 如果是 K,可以直接比较,如果是map,标准库中的 pair<K, V> 的比较对象不支持仅比较K
// 因子设计了仿函数 KeyOfT 如果是 set 返回 K, 如果是map 中的 pair<K, V>,取出 K 进行比较
// KeyOfT 完全是为了 map 设计的,
KeyOfT kot;
while (curNode)
{
if (kot(data) < kot(curNode->_data))
{
parent = curNode;
curNode = curNode->_left;
}
else if (kot(data) > kot(curNode->_data))
{
parent = curNode;
curNode = curNode->_right;
}
// 搜索树中不允许有重复的值 对于已有值,不插入
else
{
//return false;
return make_pair(iterator(curNode), false);
}
}
// while 循环结束后,代表找到了可以插入的位置
// 找到位置了,但父节点不知道 新结点比自己大还是比自己小
curNode = new Node(data);
// 保存 cur 指针
Node* newNode = curNode;
if (kot(curNode->_data) < kot(parent->_data))
parent->_left = curNode;
else
parent->_right = curNode;
curNode->_parent = parent;
// 以上是二叉搜索树的插入逻辑,这样插入可能导致树不平衡,从而导致查找效率退化为 O(n)
// 以下是红黑树的性质控制,是对二叉搜索树 进行的 控制平衡 操作
// 控制近似平衡 ...
// while 循环控制 颜色继续往上更新
// 新插入的结点为红色 因此 parent 存在且 parent 为红色时,才需要更新颜色
//
while (parent && parent->_col == Red)
{
Node* grandFather = parent->_parent;
// parent 在 grandFather 左的场景
if (parent == grandFather->_left)
{
Node* uncle = grandFather->_right;
// 情况一 cur为红 parent为红 grandFather为黑,uncle 存在且为红
if (uncle && uncle->_col == Red)
{
// p 和 u 变黑,g 变红,cur 变成 grandFather 继续往上更新
// 变色
parent->_col = uncle->_col = Black;
grandFather->_col = Red;
// 继续向上处理
curNode = grandFather;
parent = curNode->_parent;
// 如果更新完后 grandFather 为红色:
// 1. g 为根节点,那么parent为空
// 2. g 上面还有结点
// 如果是黑色的,无需处理,进不去循环
// 如果是红色,继续处理
}
// u不存在 或 u存在且为黑
else
{
// 左左 -> 右单旋
if (curNode == parent->_left)
{
// g
// p
// c
RotateR(grandFather);
parent->_col = Black;
grandFather->_col = Red;
}
// 左右 -> 左右双旋
else
{
// g
// p
// c
RotateL(parent);
RotateR(grandFather);
curNode->_col = Black;
grandFather->_col = Red;
}
break;
}
}
// parent 在 grandFather 右的场景
else
{
Node* uncle = grandFather->_left;
// 情况一 cur为红 parent为红 grandFather为黑,uncle 存在且为红
if (uncle && uncle->_col == Red)
{
// p 和 u 变黑,g 变红,cur 变成 grandFather 继续往上更新
// 变色
parent->_col = uncle->_col = Black;
grandFather->_col = Red;
// 继续向上处理
curNode = grandFather;
parent = curNode->_parent;
// 如果更新完后 grandFather 为红色:
// 1. g 为根节点,那么parent为空
// 2. g 上面还有结点
// 如果是黑色的,无需处理,进不去循环
// 如果是红色,继续处理
}
// u不存在 或 u存在且为黑
else
{
// 右右 -> 左单旋
if (curNode == parent->_right)
{
// g
// p
// c
RotateL(grandFather);
parent->_col = Black;
grandFather->_col = Red;
}
// 右左 -> 右左双旋
else
{
// g
// p
// c
RotateR(parent);
RotateL(grandFather);
curNode->_col = Black;
grandFather->_col = Red;
}
break;
}
}
}
// 继续向上处理 parent = curNode->_parent 如果 parent == nullptr 会结束循环
// parent == nullptr 结束循环时,curNode为_root结点,需要对 _root 节点变色
_root->_col = Black; // 粗暴的处理,直接将根节点设为黑色,根节点为黑色总是正确的
//return true;
return make_pair(iterator(newNode), true);
}
4. map中 operator[] 的实现
- map 中的 insert 实现
cpp
// map 中的 iterator 是对 红黑树中 普通 iterator 的封装
pair<iterator, bool> insert(const pair<const K, V>& kv)
{
return _tree.insert(kv);
}
- map 中的 operator[] 实现
cpp
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return (ret.first)->second;
}
operator[]的实现:
- 先根据
Key和Value插入pair,再返回pair中Value的引用
4. set 中 insert 的实现
实现 insert
- 由于
set中 只有const_iterator,因此修改了树的insert函数后,需要将 set 中 insert 函数的返回值和红黑树中 insert 的返回值做对应
cpp
// m_set::
pair<iterator, bool> insert(const K& key)
{
// 这里树的 insert 返回的 iterator 是树里 的 普通iterator
// 但是 set 中 insert 的返回值 pair 中的迭代器 是 const_iterator
// 解决方法,先接收 树里的 普通迭代器,再用 普通迭代器构造 const_迭代器 返回
pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> retPair = _tree.insert(key);
// 下面用 普通迭代器去构造 const 迭代器 并 返回
return pair<const_iterator, bool>(retPair.first, retPair.second);
}
由树的普通迭代器构造 const 迭代器 详解
cpp
// __TreeIterator::
typedef __TreeIterator<T, Ptr, Ref> Self;
typedef __TreeIterator<T, T*, T&> Iterator;
// 普通迭代器构造 const 迭代器的函数
__TreeIterator(const Iterator& it)
:_node(it._node)
{ }
typedef __TreeIterator<T, T*, T&> Iterator:不论__TreeIterator被实例化成什么类型,Iterator始终为普通迭代器类型 。函数__TreeIterator(const Iterator& it)的形参为普通迭代器的 const 引用- 当
__TreeIterator被实例化成普通迭代器 时,此时该函数__TreeIterator(const Iterator& it)充当__TreeIterator类的拷贝构造函数 - 当
__TreeIterator被实例化成const迭代器 时,此时该函数__TreeIterator(const Iterator& it)充当__TreeIterator类的构造函数 ,可以由普通迭代器构造 const 迭代器
- 当
七、完整代码实现
m_set
cpp
#pragma once
#include "Red_Black_Tree.h"
namespace m_set
{
template<class K>
class set
{
// 萃取器
struct SetKeyOfT
{
const K& operator()(const K& key) const
{
return key;
}
};
RBTree<K, K, SetKeyOfT> _tree;
public:
//typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;
// set 不允许修改,解决方案: set 的迭代器和const迭代器均为 const_iterator
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
// 库里的写法,set 中,这里的 iterator 实际是 const_iterator
/*iterator begin() const
{
return _tree.begin();
}
iterator end() const
{
return _tree.end();
}*/
// set 的 iterator 都是 const_iterator,因此可以只提供 const 版本的迭代器
const_iterator begin() const
{
return _tree.begin();
}
const_iterator end() const
{
return _tree.end();
}
// set
//
pair<iterator, bool> insert(const K& key)
{
//return _tree.insert(key);
//这里树的 insert 返回的 iterator 是树里 的 普通iterator
// 但是 set 的 insert 的返回值 pair中的迭代器 是 const_iterator
// 解决方法,先接收 树里的 普通迭代器,再用 普通迭代器构造 const_迭代器
pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _tree.insert(key);
// 下面用 普通迭代器去初始化 const 迭代器
return pair<const_iterator, bool>(ret.first, ret.second);
}
};
}
m_map
cpp
#pragma once
#include "Red_Black_Tree.h"
namespace m_map
{
template<class K, class V>
class map
{
private:
// 萃取器 把 T 对象中的 key 取出来
struct MapKeyOfT
{
const K& operator()(const pair<const K, V>& kv) const
{
return kv.first;
}
};
// map 要求实现只能修改 key 不能修改 value
// 这样定义,pair 可以修改, pair 中的 K 不能修改
RBTree<K, pair<const K, V>, MapKeyOfT> _tree;
public:
// 按照 set 实现 const 迭代器的方法实现的话,会导致 map 的 value 也不能修改
/*typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::const_iterator const_iterator;*/
// key 不能修改, value 能修改
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 _tree.begin();
}
iterator end()
{
return _tree.end();
}
const_iterator begin() const
{
return _tree.begin();
}
const_iterator end() const
{
return _tree.end();
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return (ret.first)->second;
}
std::pair<iterator, bool> insert(const pair<const K, V>& kv)
{
return _tree.insert(kv);
}
};
}
改造后的红黑树
cpp
#pragma once
#include <assert.h>
#include <iostream>
#include <utility>
using namespace std;
enum Colour {
Red,
Black
};
// 红黑树被实例化 出两份,一份 _data 是 K 类型,一份_data 是 pair<K, V> 类型
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data;
Colour _col;
// 新结点默认是红色的
RBTreeNode(const T& data)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_data(data)
,_col(Red)
{ }
};
template<class T, class Ptr, class Ref>
struct __TreeIterator
{
typedef RBTreeNode<T> Node;
typedef __TreeIterator<T, Ptr, Ref> Self;
typedef __TreeIterator<T, T*, T&> Iterator;
// 类模板 __TreeIterator 被实例化成 普通迭代器时, Self 和 Iterator 是同一个类型
// 类模板 __TreeIterator 被实例化成 const迭代器时:
// Self 就成了 const 迭代器,Iterator 始终是 普通迭代器
Node* _node;
__TreeIterator(Node* node)
:_node(node)
{ }
__TreeIterator(const Iterator& it)
:_node(it._node)
{ }
// const 迭代器相当于套了一层模板
Ref operator*() const
{
if (_node == nullptr)
assert(false);
return _node->_data;
}
Ptr operator->() const
{
if (_node == nullptr)
assert(false);
return &(_node->_data);
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
Self operator--(int)
{
Self tmp(*this);
--*this;
return tmp;
}
Self& operator--()
{
// -- 就是 ++ 反过来
// 左子树不为空,就去找左树的最大结点
if (_node->_left)
{
Node* subRight = _node->_left;
while (subRight->_right)
subRight = subRight->_right;
_node = subRight;
}
else
{
// 孩子是父亲的右的那个节点
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
Self& operator++()
{
// 右树不为空,就访问右树的最左结点
if (_node->_right)
{
// 找右树的最左节点(最小节点)
Node* subLeft = _node->_right;
while (subLeft->_left)
subLeft = subLeft->_left;
_node = subLeft;
}
// 右树为空时分两种情况讨论
else
{
Node* cur = _node;
Node* parent = cur->_parent;
// 找孩子是父亲左的那个祖先节点,就是下一个要访问的节点
while (parent && cur == parent->_right)
{
cur = cur->_parent;
parent = parent->_parent;
}
// 有两种情况会结束循环
// 1. cur == parent->_left 时, 下一个访问的就是cur 的 父亲,_node = parent
// 2. parent 为 空 时,cur 此时为根节点, 且 cur 的右为空,此时中序遍历结束
_node = parent;
}
return *this;
}
Self operator++(int)
{
Self tmp(*this);
++*this;
return tmp;
}
};
// 迭代器与const迭代器 是同一个类模板,传入不同的模板参数,实例化出不同的类型
// KeyOfT 是一个仿函数
// Set->RBTree<K,K,SetKeyOfT> _t
// map->RBTree<K,pair<K,V>, MapKeyOfT> _t;
template<class K, class T, class KeyOfT>
class RBTree
{
//public:
// int _rotateCount = 0;
private:
typedef RBTreeNode<T> Node;
RBTreeNode<T>* _root = nullptr;
public:
// 同一个类模板 传不同的参数,实例化出不同的类型
typedef __TreeIterator<T, T*, T&> iterator;
typedef __TreeIterator<T, const T*, const T&> const_iterator;
// begin 返回的是 中序遍历的第一个位置
iterator begin()
{
Node* leftMin = _root;
while (leftMin && leftMin->_left)
leftMin = leftMin->_left;
return iterator(leftMin);
}
iterator end()
{
return iterator(nullptr);
}
// const 版本
const_iterator begin() const
{
Node* leftMin = _root;
while (leftMin && leftMin->_left)
{
leftMin = leftMin->_left;
}
return const_iterator(leftMin);
}
const_iterator end() const
{
return const_iterator(nullptr);
}
public:
// find 函数也需要用 KeyOfT 控制
Node* find(const K& key) const
{
Node* curNode = _root;
KeyOfT kot;
while (curNode)
{
if (key > kot(curNode->_data))
curNode = curNode->_right;
else if (key < kot(curNode->_data))
curNode = curNode->_left;
else
return curNode;
}
return nullptr;
}
std::pair<iterator, bool> insert(const T& data)
{
// 先走二叉搜索树的插入逻辑
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = Black; // 性质 根节点是黑色的
//return true;
return make_pair(iterator(_root), true);
}
// _root 不为空时,二叉搜索树的逻辑
Node* parent = nullptr;
Node* curNode = _root;
// 先找空,找到一个可以插入的位置
// 使用 萃取器 取出 Key 设计相当巧妙
// 标准库中的 pair 不能直接比较大小,我们设计了 一个萃取器 用于取出 T 中的 Key
// 可以认为这里是 set 迁就了 map ,因为set的 K 可以直接比较,map 的pair<K, V> 需要取出 Key
// 这是 map 和 set 的设计,_data 可能是 K,也可能是 pair
// 如果是 K,可以直接比较,如果是map,标准库中的 pair<K, V> 的比较对象不支持仅比较K
// 因子设计了仿函数 KeyOfT 如果是 set 返回 K, 如果是map 中的 pair<K, V>,取出 K 进行比较
// KeyOfT 完全是为了 map 设计的,
KeyOfT kot;
while (curNode)
{
if (kot(data) < kot(curNode->_data))
{
parent = curNode;
curNode = curNode->_left;
}
else if (kot(data) > kot(curNode->_data))
{
parent = curNode;
curNode = curNode->_right;
}
// 搜索树中不允许有重复的值 对于已有值,不插入
else
{
//return false;
return make_pair(iterator(curNode), false);
}
}
// while 循环结束后,代表找到了可以插入的位置
// 找到位置了,但父节点不知道 新结点比自己大还是比自己小
curNode = new Node(data);
// 保存 cur 指针
Node* newNode = curNode;
if (kot(curNode->_data) < kot(parent->_data))
parent->_left = curNode;
else
parent->_right = curNode;
curNode->_parent = parent;
// 以上是二叉搜索树的插入逻辑,这样插入可能导致树不平衡,从而导致查找效率退化为 O(n)
// 以下是红黑树的性质控制,是对二叉搜索树 进行的 控制平衡 操作
// 控制近似平衡 ...
// while 循环控制 颜色继续往上更新
// 新插入的结点为红色 因此 parent 存在且 parent 为红色时,才需要更新颜色
//
while (parent && parent->_col == Red)
{
Node* grandFather = parent->_parent;
// parent 在 grandFather 左的场景
if (parent == grandFather->_left)
{
Node* uncle = grandFather->_right;
// 情况一 cur为红 parent为红 grandFather为黑,uncle 存在且为红
if (uncle && uncle->_col == Red)
{
// p 和 u 变黑,g 变红,cur 变成 grandFather 继续往上更新
// 变色
parent->_col = uncle->_col = Black;
grandFather->_col = Red;
// 继续向上处理
curNode = grandFather;
parent = curNode->_parent;
// 如果更新完后 grandFather 为红色:
// 1. g 为根节点,那么parent为空
// 2. g 上面还有结点
// 如果是黑色的,无需处理,进不去循环
// 如果是红色,继续处理
}
// u不存在 或 u存在且为黑
else
{
// 左左 -> 右单旋
if (curNode == parent->_left)
{
// g
// p
// c
RotateR(grandFather);
parent->_col = Black;
grandFather->_col = Red;
}
// 左右 -> 左右双旋
else
{
// g
// p
// c
RotateL(parent);
RotateR(grandFather);
curNode->_col = Black;
grandFather->_col = Red;
}
break;
}
}
// parent 在 grandFather 右的场景
else
{
Node* uncle = grandFather->_left;
// 情况一 cur为红 parent为红 grandFather为黑,uncle 存在且为红
if (uncle && uncle->_col == Red)
{
// p 和 u 变黑,g 变红,cur 变成 grandFather 继续往上更新
// 变色
parent->_col = uncle->_col = Black;
grandFather->_col = Red;
// 继续向上处理
curNode = grandFather;
parent = curNode->_parent;
// 如果更新完后 grandFather 为红色:
// 1. g 为根节点,那么parent为空
// 2. g 上面还有结点
// 如果是黑色的,无需处理,进不去循环
// 如果是红色,继续处理
}
// u不存在 或 u存在且为黑
else
{
// 右右 -> 左单旋
if (curNode == parent->_right)
{
// g
// p
// c
RotateL(grandFather);
parent->_col = Black;
grandFather->_col = Red;
}
// 右左 -> 右左双旋
else
{
// g
// p
// c
RotateR(parent);
RotateL(grandFather);
curNode->_col = Black;
grandFather->_col = Red;
}
break;
}
}
}
// 继续向上处理 parent = curNode->_parent 如果 parent == nullptr 会结束循环
// parent == nullptr 结束循环时,curNode为_root结点,需要对 _root 节点变色
_root->_col = Black; // 粗暴的处理,直接将根节点设为黑色,根节点为黑色总是正确的
//return true;
return make_pair(iterator(newNode), true);
}
bool CheckColour(Node* root, int blacknum, int benchmark)
{
if (root == nullptr)
{
if (blacknum != benchmark)
return false;
return true;
}
// 检查黑色节点的数量
if (root->_col == Black)
++blacknum;
// 检查有没有连续的红色结点
if (root->_col == Red && root->_parent && root->_parent->_col == Red)
{
cout << root->_kv.first << "出现连续红色节点" << endl;
return false;
}
return CheckColour(root->_left, blacknum, benchmark)
&& CheckColour(root->_right, blacknum, benchmark);
}
bool isBalance()
{
return _isBalance(_root);
}
bool _isBalance(Node* root = _root)
{
if (root == nullptr)
return true;
if (root->_col != Black)
return false;
// 基准值
int benchmark = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == Black)
++benchmark;
cur = cur->_left;
}
return CheckColour(root, 0, benchmark);
}
int Height()
{
return _Height(_root);
}
int _Height(Node* root = _root)
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
private:
// 旋转操作 可参考 红黑树博客
};
八、结语
至此,我们完成了一个 STL 风格的 map 和 set 容器封装。
通过模板与萃取器机制,我们让一棵红黑树同时支持不同类型的数据结构,并实现了 insert、operator[]、迭代器等关键功能。
这个过程不仅帮助我们理解了 STL 的底层逻辑,也展示了泛型编程的强大之处------
同一套代码,通过不同的模板参数,就能支撑多种容器行为。
以上就是本文的所有内容了,如果觉得文章对你有帮助,欢迎 点赞⭐收藏 支持!如有疑问或建议,请在评论区留言交流,我们一起进步
分享到此结束啦
一键三连,好运连连!你的每一次互动,都是对作者最大的鼓励!
征程尚未结束,让我们在广阔的世界里继续前行!🚀