
🦌云深麋鹿
专栏 :C++ | 用C语言学数据结构 | Java

回顾:上一篇我们结束了 红黑树 的相关介绍,接下来这篇文章让我们尝试 封装 红黑树 实现容器mymap跟myset,体会新的设计思路吧~
放个目录
- 前言
- [一 分析源码](#一 分析源码)
-
- [1.1 可以分别实现map和set的红黑树是怎样的](#1.1 可以分别实现map和set的红黑树是怎样的)
- [1.2 函数怎么适配map的pair跟set的key](#1.2 函数怎么适配map的pair跟set的key)
- [1.3 源码怎么实现iterator](#1.3 源码怎么实现iterator)
- [1.4 源码怎么实现const_iterator](#1.4 源码怎么实现const_iterator)
- [1.5 Key](#1.5 Key)
- [二 模拟实现](#二 模拟实现)
-
- [2.1 搭框架](#2.1 搭框架)
-
- [2.1.1 RBTreeNode结构](#2.1.1 RBTreeNode结构)
- [2.1.2 RBTree结构](#2.1.2 RBTree结构)
前言
摘要:本文基于红黑树实现mymap与myset容器,通过模板参数设计统一底层结构:以T存储节点数据,K提取键值,利用_KeyOfValue函数适配map的pair与set的key。通过迭代器封装、const迭代器机制及节点key不可修改特性,模拟标准库源码思路,实现高效、安全的键值对与集合管理,深入理解STL容器底层设计逻辑。
一 分析源码
这里可以用Everything搜源码位置打开来看:
- 打开Everything

- 输入相应文件名称查找



要同时满足两个容器的特点,底层上关键在于树结点存不同数据的同时,函数接口能够取得所需数据。
1.1 可以分别实现map和set的红黑树是怎样的
源码的设计巧思从模板参数开始:
- 给个模板参数(即_Val)。

- 树结点就是用这个_Val构造出来的。

- 另外,Key模板参数是给find/erase函数用的。


1.2 函数怎么适配map的pair跟set的key
关键在于模板参数_KeyOfValue,我们代码实现部分再细讲。

1.3 源码怎么实现iterator
源码比较长,直接拷贝:
cpp
template<typename _Tp>
struct _Rb_tree_iterator
{
typedef _Tp value_type;
typedef _Tp& reference;
typedef _Tp* pointer;
typedef bidirectional_iterator_tag iterator_category;
typedef ptrdiff_t difference_type;
typedef _Rb_tree_iterator<_Tp> _Self;
typedef _Rb_tree_node_base::_Base_ptr _Base_ptr;
typedef _Rb_tree_node<_Tp>* _Link_type;
_Rb_tree_iterator() _GLIBCXX_NOEXCEPT
: _M_node() { }
explicit
_Rb_tree_iterator(_Link_type __x) _GLIBCXX_NOEXCEPT
: _M_node(__x) { }
reference
operator*() const _GLIBCXX_NOEXCEPT
{ return *static_cast<_Link_type>(_M_node)->_M_valptr(); }
pointer
operator->() const _GLIBCXX_NOEXCEPT
{ return static_cast<_Link_type> (_M_node)->_M_valptr(); }
_Self&
operator++() _GLIBCXX_NOEXCEPT
{
_M_node = _Rb_tree_increment(_M_node);
return *this;
}
_Self
operator++(int) _GLIBCXX_NOEXCEPT
{
_Self __tmp = *this;
_M_node = _Rb_tree_increment(_M_node);
return __tmp;
}
_Self&
operator--() _GLIBCXX_NOEXCEPT
{
_M_node = _Rb_tree_decrement(_M_node);
return *this;
}
_Self
operator--(int) _GLIBCXX_NOEXCEPT
{
_Self __tmp = *this;
_M_node = _Rb_tree_decrement(_M_node);
return __tmp;
}
bool
operator==(const _Self& __x) const _GLIBCXX_NOEXCEPT
{ return _M_node == __x._M_node; }
bool
operator!=(const _Self& __x) const _GLIBCXX_NOEXCEPT
{ return _M_node != __x._M_node; }
_Base_ptr _M_node;
};
- 后面模拟实现部分细讲。
1.4 源码怎么实现const_iterator
普通迭代器:

const迭代器:

- 可以看到const迭代器直接在typedef的时候加上了const。
- 我们可以使用模板参数实现类似效果。
1.5 Key
- map中的结点要求key不能修改,值可以修改;set结点只有值,也不能修改。
- 源码中,map这里typedef的时候加上了const。

- set则是普通迭代器也使用了const迭代器,以此限制Key的修改。

二 模拟实现
2.1 搭框架
原来的RBTree头文件需要改下。
2.1.1 RBTreeNode结构
cpp
template<class T>
struct RBTreeNode {
T _t;
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
Colour _col;
RBTreeNode(const T& t)
:_t(t)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED)
{}
};
- 只留下T,T具体是什么,由底层(map/set)决定。
2.1.2 RBTree结构
(1)修改框架
cpp
template<class K, class T>
class RBTree {
typedef RBTreeNode<T> Node;
public:
//...
private:
Node* _root = nullptr;
};
(2)修改Insert
cpp
bool Insert(const T& t) {
if (_root == nullptr) {
_root = new Node(t);
_root->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
if (cur->_kv.first < kv.first) {
//...
}
修改到一半卡住了。
①_kv.first怎么修改?
- 源码给出了解决方案,加上模板参数KeyOfValue。
cpp
template<class K, class T,class KeyOfValue>
- 再加上MyMap跟MySet对应代码:
- MyMap
cpp
template<typename K>
class MySet {
public:
struct SetKeyOfT {
const K& operator()(const K& k) const {
return k;
}
};
private:
RedBlackTree<K,K,SetKeyOfT> _mySet;
};
- MySet
cpp
template<typename K>
class MySet {
public:
struct SetKeyOfT {
const K& operator()(const K& k) const {
return k;
}
};
private:
RedBlackTree<K,K,SetKeyOfT> _mySet;
};
- 重载操作符()。
- 改函数
cpp
KeyOfT kot;
while (cur) {
if (kot(cur->_t) < kot(t)) {
parent = cur;
cur = cur->_right;
}else if (kot(cur->_t) > kot(t)) {
parent = cur;
cur = cur->_left;
}else {
return false;
}
}
②测试Insert
分别给MyMap跟MySet写个Print方便输出观察:
cpp
template<class K, class V>
void Print(const MyMap<K,V>& m) {
for (auto& e : m) {
cout << e.first << " " << e.second;
cout << endl;
}
cout << endl;
}
template<class K>
void Print(const MySet<K>& s) {
for (auto& e:s) {
cout << e << " ";
}
cout << endl;
}
测试函数:
cpp
void TestMyMap01(void) {
cout << "TestMyMap:" << endl;
MyMap<string, string> m;
m.insert(make_pair("1", "one"));
m.insert(make_pair("2", "two"));
m.insert(make_pair("3", "three"));
Print(m);
}
void TestMySet01(void) {
cout << "TestMySet:" << endl;
MySet<string> s;
s.insert("one");
s.insert("two");
s.insert("three");
Print(s);
}
在main函数里调用运行:

符合预期。
(3)封装iterator
①搭个框架
cpp
template<class T>
struct RBTreeIterator {
typedef RBTreeNode<T> Node;
Node* _node;
RBTreeIterator(Node* node):_node(node){}
}
- 提供模板参数方便适配不同node类型的Iterator。
- 给结点类型起一个简短别名Node。
- RBTreeIterator初始化,带一个参数node给_node赋值。
②重载需要的操作符(简单)
cpp
struct RBTreeIterator {
// ...
T& operator*() const{
return _node->_t;
}
T* operator->() const{
return &_node->_t;
}
bool operator==(const Self& it) const{
return _node == it._node;
}
bool operator!=(const Self& it) const {
return _node != it._node;
}
}
- 对一个Iterator解引用*,为_node的_t内容,把Node定义找出来瞅一眼:
cpp
template<class T>
struct RBTreeNode {
T _t;
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
Colour _col;
// ...
}
- 重载成员访问符->,结果返回_node的_t地址。
- 重载成员访问符==跟!=,就是比较两个_node。
③重载需要的操作符(稍难)
- 这里就不区分前后置了。
- 重载自增++
- ++遵循左根右原则。
cpp
Self& operator++() {
if(_node->_right){
Node* minLeft = _node->_right;
while(minLeft->_left) {
minLeft = minLeft->_left;
}
_node = minLeft;
}else {
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right) {
cur = parent;
parent = cur->_parent;
}
_node = parent;
}
return *this;
}
- 为了更好演示,假设当前遍历到了A结点。
- 第一种情况:右子树不为空,则++到右子树的最左结点。

如图,++后遍历到B结点。
- 第二种情况:右子树为空,则向上找祖先。

图中向上找到B,当前结点A是B的左孩子,则++遍历到B结点。 - 第三种情况:右子树为空,向上找不到祖先。

图中A结点不属于任何一个祖先的左孩子,则已遍历完整棵树,再++就是nullptr(即End())。
- 重载自减- -
- - -遵循右根左原则
cpp
Self& operator--() {
if(!_node){
Node* maxRight = _root;
while (maxRight->_right) {
maxRight = maxRight->_right;
}
_node = maxRight;
}else if(_node->_left) {
Node* maxRight = _node->_left;
while (maxRight->_right) {
maxRight = maxRight->_right;
}
_node = maxRight;
}else {
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left) {
cur = parent;
parent = cur->_parent;
}
_node = parent;
}
return *this;
}
- 同样假设当前遍历到了A结点。
- 第一种情况:当前结点为空结点。

找到当前子树的最右结点,该结点为当前空结点–后下一个结点。
- 第二种情况:当前结点不为空,且有左孩子

当前结点的左孩子就是下一个结点。
- 第三种情况:当前结点不为空,且没有左孩子

向上找祖先,直到自己为祖先的右孩子,该祖先即为下一个结点。
- 第四种情况:当前结点不为空,且没有左孩子,且遍历不到下一个祖先。

一般不会有这种情况,当前代码下–后变为空结点。
(4)封装const_iterator
①加俩模板参数
cpp
template<class T, class Ref, class Ptr>
②在RBTree里
- 根据模板参数不同,构造Iterator跟ConstIterator。
cpp
typedef RBTreeIterator<T, T&, T*> Iterator;
typedef RBTreeIterator<T, const T&,const T*> ConstIterator;
- 加一个ConstIterator的Begin和End。
cpp
ConstIterator Begin() const {
Node* minLeft = _root;
while (minLeft && minLeft->_left) {
minLeft = minLeft->_left;
}
return ConstIterator(minLeft, _root);
}
ConstIterator End() const {
return ConstIterator(nullptr, _root);
}
(5)修改key
①修改Insert返回值
我们写的Insert返回值是bool,但是库里的返回值是一个pair:

我们也改成这样,方便获取插入位置。
修改底层RedBlackTree里的Insert:
cpp
pair<Iterator,bool> Insert(const T& t) {
//...
// traverse to find the right position for the node to insert:
while(cur){
if //...
else{
return make_pair(Iterator(cur,_root), false);
}
}
// create a new node and assign it to cur...
pair<Iterator,bool> ret = make_pair(Iterator(cur,_root), true);
// change colour...
return ret;
}
同步修改MyMap跟MySet:
cpp
class MySet {
//...
pair<iterator,bool> insert(const K& key) {
return _mySet.Insert(key);
}
}
class MyMap {
//...
pair<iterator,bool> insert(const pair<K, V>& kv) {
return _myMap.Insert(kv);
}
}
以上代码仅为展示修改部分。
②MySet的Key不能修改
现在MySet的Key是可以修改的。
测试代码如下:
cpp
void TestMySet02(void) {
cout << "TestMySet:" << endl;
MySet<string> s;
pair<MySet<string>::iterator, bool> ret = s.insert("one");
MySet<string>::iterator it = ret.first;
*it = "two";
Print(s);
}
运行:

修改代码。
直接在定义类成员变量以及typedef迭代器时给模板参数加上const:
cpp
public:
typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;
private:
RBTree<K,const K,SetKeyOfT> _mySet;
之前的测试代码现在就报错了:

③MyMap的Key不能修改
现在MyMap的Key同样是可以修改的。
测试代码如下:
cpp
void TestMyMap02(void) {
cout << "TestMyMap:" << endl;
MyMap<string, string> m;
pair<MyMap<string, string>::iterator, bool> ret = m.insert(make_pair("1", "one"));
MyMap<string, string>::iterator it = ret.first;
it->first = "2";
Print(m);
}
- 代码注释:声明一个MyMap容器对象 m,向m中插入键值对1:one,再修改该键值对的键为2。
运行:

修改代码。
同样直接在定义类成员变量以及typedef迭代器时给模板参数加上const:
cpp
public:
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;
private:
RBTree<K, pair<const K,V>,MapKeyOfT> _myMap;
回到之前的测试代码:

- 实现不能修改键,但是可以修改值。
(6)重载\[\]
- 借助insert函数直接返回值本身:
cpp
V& operator[] (const K& key) {
pair<iterator, bool> ret = insert(make_pair(key, V()));
iterator it = ret.first;
return it->second;
}
- V()返回的是V类型的临时对象。
简单测试
测试代码如下:
cpp
void TestMyMap03(void) {
MyMap<string, int> m;
m["one"] = 1;
m["one"]++;
Print(m);
}
运行:

符合预期。
封装红黑树的学习就到这里,下一篇我们要介绍两个不太一样的容器,很快会更出来~

