map和set封装
创作中心-CSDN
https://mpbeta.csdn.net/mp_blog/creation/editor/147238663
目录
创作中心-CSDNhttps://mpbeta.csdn.net/mp_blog/creation/editor/147238663
一、封装原理
二、改造红黑树
三、实现迭代器
四、测试
五、小tip
一、封装原理
上一篇文章我们完成了红黑树的模拟实现,现在我们要基于这个红黑树进行改造完成map和set的封装
这个是stl里面的红黑树原码
这里面是map的原码
这里面是set的原码
第一个模板参数通过观察我们可以发现是key,第二个模板参数map里面是pair结构,set里面是key
第二个模板参数决定了node中存什么
这样我们就不用实现两棵树了,我们通过第二个模板参数传什么来达到传到底是传key变成set还是派人变成map
二、改造红黑树
2.1初步改造
以下是模仿原码的红黑树的进行初步改造,具体是key还是value由传值传参决定
enum Color
{Red,Black
};
template<class T>
struct RBTreenode
{RBTreenode<T>* _left;RBTreenode<T>* _right;RBTreenode<T>* _parent;/*pair<K, V> _kv;*/Color _col;T _data;RBTreenode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(Red){}
};
template<class K,class T>
class RBtree
{typedef RBTreenode<T> Node;
}
#pragma once
#include "RBTree.h"
namespace stn {template<class K>class set{private:RBtree<K, K> _t;};
}
#pragma once
#include "RBTree.h"
namespace stn {template<class K,class V>class map{private:RBtree<K, pair<K,V>> _t;};
}
2.2改造插入
while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}
我们上一篇写的文章支持比较大小在这里就不管用了,如果他是set就支持比较,如果是pair这里就满足不了我们的需求
pair的比较规则是有一个小就小
while (cur){if (cur->_data < data){parent = cur;cur = cur->_right;}else if (cur->_data > data){parent = cur;cur = cur->_left;}else{return false;}}
所以我们这里要写一个仿函数来支持它
class set{public:struct SetKeyofT{const K& operator()(const K& key) {return key;}};private:RBtree<K, K, SetKeyofT> _t;};
class map{public:struct MapKeyofT{const K& operator()(const pair<K,V>& kv) {return kv.first;}};private:RBtree<K, pair<K,V>,MapKeyofT> _t;};
template<class K,class T,class KeyofT>
class RBtree
{typedef RBTreenode<T> Node;
KeyofT kot;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 false;}}
cur = new Node(data);cur->_col = Red;if (kot(parent->_data) < kot(data)){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}
bool Insert(const K& key)
{return _t.Insert(key);
}
bool Insert(const pair<K,T>& kv){return _t.Insert(kv);}
这就是对插入的改造
三、实现迭代器
template<class T>
struct _TreeIterator
{typedef RBTreenode<T> Node;typedef _TreeIterator<T> iterator;Node* _node;_TreeIterator(Node* node):_node(node){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}};
3.1迭代器遍历
operator++我们应该如何遍历访问节点呢
比如it指向11,说明左子树已经访问完了,要访问右子树,下一个位置就应该是13
1.it指向的节点,右子树不为空,下一个就是右子树的最左节点
上面讨论的是右不为空的场景,接下来我们讨论右为空的场景比如说在1这个位置我们怎么访问到下一个位置
假设我们访问的是8这个位置8是7的右子树,根据中序来看,左根右,那么右子树完了父亲也完了,再看8这个位置属于2的右子树,接下来访问的就是11
假设我们访问的是5这个7的左子树位置的节点,此时访问完5了,但是7还没访问,那么下一个访问的就是根节点
2.it指向的节点,右子树为空,it中的节点所在的子树访问完了,往上找孩子是父亲左的那个祖先
就是比如说8这个节点,右子树一路访问完了,到2这个父亲左的节点,这个访问完了就应该访问它的父亲也就是8的祖先
iterator begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}
begin访问的是最左节点,end返回的是什么呢?我们来看15这个节点,15这个节点访问完了,向上返回,右树都访问完了,根据2规则,找孩子是父亲左的那个,但是我们访问到11的时候父亲为空,所以我们end应该是空节点
Self& operator++(){//右不为空,找右树的最左节点;if (_node->_right){Node* cur = _node->_right;while (cur->_left){cur = cur->_left;}_node = cur;}else //右为空,找孩子是父亲左的节点{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}
bool operator!=(const Self&s)
{return _node != s._node;
}
typedef typename RBtree<K, pair<K, V>, MapKeyofT>::iterator iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}
typedef typename RBtree<K, K, SetKeyofT>::iterator iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}
对类模板取内嵌类型,加typename告诉编译器这里是类型
3.2方括号补充
pair<iterator,bool> Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_col = Black;return make_pair(iterator(_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)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false);}}cur = new Node(data);Node* newnode = cur;cur->_col = Red;if (kot(parent->_data) < kot(data)){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}while (parent && parent->_col == Red){// g// p u// cNode* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;//叔叔存在且为红if (uncle && uncle->_col == Red){parent->_col = uncle->_col = Black;grandfather->_col = Red;cur = grandfather;parent = grandfather->_parent;}else{if (parent->_left == cur){//叔叔不存在或者叔叔存在且为黑RoRight(grandfather);parent->_col = Black;grandfather->_col = Red;} // g// p u// celse{//叔叔不存在或者叔叔存在且为黑RoLeft(parent);RoRight(grandfather);cur->_col = Black;grandfather->_col = Red;}break;}}// g// u p// celse{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// cif (parent->_right == cur){RoLeft(grandfather);grandfather->_col = Red;parent->_col = Black;}else{RoRight(parent);RoLeft(grandfather);grandfather->_col = Red;cur->_col = Black;}break;}}}_root->_col = Black;return make_pair(iterator(newnode), true);
}
如果我们要加入方括号就需要对插入进行改造
这里为什么用pair<iterator, bool> 呢,因为看我们下面这个代码我们要支持[]的话就必须针对返回类型进行改造,那我们用bool的话就拿不到这个迭代器指向的结点里面的value,如果key已存在,插入失败,返回对应位置的迭代器
最后一行插入newnode而不是cur的原因是cur可能会向上更新
这个是map的
pair<iterator, bool> Insert(const pair<K, V>& kv){return _t.Insert(kv);}V& operator[](const K& key){//如果没有代表新插入一个节点pair<iterator, bool> ret = Insert(make_pair(key,V()));return ret.first->second;}
这个是set的
pair<iterator, bool> Insert(const K& key)
{return _t.Insert(key);
}
3.3const迭代器
因为这里几乎全部都要改造我就把大部分代码贴出来了
template<class T,class Ref,class Ptr>
struct _TreeIterator
{typedef RBTreenode<T> Node;typedef _TreeIterator<T,Ref,Ptr> Self;Node* _node;_TreeIterator(Node* node):_node(node){}Self& operator++(){//右不为空,找右树的最左节点;if (_node->_right){Node* cur = _node->_right;while (cur->_left){cur = cur->_left;}_node = cur;}else //右为空,找孩子是父亲左的节点{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}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*> const_iterator;iterator begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}const_iterator begin()const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_iterator(cur);}const_iterator end()const{return const_iterator(nullptr);}pair<Node*,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(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(cur, false);}}cur = new Node(data);Node* newnode = cur;cur->_col = Red;if (kot(parent->_data) < kot(data)){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}while (parent && parent->_col == Red){// g// p u// cNode* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;//叔叔存在且为红if (uncle && uncle->_col == Red){parent->_col = uncle->_col = Black;grandfather->_col = Red;cur = grandfather;parent = grandfather->_parent;}else{if (parent->_left == cur){//叔叔不存在或者叔叔存在且为黑RoRight(grandfather);parent->_col = Black;grandfather->_col = Red;} // g// p u// celse{//叔叔不存在或者叔叔存在且为黑RoLeft(parent);RoRight(grandfather);cur->_col = Black;grandfather->_col = Red;}break;}}// g// u p// celse{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// cif (parent->_right == cur){RoLeft(grandfather);grandfather->_col = Red;parent->_col = Black;}else{RoRight(parent);RoLeft(grandfather);grandfather->_col = Red;cur->_col = Black;}break;}}}_root->_col = Black;return make_pair(newnode, true);
}
这里面我们不用iterator的原因是用了一种巧妙的写法,因为我们iterator转化成const_iterator是无法转化的,如果要转化我们还要写再实现成复杂一点,有人说这里不是权限的缩小 吗,这是不一样的,const iterator 修饰的是这个指针不能改变,const_iterator修饰的指针指向的内容不能改变,这里其实都是两个类。
这里我们改用node*是因为pair可以根据不同的类型来初始化,因为你这个迭代器的本质实际上是一个结点,pair类型相同就是拷贝构造,类型不相同就是构造。
3.3.1set的const迭代器
typedef typename RBtree<K, K, SetKeyofT>::const_iterator iterator;typedef typename RBtree<K, K, SetKeyofT>::const_iterator const_iterator;iterator begin()const{return _t.begin();}iterator end()const {return _t.end();}
因为set普通对象和const对象都不能被修改,所以普通对象和const对象我们都把它去调下面这个const版本。
typedef typename RBtree<K, pair<const K, V>, MapKeyofT>::iterator iterator;
typedef typename RBtree<K, pair<const K, V>, MapKeyofT>::const_iterator const_iterator;
至于map的迭代器,因为我们的value支持修改所以我们给它做普通迭代器和const迭代器,但是我们又期望key不被修改所以在类型那里加key
RBtree<K, pair<const K,V>,MapKeyofT> _t;
但是我们平常写插入的时候却没有写const
pair<iterator, bool> Insert(const pair<K, V>& kv)
{return _t.Insert(kv);
}
得益于它是一层一层往下传的
K,去初始化const K,就用到了这个pair的这个构造
四、测试
#include<map>
#include<set>
#include <string>
#include <iostream>
using namespace std;
#include "mymap.h"
#include "myset.h"
void test_set()
{stn::set<int> s;s.Insert(3);s.Insert(5);s.Insert(2);s.Insert(1);s.Insert(3);stn::set<int>::iterator it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;for (auto e : s){cout << e << " ";}
}
void test_map()
{stn::map<string, string> mp;mp.Insert(make_pair("sort", "排序"));mp.Insert(make_pair("left", "左边"));stn::map<string, string>::iterator it = mp.begin();while (it != mp.end()){cout << it->first << " "<<it->second<<" "<<endl;++it;}string arr[] = { "牛", "羊","猪", "狗", "牛", "牛", "狗", "羊", "猪", "猪", "狗", "猪", "牛" };stn::map<string, int> countMap;for (auto& e : arr){countMap[e]++;}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}cout << endl;
}
int main()
{test_set();test_map();return 0;
}
五、小tip
有人会应该一样跟我疑虑那第一个模板参数是拿来干嘛的
T 在map中我们是传了模板pair<const K,V>
我们keyofT是不要,但是如果我们实现一个find函数的话就需要传这个K的类型;
这个你在用的时候传一个类型它会去找自己匹配的,然后比如说你在外面用解引用的时候它就会根据你的类型比如说要数据就是const T&来进行匹配
封装这里的弯弯绕绕还是非常多的,也是十分考验功底的,希望大家能借助这篇文章把自己不懂的搞明白