这篇文章将介绍set类和map类的模拟实现,set和map的底层都是红黑树来实现的。
通过模拟实现set和map,我们对模版、仿函数、复用会有更深刻的认识!
一、红黑树
如果想要详细的了解红黑树的实现,请看我的另一篇博客:
我在这里直接给出实现set和map所需要的红黑树代码的框架(包括迭代器)
//表示红黑树节点颜色
enum Colour
{
BLACK,
RED
};
//红黑树的节点
template<class T>
struct RBTreeNode
{
public:
RBTreeNode(const T& data, Colour colour = RED)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_data(data)
,_colour(colour)
{}
public:
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data;
Colour _colour;
};
//红黑树的迭代器
template<class T>
class __TreeIterator
{
public:
typedef RBTreeNode<T> Node;
typedef __TreeIterator<T> Self;
public:
__TreeIterator(Node* node)
:_node(node)
{}
T& operator*(){return _node->_data;}
T* operator->(){return &(_node->_data);}
bool operator!=(const Self& s){return _node != s._node;}
Self& operator++(){}//一会讲解
Self& operator--(){}//不讲解,大家自己实现
private:
Node* _node;
};
//红黑树本身
template<class K, class T, class KeyOfValue>
class RBTree
{
public:
typedef RBTreeNode<T> Node;
typedef __TreeIterator<T> iterator;
public:
//迭代器
iterator begin(){}
iterator end(){}
//插入
pair<iterator, bool> Insert(const T& data){}//请看我写红黑树的博客
//旋转处理
//1.左单旋
void RotateL(Node* parent){}//请看我写红黑树的博客
//2.右单旋
void RotateR(Node* parent){}//请看我写红黑树的博客
//查找
iterator Find(const K& data){}//请看我写红黑树的博客
private:
Node* _root = nullptr;
};
在这个部分重点要讲的是红黑树迭代器的实现和模版参数的含义
1.迭代器
红黑树的迭代器和之前模拟的vector/list的本质都一样:通过封装节点的指针,重载++、--、
*、->等运算符来达到我们想要的效果。
对于红黑树的迭代器,我选择重点讲解++:如果++迭代器,就会指向中序的下一个
(1)当前节点(it)的右不为空:++以后就是it的右子树的最左节点
(2)当前节点(it)的右为空:++以后的节点一定是他的某一个祖先
具体是哪一个祖先呢?标记cur和他的parent,当cur是parent是左孩子时,parent就是我们要
找的++后的节点;当cur是parent是右孩子时,继续往上更新。
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;
}
_node = parent;
}
//前置++:返回++之后的
return *this;
}
2.模版参数
template<class K, class T, class KeyOfValue>
(1)class K:对set来说就是key的类型;对map来说就是key:value键值对中key的类型
(2)class T:表示红黑树节点里面存储的数据的类型。
(3)class KeyOfValue:仿函数,作用是得到要比较的元素。
对于set:作用是将key中的key取出来,也就是直接返回。
对于map:作用是将key:value键值对里的key取出来。
有了KeyOfValue这个仿函数,就可以复用同一棵树来实现set和map,减少了代码冗余。
因为我们在插入的过程中需要根据key来把数据插入到正确的位置。如果是set的话,节点数据域
就是一个值,可以直接进行比较;但如果是map的话,节点数据域是一个键值对,我们无法直接
根据这个键值对直接进行比较。我们的确可以实现两颗红黑树,一颗用于set、一颗用于map,
但是这样代码冗余。所以我们使用仿函数来提取出key,这样仅用一颗树就可以实现set和map。
3.begin和end
(1)iterator begin()的实现
//返回起始位置的迭代器
iterator begin()
{
//找最小的:就是找最左边的
Node* cur = _root;
while (cur && cur->_left)
cur = cur->_left;
return iterator(cur);
}
(2)iterator end()的实现
//返回结束为止的迭代器
iterator end()
{
//找最后一个
return iterator(nullptr);
}
(3)STL中红黑树的结构
STL中的红黑树增加了一个头节点,目的是将end()放在头结点的位置。
而我们只是为了了解和学习原理,所以没有增加一个头节点。
二、模拟实现set
set的底层结构就是红黑树,因此在set内部封装一棵红黑树,然后包装一下接口即可将set实现
template<class K>
class set
{
public:
//仿函数:作用是将key中的key取出来,也就是直接返回
struct SetKeyOfValue
{
const K& operator()(const K& k)
{
return k;
}
};
//迭代器 typename告诉编译器这是一个类型
typedef typename RBTree<K, K, SetKeyOfValue>::iterator iterator;
iterator begin(){return _t.begin();}
iterator end(){return _t.end();}
pair<iterator, bool> Insert(const K& k){return _t.Insert(k);}
private:
RBTree<K, K, SetKeyOfValue> _t;
};
下图展示了各个模版参数之间的调用
这样我们就实现了一个自己的set,map的实现过程和set类似,下面我们来看map。
三、模拟实现map
map的底层结构就是红黑树,因此在map中封装一棵红黑树,然后包装一下接口即可将map实现
template<class K, class V>
class map
{
public:
//仿函数:作用是将key:value键值对里的key取出来
struct MapKeyOfValue
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
//迭代器 typename告诉编译器这是一个类型
typedef typename RBTree<K, pair<K, V>, MapKeyOfValue>::iterator iterator;
iterator begin(){return _t.begin();}
iterator end(){return _t.end();}
//重载[]
V& operator[](const K& key)
{
pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
return (ret.first)->second;
}
pair<iterator, bool> Insert(const pair<K, V>& kv){return _t.Insert(kv);}
private:
RBTree<K, pair<K, V>, MapKeyOfValue> _t;
};
下图展示了各个模版参数之间的调用
与set不同的是,map中还有一个很重要的东西:重载[],下面讲解一下operator[]的实现。
(1)STL中insert的实现:返回 迭代器:布尔 的键值对
①iterator的含义:指向被插入元素的迭代器,如果元素已经存在,就是指向现有元素的迭代器
②bool的含义:插入成功为true,插入失败为flase
(2)实现operator[]:V& operator[](const K& key)
//重载[]
V& operator[](const K& key)
{
pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
return (ret.first)->second;
}
①ret.first指向了插入的节点
②(ret.first)->second利用了迭代器中 重载-> 取出来key:value中的value