一. 源码及框架分析


通过上图对框架的分析,我们可以看到源码中rb_tree⽤了⼀个巧妙的泛型思想实现,rb_tree是实
现key的搜索场景,还是key/value的搜索场景不是直接写死的,⽽是由第⼆个模板参数Value决定
_rb_tree_node中存储的数据类型。
set实例化rb_tree时第⼆个模板参数给的是key,map实例化rb_tree时第⼆个模板参数给的是
pair<const key, T>,这样⼀颗红⿊树既可以实现key搜索场景的set,也可以实现key/value搜索场
景的map。
要注意⼀下,源码⾥⾯模板参数是⽤T代表value,⽽内部写的value_type不是我们我们⽇常
key/value场景中说的value,源码中的value_type反⽽是红⿊树结点中存储的真实的数据的类型。
rb_tree第⼆个模板参数Value已经控制了红⿊树结点中存储的数据类型,为什么还要传第⼀个模板
参数Key呢?尤其是set,两个模板参数是⼀样的,这是很多同学这时的⼀个疑问。要注意的是对于
map和set,find/erase时的函数参数都是Key,所以第⼀个模板参数是传给find/erase等函数做形
参的类型的。对于set⽽⾔两个参数是⼀样的,但是对于map⽽⾔就完全不⼀样了,map insert的
是pair对象,但是find和ease的是Key对象。
吐槽⼀下,这⾥源码命名⻛格⽐较乱,set模板参数⽤的Key命名,map⽤的是Key和T命名,⽽
rb_tree⽤的⼜是Key和Value,可⻅⼤佬有时写代码也不规范,乱弹琴。
我们可以发现,库中也是通过一棵树结构来实现set和map的,是通过第二个参数来区分的,那么可能有人要问了,如果是根据第二个参数区分的话,那么我们第一个参数的作用是什么呢?是不是就没有用了,不需要了呢?
当然不是的,对于我们的set,只有一个key,第一个参数和第二个参数可要可不要,但是对于我们的map就不一样了,我们的map的find和erase函数都是需要用到key的,第二个参数是pair,所以此时第一个参数就显得很重要了。
下面我们来模拟实现一下,可以帮助我们更好的理解。
二. 模拟实现map和set
2.1 实现出复⽤红⿊树的框架,并⽀持insert
参考源码框架,map和set复⽤之前我们实现的红⿊树。
我们这⾥相⽐源码调整⼀下,key参数就⽤K,value参数就⽤V,红⿊树中的数据类型,我们使⽤
T。
其次因为RBTree实现了泛型不知道T参数导致是K,还是pair<K, V>,那么insert内部进⾏插⼊逻辑
⽐较时,就没办法进⾏⽐较,因为pair的默认⽀持的是key和value⼀起参与⽐较,我们需要时的任
何时候只⽐较key,所以我们在map和set层分别实现⼀个MapKeyOfT和SetKeyOfT的仿函数传给
RBTree的KeyOfT,然后RBTree中通过KeyOfT仿函数取出T类型对象中的key,再进⾏⽐较,具体
细节参考如下代码实现。

这是我们实现的一个树结构。
myset

mymap

此时我们就发现了一个问题,我们在插入和find等函数中,我们的map需要kv.first的比较,但是,我们传过去的V是一个pair类型的对象,此时是无法完成比较的,两个pair类型的参数的比较,底层的比较逻辑是无法满足我们的需求的,即使能满足,我们的find函数一个pair类型和一个K类型的比较也是无法完成的,此时有人可能会想到用仿函数直接写一个比较,但是此时你的map的仿函数第二个参数的类型也是不确定的,你还是没法写,因为insert中第二个参数类型是pair,find中第二个参数类型又变成K了,此时还是无法完成的,所以,我们不应该直接通过仿函数来比较,而是通过仿函数拿到kv.first的值即可。下面我们来实现一下。
仿函数的实现


大家可以看一下,我们增加了仿函数,我们的RBTree类也要多一个函数模板参数了。

此时我们修改一下insert和find函数即可。

这是我们插入函数修改的地方,直接使用仿函数来获取我们所需要的值。

这是我们的find函数,此时就完成了我们需要的操作。
2.2 ⽀持iterator的实现
iterator实现思路分析
iterator实现的⼤框架跟list的iterator思路是⼀致的,⽤⼀个类型封装结点的指针,再通过重载运算
符实现,迭代器像指针⼀样访问的⾏为。
这⾥的难点是operator++和operator--的实现。之前使⽤部分,我们分析了,map和set的迭代器⾛
的是中序遍历,左⼦树->根结点->右⼦树,那么begin()会返回中序第⼀个结点的iterator也就是10
所在结点的迭代器。
迭代器++的核⼼逻辑就是不看全局,只看局部,只考虑当前中序局部要访问的下⼀个结点。
迭代器++时,如果it指向的结点的右⼦树不为空,代表当前结点已经访问完了,要访问下⼀个结点
是右⼦树的中序第⼀个,⼀棵树中序第⼀个是最左结点,所以直接找右⼦树的最左结点即可。
迭代器++时,如果it指向的结点的右⼦树空,代表当前结点已经访问完了且当前结点所在的⼦树也
访问完了,要访问的下⼀个结点在当前结点的祖先⾥⾯,所以要沿着当前结点到根的祖先路径向上
找。
如果当前结点是⽗亲的左,根据中序左⼦树->根结点->右⼦树,那么下⼀个访问的结点就是当前结
点的⽗亲;如下图:it指向25,25右为空,25是30的左,所以下⼀个访问的结点就是30。
如果当前结点是⽗亲的右,根据中序左⼦树->根结点->右⼦树,当前当前结点所在的⼦树访问完
了,当前结点所在⽗亲的⼦树也访问完了,那么下⼀个访问的需要继续往根的祖先中去找,直到找
到孩⼦是⽗亲左的那个祖先就是中序要问题的下⼀个结点。如下图:it指向15,15右为空,15是10
的右,15所在⼦树话访问完了,10所在⼦树也访问完了,继续往上找,10是18的左,那么下⼀个
访问的结点就是18。
end()如何表⽰呢?如下图:当it指向50时,++it时,50是40的右,40是30的右,30是18的右,18
到根没有⽗亲,没有找到孩⼦是⽗亲左的那个祖先,这是⽗亲为空了,那我们就把it中的结点指针
置为nullptr,我们⽤nullptr去充当end。需要注意的是stl源码空,红⿊树增加了⼀个哨兵位头结点
做为end(),这哨兵位头结点和根互为⽗亲,左指向最左结点,右指向最右结点。相⽐我们⽤
nullptr作为end(),差别不⼤,他能实现的,我们也能实现。只是--end()判断到结点时空,特殊处
理⼀下,让迭代器结点指向最右结点。具体参考迭代器--实现。
迭代器--的实现跟++的思路完全类似,逻辑正好反过来即可,因为他访问顺序是右⼦树->根结点->
左⼦树,具体参考下⾯代码实现。
set的iterator也不⽀持修改,我们把set的第⼆个模板参数改成const K即可, RBTree<K,
const K, SetKeyOfT> _t;
map的iterator不⽀持修改key但是可以修改value,我们把map的第⼆个模板参数pair的第⼀个参
数改成const K即可, RBTree<K, pair<const K, V>, MapKeyOfT> _t;
我们先直接给出代码再来解释。

我们都知道,我们想使用迭代器,是需要拥有两个函数的begin()和end()函数,还有一些++和--的一些基本迭代器的操作,首先这些*和->还有!=和==操作都是很简单的,接下来我们重点讲一下++和--的操作,我们先来看++操作。

我们来看这个图,所谓的++操作就是在中序的基础上往后面移动一位,这是毫无疑问的,接下来我们来看它有几种情况,当我们的这个结点存在右孩子的时候,这是一种情况,我们看10这个结点,它存在右孩子,那么它中序的下一个结点怎么找呢?当然就是他的右孩子的最左结点了,这就是中序的下一位,这是很好理解的,但是如果它没有右孩子呢?这时候该怎么办呢?当指向15的时候是不存在右孩子的,这时候我们就要从祖先结点中去找我们的中序的下一位了,这是毫无疑问的,15这个我们从图中很容易找到,下一个结点是18,18是15的爷爷,那么是不是不存在右孩子,爷爷就是我们中序的下一位呢?当然不是了,你再看看35,不存在右孩子,但是此时他的父亲是它中序的下一位,而不是爷爷,显然我们上面说的是错误的。
那么有什么规律呢?我们可以看出,我们结束的条件是我们的cur==parent->left,当cur==parent-》right的时候是继续往上面走的,这时候我们的这个问题就得到解决了,当时如果永远不满足这个条件该怎么结束呢?比如我们的50,他是永远不会满足我们的cur==parent->left,那么我们就要再加一个条件,就是parent存在,此时问题就得到了解决了。
++得到了解决,--的做法是一样的,大家可以自己画个图完成一下代码的书写。
Begin和End方法

这是我们的所写的Begin和End方法,因为End指向的是最后一个元素的下一个位置,所以我们直接传入nullptr就行,Begin我们找到树的最左结点即可,都是很简单实现的。
我们实现了基本的迭代器,但是还存在const类型的迭代器呀,我们应该怎么实现呢?
Const_Iterator
我们要怎么实现这个类型的迭代器呢?难道是重新写一个结构体来实现const类型的吗?当然不是了,为了使我们的代码的复用性增高,我们只需要增加两个模板参数即可完成操作。
下面我来实现一下。

我们只需要这样改一下即可,增加了两个模板参数来区分我们的T&,T*和const T& , const T*。

这样改一下就行了。

这是我们在我们的mymap中实现的begin和end方法,调用一下RBTree中的即可。
有人可能会问,这个typename的作用是什么呢?
在这句代码中,typename 的核心作用是告诉编译器:RBTree<K, const K, SetKeyOfT>::Iterator 是一个类型(而非成员变量或其他实体),从而避免编译歧义。
具体原因分析:
C++ 模板中,当通过类模板的嵌套依赖名称(即依赖于模板参数的内部名称,如 RBTree<...>::Iterator)引用类型时,编译器在解析阶段无法确定该名称是 “类型” 还是 “非类型”(如静态成员变量)。
例如,对于 RBTree<K, const K, SetKeyOfT>::Iterator:
RBTree是一个类模板,其模板参数K是不确定的(依赖于外部模板参数);Iterator是RBTree内部的一个嵌套类型(迭代器类型),但编译器在未实例化模板前,无法仅凭名称判断它是类型还是其他成员(比如静态变量)。
此时,必须用 typename 显式声明:这个嵌套依赖名称是一个类型,编译器才能正确解析。
反例:缺少 typename 会导致什么?
如果去掉 typename:
cpp
运行
typedef RBTree<K, const K, SetKeyOfT>::Iterator iterator; // 错误
编译器可能会将 Iterator 误认为是 RBTree<...> 的静态成员变量,从而报 “语法错误” 或 “无法将非类型转换为类型” 的错误。

底层红黑树的实现,它还加了一个哨兵位的头结点,左孩子是begin(),右孩子是最后一个结点。
哨兵位结点的作用:
红黑树中哨兵位头结点(Sentinel Node) 是一个虚拟节点(不存储实际数据),核心作用是简化边界条件处理、统一代码逻辑、提升操作效率,避免因空指针(nullptr)判断导致的复杂分支。
一、核心作用(分点解析)
1. 替代空指针(nullptr),消除边界判断
红黑树的节点操作(插入、删除、旋转)中,大量涉及 “父节点、左 / 右子节点是否为空” 的判断(如叶子节点的子节点、根节点的父节点)。
-
没有哨兵时:需频繁判断
node->_left == nullptr或node->_parent == nullptr,代码冗余且易出错; -
有哨兵时:将所有 “空指针位置” 统一指向哨兵节点(如叶子节点的左 / 右子节点、根节点的父节点均为哨兵),无需再判断空指针,直接操作哨兵即可。
2. 统一根节点与普通节点的操作逻辑
根节点的特殊之处在于 “无父节点”,删除或旋转时需单独处理 “根节点更新” 逻辑。
-
哨兵节点作为根节点的父节点:根节点的操作(如旋转后更新父指针)可与普通节点完全一致,无需额外编写根节点专属代码。
3. 简化迭代器遍历的边界处理
红黑树迭代器的 Begin()(最左节点)和 End()(尾后位置)需要明确边界:
-
哨兵节点可作为
End()的指向(尾后迭代器),无需返回nullptr; -
迭代器移动时(如
operator++到尾后),直接指向哨兵,判断迭代器是否相等时只需对比节点指针(而非判断nullptr),逻辑更统一。
2.3 map⽀持[]
根据我们前面所学的[]的实现要借助Insert函数,我们要把返回值改成pair<iterator,bool>类型的。

我们改完之后就可以完成我们的[]的书写了。
我们可以很容易的想到,bool值的利用就是来判断我们是否插入成功,而迭代器是存放我们插入元素的迭代器的,使我们可以访问到它。
看一下下面的代码就知道了

这是我们在我们写的map中实现的[]操作符的使用,我们可以使用[]来访问里面的元素,当这个元素不存在的时候进行插入操作,存在的时候就访问,这里的严格来说应该是->->才能访问的到second的,因为第一个箭头是我们得到了
这个地址,想访问里面的元素,还需要一个箭头来操作,但是我们编译器优化之后,只需要一个箭头即可。
这里我们的map[]就完成了。
测试一下我们的核心功能就结束了。




我们发现符合我们的预期,我们的代码书写就完成了,这个代码有点麻烦,我附一下源代码吧。
附录:
RBTree类
#pragma once
// 枚举值表示颜色
enum Colour
{
RED,
BLACK
};
// 这里我们默认按key/value结构实现
template<class T>
struct RBTreeNode
{
// 这里更新控制平衡也要加入parent指针
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)
{
}
};
template<class T, class Ref, class Ptr>
struct __TreeIterator
{
typedef RBTreeNode<T> Node;
typedef __TreeIterator<T, Ref, Ptr> Self;
Node* _node;
Node* _root;
__TreeIterator(Node* node, Node* root)
:_node(node)
, _root(root)
{
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
Self& operator++()
{
if (_node->_right)
{
// 右子树的最左节点
_node = _node->_right;
while (_node->_left)
{
_node = _node->_left;
}
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
Self& operator--()
{
if (_node == nullptr) // end()
{
// --end(),特殊处理,走到中序最后一个结点,整棵树的最右结点
Node* rightMost = _root;
while (rightMost && rightMost->_right)
{
rightMost = rightMost->_right;
}
_node = rightMost;
}
else if (_node->_left)
{
// 左子树不为空,中序左子树最后一个
Node* rightMost = _node->_left;
while (rightMost->_right)
{
rightMost = rightMost->_right;
}
_node = rightMost;
}
else
{
// 孩子是父亲右的那个祖先
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left)
{
cur = parent;
parent = cur->_parent;
}
_node = parent;
}
return *this;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
bool operator==(const Self& s)
{
return _node == s._node;
}
};
template<class K, class T, class KeyOfT>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
typedef __TreeIterator<T, T&, T*> Iterator;
typedef __TreeIterator<T, const T&, const T*> ConstIterator;
Iterator Begin()
{
Node* minLeft = _root;
while (minLeft && minLeft->_left)
{
minLeft = minLeft->_left;
}
return Iterator(minLeft, _root);
}
Iterator End()
{
return Iterator(nullptr, _root);
}
ConstIterator Begin() const
{
Node* minLeft = _root;
while (minLeft && minLeft->_left)
{
minLeft = minLeft->_left;
}
return ConstIterator(minLeft, _root);
}
ConstIterator End() const
{
return ConstIterator(nullptr, _root);
}
pair<Iterator, bool> Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return { Iterator(_root, _root), true };
}
Node* parent = nullptr;
Node* cur = _root;
KeyOfT kot;
while (cur)
{
if (kot(cur->_data) < kot(data))
{
parent = cur;
cur = cur->_right;
}
//else if (kot(cur->_data) > kot(data))
else if (kot(data) < kot(cur->_data))
{
parent = cur;
cur = cur->_left;
}
else
{
return { 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;
if (parent == grandfather->_left)
{
// g
// p u
Node* uncle = grandfather->_right;
// 叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上处理
cur = grandfather;
parent = cur->_parent;
}
else
{
// 叔叔不存在或者存在且为黑
// g
// p u
//c
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// p u
// c
RotateL(parent);
RotateR(grandfather);
grandfather->_col = RED;
cur->_col = BLACK;
}
break;
}
}
else
{ // g
// u p
Node* uncle = grandfather->_left;
// 叔叔存在且为红,-》变色即可
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上处理
cur = grandfather;
parent = cur->_parent;
}
else // 叔叔不存在,或者存在且为黑
{
// 情况二:叔叔不存在或者存在且为黑
// 旋转+变色
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// u p
// c
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return { Iterator(newnode, _root), true };
}
Iterator Find(const K& key)
{
KeyOfT kot;
Node* cur = _root;
while (cur)
{
if (kot(cur->_data) < key)
{
cur = cur->_right;
}
else if (kot(cur->_data) > key)
{
cur = cur->_left;
}
else
{
return Iterator(cur, _root);
}
}
return End();
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
subL->_right = parent;
if (subLR)
subLR->_parent = parent;
Node* parentParent = parent->_parent;
parent->_parent = subL;
//if (parentParent == nullptr)
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
{
parentParent->_left = subL;
}
else
{
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* parentParent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (parentParent == nullptr)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parent == parentParent->_left)
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
// 前序递归遍历
bool Check(Node* root, int blackNum, const int refNum)
{
if (root == nullptr)
{
// 前序遍历走到空时,意味着一条路径走完了
//cout << blackNum << endl;
if (refNum != blackNum)
{
cout << "存在黑色结点的数量不相等的路径" << endl;
return false;
}
return true;
}
// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了
if (root->_col == RED && root->_parent && root->_parent->_col == RED)
{
cout << root->_kv.first << "存在连续的红色结点" << endl;
return false;
}
if (root->_col == BLACK)
{
blackNum++;
}
return Check(root->_left, blackNum, refNum)
&& Check(root->_right, blackNum, refNum);
}
bool IsBalanceTree()
{
if (_root == nullptr)
return true;
if (_root->_col == RED)
return false;
// 参考值
int refNum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refNum;
}
cur = cur->_left;
}
return Check(_root, 0, refNum);
}
private:
Node* _root = nullptr;
};
mymap:
#pragma once
#include"RBTree.h"
namespace qrh
{
template<class K, class V>
class map
{
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>::ConstIterator 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 pair<K, V>& kv)
{
return _t.Insert(kv);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = _t.Insert({ key, V() });
return ret.first->second;
}
private:
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
}
myset:
#pragma once
#include"RBTree.h"
namespace qrh
{
template<class K>
class set
{
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;
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, const K, SetKeyOfT> _t;
};
}
Test:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
using namespace std;
#include"RBTree.h"
// 测试代码
#include"myset.h"
#include"mymap.h"
void Print(const qrh::set<int>& s)
{
for (auto& e : s)
{
//e++;
cout << e << " ";
}
cout << endl;
}
void test_set()
{
qrh::set<int> s;
s.insert(4);
s.insert(1);
s.insert(3);
s.insert(6);
s.insert(16);
s.insert(6);
s.insert(36);
s.insert(-6);
s.insert(9);
qrh::set<int>::iterator it = s.begin();
while (it != s.end())
{
//*it = 1;
cout << *it << " ";
++it;
}
cout << endl;
for (auto& e : s)
{
//e++;
cout << e << " ";
}
cout << endl;
Print(s);
it = s.end();
while (it != s.begin())
{
--it;
cout << *it << " ";
}
cout << endl;
}
void test_map()
{
qrh::map<string, string> dict;
dict.insert({ "sort", "排序" });
dict.insert({ "left", "左边" });
dict.insert({ "right", "右边" });
qrh::map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{
//it->first += 'x';
it->second += 'x';
cout << it->first << ":" << it->second << endl;
++it;
}
cout << endl;
}
int main()
{
test_set();
test_map();
return 0;
}
3518

被折叠的 条评论
为什么被折叠?



