在实现了红黑树后,我们就可以着手实现一个简易版的map和set了,在粗略地看了一下源码后,发现,map和set底层是使用同一份红黑树的代码,set是K结构,只需要一个模板参数,map是K-V结构需要两个模板参数,如何做到共用一份红黑树代码创建的红黑树来存储数据呢?
源码中的红黑树模板参数如下
template<class K,class T,class KofT>
红黑树的节点结构
enum color
{
Red,
Black
};
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data;
color _co;
RBTreeNode(const T&val)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
, _data(val)
,_co(Red)
{}
};
只用T这个参数来存储数据,不管是set的单个数据,还是map键值对类型的参数,然后用KofT来接受一个仿函数类型,map传的仿函数用于把T接受的键值对类型的第一个数据key提取出来,set传的仿函数就直接返回T接受的数据类型。
template<class K>
class set
{
public:
struct setKofT
{
const K& operator()(const K& val) //这里就是set的仿函数
{
return val;
}
};
private:
RBTree<K, K, setKofT>_t; //红黑树需要三个模板参数,set用同一份红黑树代码,K既是存储数据也是用来比较大小建树的参数
};
template<class K, class V>
class map
{
public:
struct mapKofT
{
const K& operator()(const pair<K,V>& val) //map的仿函数用来把pair中的first提出来用在插入节点时的位置寻找。
{
return val.first;
}
};
private:
RBTree<K, pair<K,V>, mapKofT>_t;//map和set虽然用同一份红黑树的代码,但控制传入参数类型不同就可以实现存储K和K-V两种不同类型数据
};
map和set中不设计迭代器,就设计红黑树的迭代器,map和set在它们类中复用一下红黑树的迭代器即可。
迭代器就是对节点地址的封装,加上迭代器常见的几种操作,map和set的迭代器都是双向迭代器,需要注意的是++迭代器是按照树的中序遍历来走的,迭代器- -操作就是中序的逆序。
template<class T, class Ref, class Ptr> //迭代器常见的模板参数,T指明数据类型,
//Ref是指明引用返回的是const T 还是T ,Ptr指明是const T*,还是T*
struct RBiterator
{
typedef RBTreeNode<T> Node; //两个重命名,简写一下
typedef RBiterator<T, Ref, Ptr> self;
public:
RBiterator(Node*node)//迭代器构造函数用节点指针构造
:it(node)
{}
Ref operator*() //迭代器的解引用返回数据
{
return it->_data;
}
self& operator++() //这里的++就是要按照中序顺序走的
{
if (it->_right) //从当前节点走到中序的下一个节点,如果右孩子存在,就去右子树找最左端。
{
Node* cur = it->_right;
while (cur->_left)
{
cur = cur->_left;
}
it = cur;
}
else //右子树为空,往上走,如果当前节点是父亲的右孩子,还要继续往上走,
//直到为空或者当前节点是父亲的左孩子,如果走到根以上说明已经是中序末端了
{
Node* parent = it->_parent;
while (parent&& it == parent->_right)
{
it = parent;
parent = it->_parent;
}
it = parent;
}
return *this; //返回一个迭代器
}
self operator++(int) //后置++类似
{
self ret= *this;
if (it->_right)
{
Node* cur = it->_right;
while (cur->_left)
{
cur = cur->_left;
}
it = cur;
}
else
{
Node* parent = it->_parent;
while (parent && it == parent->_right)
{
it = parent;
parent = it->_parent;
}
it = parent;
}
return ret;
}
self operator--(int) //--迭代器就按++的相反来操作
{
self ret = *this;
if (it->_left)
{
Node* cur = it->_left;
while (cur->_right)
{
cur = cur->_right;
}
it = cur;
}
else
{
Node* parent = it->_parent;
while (parent && it == parent->_left)
{
it = parent;
parent = it->_parent;
}
it = parent;
}
return ret;
}
self& operator--()
{
if (it->_left)
{
Node* cur = it->_left;
while (cur->_right)
{
cur = cur->_right;
}
it = cur;
}
else
{
Node* parent = it->_parent;
while (parent&&it==parent->_left)
{
it = parent;
parent = it->_parent;
}
it = parent;
}
return *this;
}
Ptr operator->()
{
return &it->_data;
}
bool operator!=(const self&t)const
{
return it != t.it;
}
bool operator==(const self& t)const
{
return it == t.it;
}
Node* it;
};
template<class K,class T,class KofT>
class RBTree //红黑树的实现,上层map和set就共用这一份红黑树代码
{
typedef RBTreeNode<T> Node;
public:
typedef RBiterator<T, T&, T*> iterator;
typedef RBiterator<T, const T&, const T*> const_iterator;
RBTree()
:_root(nullptr)
{}
················//这里就是红黑树的一些构造函数和拷贝函数,对红黑树常用的操作函数,下面一一讲解,map和set中各自有一棵红黑树在包装各种对红黑树的操作
};
实现了迭代器,我们再来看红黑树的插入和查找,可以先实现插入和查找,返回类型为bool就行了,按照map和set插入返回类型是pair,查找的返回类型是迭代器,现在我们只需对插入和查找的返回类型做一些修改就行了。
pair<iterator,bool> insert(const T& val)
{
if (_root == nullptr)
{
_root = new Node(val);
_root->_co = Black;
return pair<iterator,bool>(iterator(_root),true); //返回pair<itreator,bool>
}
Node* parent = nullptr, * cur = _root;
KofT KT; //这里就是用到了仿函数,set的红黑树用set的仿函数,map
//的红黑树用map的仿函数,都是为了把存储数据中的key取出来,比较大小,插入节点.
while (cur)
{
if (KT(val) <KT(cur->_data))
{
parent = cur;
cur = cur->_left;
}
else if (KT(val) > KT(cur->_data))
{
parent = cur;
cur = cur->_right;
}
else
return pair<iterator, bool>(iterator(cur),false);
}
cur = new Node(val);
Node* newnode = cur; //返回插入节点的迭代器作为first的pair值,后序cur可能改变,所以先保持新节点地址
if (KT(cur->_data) < KT(parent->_data))
{
parent->_left = cur;
}
else
parent->_right = cur;
cur->_parent = parent;
while (parent && parent->_co == Red) //更新颜色
{
Node* grandparent = parent->_parent;
if (parent == grandparent->_left)
{
Node* uncle = grandparent->_right;
if (uncle && uncle->_co == Red)
{
parent->_co = uncle->_co = Black;
grandparent->_co = Red;
cur = grandparent;
parent = cur->_parent;
}
else // if(uncle == nullptr || uncle->_co == Black) //g
{ //p
if (parent->_left == cur) //c
{
RotateR(grandparent); //右单旋 parent变黑色grandparent变红色
grandparent->_co = Red;
parent->_co = Black;
}
else //g
{ //p
RotateL(parent); //c
RotateR(grandparent);
grandparent->_co = Red;
cur->_co = Black;
}
break;
}
}
else
{
Node* uncle = grandparent->_left;
if (uncle && uncle->_co == Red)
{
parent->_co = uncle->_co = Black;
grandparent->_co = Red;
cur = grandparent;
parent = cur->_parent;
}
else //if(uncle == nullptr || uncle->_co == Black)
{
if (parent->_right == cur) //g
{ //p
RotateL(grandparent); //左单旋 parent变黑色grandparent变红色 //c
grandparent->_co = Red;
parent->_co = Black;
}
else //g
{ //p
RotateR(parent); //c
RotateL(grandparent);
grandparent->_co = Red;
cur->_co = Black;
}
break;
}
}
}
_root->_co = Black;
return pair<iterator, bool>(iterator(newnode),true); //返回pair
}
查找操作就要用到红黑树的第一个模板参数了,因为我们就是按key值来查找的,查找的函数就只有一个key的函数参数,所以构建红黑树,就传一个key的模板参数。
iterator find(const K& val)const
{
Node* cur = _root;
KofT KT;
while (cur)
{
if (val < KT(cur->_data))
{
cur = cur->_left;
}
else if (val > KT(cur->_data))
{
cur = cur->_right;
}
else
return iterator(cur);
}
return end(); //迭代器指向最后一位数据的后一位。
}
红黑树的begin和end接口,上层map和set的begin和end就直接调用红黑树的begin和end接口。
iterator begin()
{
if (_root == nullptr)
return iterator(nullptr);
Node* cur = _root;
while (cur->_left)
{
cur = cur->_left;
}
return iterator(cur);
}
iterator end()
{
return iterator(nullptr);
}
还有判空empty和数据数量size两个接口也由红黑树实现,map和set直接调用即可
bool empty()const
{
return _root == nullptr;
}
size_t size()const
{
return _size(_root);
}
size_t _size(Node* root)const
{
if (root == nullptr)
return 0;
return _size(root->_left) + _size(root->_right) + 1;
}
到这里map和set的主要接口就可以实现了
template<class K, class V>
class map
{
public:
struct mapKofT
{
const K& operator()(const pair<K,V>& val)
{
return val.first;
}
};
typedef typename RBTree<K, pair<K, V>, mapKofT>::iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
iterator find(const K&val)
{
return _t.find(val);
}
pair<iterator,bool> insert(const pair<K, V>& val)
{
return _t.insert(val);
}
V& operator[](const K& k)
{
auto it = _t.insert(pair<K, V>(k, V()));
return it.first->second;
}
bool empty()const
{
return _t.empty();
}
size_t size()const
{
return _t.size();
}
private:
RBTree<K, pair<K,V>, mapKofT>_t;
};
template<class K>
class set
{
public:
struct setKofT
{
const K& operator()(const K& val)
{
return val;
}
};
typedef typename RBTree<K, K, setKofT>::iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
pair<iterator, bool> insert(const K& val)
{
return _t.insert(val);
}
iterator find(const K&val)
{
return _t.find(val);
}
bool empty()const
{
return _t.empty();
}
size_t size()const
{
return _t.size();
}
private:
RBTree<K, K, setKofT>_t;
};
可以看出map和set主要操作都是在红黑树中就实现了,只需给明红黑树需要的模板参数即可,我们也不必在map和set中实现拷贝构造,赋值操作,析构函数等,因为map和set中成员的数据就只有自定义类型的红黑树,我们同样只要实现红黑树的这些成员方法即可,它们会自动调用自定义类型相应的构造函数和析构函数。
RBTree(const RBTree<K,T,KofT>&tree)
{
_root = copy(tree._root); //实现深拷贝
}
RBTree<K, T, KofT>& operator=(RBTree<K, T, KofT> t)
{
swap(_root, t._root); //赋值方法现代写法
return *this;
}
~RBTree() //析构函数
{
destory(_root);
_root = nullptr;
}
Node* copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newnode = new Node(root->_data); //一个个节点对应把数据拷贝创建新节点复刻
newnode->_co = root->_co; //颜色同样要拷贝
newnode->_left = copy(root->_left); //连接关系拷贝
newnode->_right = copy(root->_right);
if (newnode->_left)
newnode->_left->_parent = newnode;
if (newnode->_right)
newnode->_right->_parent = newnode;
return newnode;
}
void destory(Node* root)
{
if (root == nullptr)
return;
destory(root->_left); //析构顺序是要按照后序来析构的
destory(root->_right);
delete root;
}
至此就实现了一个简易的map和set。