目录
1.源码及框架分析
SGI-STL30版本源代码,map和set的源代码在map/set/stl_map.h/stl_set.h/set_tree.h等几个头文件中。
1.通过上图对框架的分析,我们可以看到源码中rb_tree用了一个巧妙的泛型思想实现,rb_tree是实现key的搜索场景还是key/value的搜索场景不是直接写死的,而是由第二个模板参数Value决定_rb_tree_node中存储的数据类型。
2.set实例化rb_tree时第二个模板参数给的是key,map实例化rb_tree时第二个模板参数给的是pair<const key, T>,这样一棵红黑树既可以实现set,也可以实现map。
3.要注意一下,源码里面模板参数是用T代表value,⽽内部写的value_type不是我们我们⽇常key/value场景中说的value,源码中的value_type反⽽是红⿊树结点中存储的真实的数据的类型。
4.rb_tree第⼆个模板参数Value已经控制了红⿊树结点中存储的数据类型,为什么还要传第⼀个模板参数Key呢?尤其是set,两个模板参数是⼀样的。要注意的是对于map和set,find/erase时的函数参数都是Key,所以第⼀个模板参数是传给find/erase等函数做形参的类型的。对于set⽽⾔两个参数是⼀样的,但是对于map⽽⾔就完全不⼀样了,map insert的是pair对象,但是find和ease的是Key对象。
2.封装红黑树模拟实现map和set
2.1实现出泛型的红黑树框架
这⾥相⽐源码调整⼀下,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,再进⾏⽐较。
2.1.1红黑树的节点结构
//红黑树节点结构
//这里的T代表的是红黑树节点里面存储的数据类型
//红黑树的节点只存储数据和对应数据的指针,所有只需要一个模板参数来表示存储的数据类型即可
//对于set来说是key
//对于map来说是pair
template<class 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)
{}
};
2.1.2map和set中的仿函数 -- 用于取出Key
//MyMap.h中的仿函数
struct MapKeyOfT
{
//返回pair中的first作为map的Key
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
//MySet.h中的仿函数
struct SetKeyOfT
{
//返回Key作为set的Key
const K& operator()(const K& key)
{
return key;
}
};
2.1.3泛型红黑树的Insert()
在Insert()的实现中要进行Key大小的比较,对于set来说直接可以使用key比较,对于map来说,需要把pair中的first取出来作为key进行比较,所以在红黑树的模板中引入一个模板参数KeyOfT,用于接收上层map或者set传来的仿函数,使用仿函数在红黑树中提取出Key进行比较。
pair<Iterator, bool> Insert(const T& data)
{
//如果为空树,插入的节点作为根
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK; //根必须为黑色
return {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
{
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;
//父亲是红色,出现连续红色节点,需要进行处理
//1.叔叔在右边
//1.1 叔叔存在且为红色 -- 父亲和叔叔变为黑色,爷爷变为红色继续向上处理
//1.2 叔叔不存在或者叔叔为黑
//1.2.1 插入在父亲节点左边 -- 爷爷节点进行右单旋,且父亲节点变黑,爷爷节点变红
//1.2.2 插入在父亲节点右边 -- 父亲节点左旋爷爷节点右旋,且当前节点变黑,爷爷节点变红
//2.叔叔在左边
while (parent && parent->_col == RED) //判断parent是否存在是为了处理向上处理到根的情况
{
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 = gran