map ,set的简单模拟实现
这一节我们来用红黑树封装map和set,为了达到map和set的正常功能,所以就要对红黑树再进行改造,我会在封装中解释为什么要这样改
一,map和set的结构
我们在之前介绍过map和set的用法,map和set是一种关联式容器,其内部存储的是键值对,底层都是用红黑树实现的,所以我们模拟实现map和set时直接用红黑树的接口就可以
我们先来写出map和set的基本结构:
template<class K>
class Myset {
public:
//..
private:
RBTree<K, K, SetKeyofT> _t;
};
template<class K,class V>
class Mymap {
public:
//..
private:
RBTree<K, pair<const K, V>, MapKeyofT> _t;//这里的const的pair
};
这里map不能修改Key值,但是可以修改Value,所以构造时对Key加上 const
二,封装set
用红黑树封装set,set中只存储了Key值,而map中存储的是键值对pair,所以我们要改造红黑树中存储的类型来让我们达到给红黑树传入不同的类型都可以完成插入等操作
template<class K, class T, class KeyofT>//KeyofT是一个仿函数用来获取给定的数据类型
class RBTree {
typedef RBTreeNode<T> Node;
public:
typedef _RBTreeIterator<T,T*,T&> iterator;
typedef _RBTreeIterator<T,const T*,const T&> const_iterator;
//...
};
其中的 第二个模板参数T决定红黑树存储的是什么
,set传入的是Key则底层的红黑树就存储的是Key
这里又增加了一个模板参数KeyofT,这个是一个仿函数类型,用来获取set或者map给红黑树传入的数据类型。因为对红黑树改造后,set传入的是一个Key,而map传入的是pair键值对,在插入操作时,要比较节点中的Key来决定插入节点的位置。要完成比较,map和set就要传入对应得仿函数去给红黑树自己得具体数据类型去比较
这里我们以一小段为例:
pair<iterator, bool> insert(const T& data){
if (_root == nullptr) {
_root = new Node(data);
_root->_col = BLACK;
return make_pair(_root, true);
}
Node* parent = nullptr;
Node* cur = _root;
KeyofT kot;//实例化仿函数
//找到插入的位置
while (cur) {
if (kot(data) > kot(cur->_data)) {
parent = cur;
cur = cur->_right;
}
else if (kot(data) < kot(cur->_data)) {
parent = cur;
cur = cur->_left;
}
else {
return make_pair(cur, false);
}
}
//..
}
这里用到仿函数 kot(data)
和 kot(cur->_data)
就是去获取set得Key去比较大小来决定插入到parent的左孩子还是右孩子。
所以我们要在set内部实现一个仿函数去返回其存储的数据类型:
template<class K>
class Myset {
public:
struct SetKeyofT//仿函数,返回set中存储的类型
{
const K& operator()(const K& Key) {
return Key;
}
};
pair<iterator, bool> insert(const K& Key) {
return _t.insert(Key);
}
void inorder() {
return _t.Inorder();
}
private:
RBTree<K, K, SetKeyofT> _t;
};
set中存储的K值是不能修改的,所以在迭代器的封装中可以直接用红黑树的const迭代器去封装set的普通迭代器和const迭代器
template<class K>
class Myset {
public:
struct SetKeyofT//仿函数,返回set中存储的类型
{
const K& operator()(const K& Key) {
return Key;
}
};
typedef typename RBTree<K, K, SetKeyofT>::const_iterator iterator;//set的普通迭代器也底层封装了const迭代器
typedef typename RBTree<K, K, SetKeyofT>::const_iterator const_iterator;//set的K不能修改
iterator begin() const{//
return _t.begin();
}
iterator end() const{
return _t.end();
}
pair<iterator, bool> insert(const K& Key) {
return _t.insert(Key);
}
void inorder() {
return _t.Inorder();
}
private:
RBTree<K, K, SetKeyofT> _t;
};
注:迭代器的声明中加上typename是为了告诉编译器这里的参数是类型
这里用红黑树的const迭代器去封装set的迭代器就又会出现新的问题,因为set的iterator是由红黑树的const_iterator封装的,set调用红黑树的insret,这样就会导致红黑树insert返回的是iterator,而set的insert返回的是const_iterator,导致不一致。
为了解决这样的问题,我们改造红黑树的insert的返回值为pair<Node* ,bool>
,这样就可以让红黑树返回的 pair<Node* ,bool> 去再进行构造set的insert的返回值 pair<iterator, bool> 。
可以这样构造的原因是pair的构造是由模板写的,支持用不同的数据类型去构造:
template<class T1,class T2>
struct pair{
template<class U,class V>//这里的构造用了模板,类型相同时是拷贝构造,不同时是构造
pair(const pair<U,V> &pr)
:first(pr.first)
,second(pr.second)
{}
T1 first;
T2 second;
};
三,封装map
map的封装和set一样,也要在内部实现一个仿函数来获取类型。
但是和set不同的是,map的Key可以修改
template<class K,class V>
class Mymap {
public:
struct MapKeyofT {
const K& operator()(const pair<K, V>& kv) {
return kv.first;
}
};
typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::const_iterator const_iterator;
iterator begin() {
return _t.begin();
}
iterator end() {
return _t.end();
}
pair <iterator, bool> insert(const pair<K, V>& kv) {
return _t.insert(kv);
}
//...
private:
RBTree<K, pair<const K, V>, MapKeyofT> _t;//这里的const的pair
};
在map的用法介绍中我们知道map可以重载[],传入的是Key,返回的是Value。为了支持这个功能,insert的返回值是一个 pair<Node*, bool>
,当map插入的Key存在时,insert返回插入后的迭代器位置,否则返回之前的位置。
V& operator[](const K& Key) {
pair<iterator, bool> ret = insert(make_pair(Key,V()));
return ret.first->second;
}
四,总结
我们讲解完了map和set的主要的一些接口,如果大家想看完整代码,可以查看我的gitee仓库:链接: map和set的封装
下一节我们讲解哈希结构,在此之前也希望大家可以好好理解红黑树的原理,这一部分也是C++上难度的部分,经过了这一部分,后面的C++内容就会想对容易一些。