C++修炼:map和set的封装
Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!
我的博客:<但凡.
我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C++修炼之路》
这期我们模拟实现一下基于红黑树的map和set。
目录
1、封装set和map,解决KeyOfT
2、iterator
3、key不支持修改的问题
4、operator[]
1、封装set和map,解决KeyOfT
首先我们先把set和map的大类实现一下:
map:
#pragma once
#include"RBTree.h"template<class K,class V>
class map
{
public:private:RBTree<K, pair<K, V>> _t;};
set:
#pragma once
#include"RBTree.h"template<class K>
class set
{
public:private:RBTree<K, K> _t;
};
对于传入红黑树的两个参数,第一个我们传入关键值key,第二个传入我们存储的实际类型,如果是key就再传入一次,如果是key_value就传入pair类型。
我们需要一个KeyOfT,将key_value里面的key值取出来,因为我们不知道存储的值到底是key还是key_value类型,所以对于map和set我们都需要一个对应的KeyOfT。
map:
struct MapKeyOfT
{const K& operator()(const pair<K, V>& kv){return kv.first;}
};
set:
struct SetKeyOfT
{const K& operator()(const K& key){return key;}
};
这样的话,我们在传入红黑树时需要往模板中传入三个参数,所以我们对红黑树进行改造:
template<class K,class T,class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;public:pair<Iterator,bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//根节点必须是黑return { Iterator(_root,_root),true };}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){//这访函数返回T值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;//新增的必须是redif (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED)//如果满足这个情况要一直向上走{Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED)//仅调色{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent=cur->_parent;}else{//u不存在或u存在且为黑//单边高if (cur == parent->_left){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//对应的是AVL树左边右边高的情况else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){uncle->_col = BLACK;parent->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return { Iterator(newnode,_root),false };}......Iterator Find(const K& key){KeyOfT kot;Node* cur = _root;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return Iterator(cur,_root);}}return End();}private:......
private:Node* _root = nullptr;
};
为了节省篇幅我把所有不涉及改动的函数都先去掉了。另外insert里面涉及的迭代器我们之后再说。
2、iterator
接着我们实现map和set的迭代器。首先我们在红黑树所在的头文件中把迭代器定义出来:
template<class T,class ptr,class ref>
class RBTreeIterator
{
public:typedef RBTreeNode<T> Node;typedef RBTreeIterator<T,ptr,ref> Self;RBTreeIterator(Node* node, Node* root):_node(node),_root(root){}ref operator*() {return _node->_data;}ptr operator->(){return &_node->_data;}bool operator==(const Self& s) const{return _node == s._node;}bool operator!=(const Self& s) const{return _node != s._node;}Self& operator++(){if (_node->_right){//节点右不为空,下一个节点就是右子树中序第一个(最左节点Node* minleft = _node->_right;while (minleft->_left){minleft = minleft->_left;}_node = minleft;}else{//节点右为空,找找祖先,并且该祖先是其父亲的左Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right)//parent是空也结束{cur = parent;parent = parent->_parent;}//如果parent为nullptr,nullptr为end()_node = parent;}return *this;}Self& operator--(){if (_node == nullptr) // end(){// --end(),特殊处理,⾛到中序最后⼀个结点,整棵树的最右结点 Node* rightMost = _root;while (rightMost && rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else if (_node->_left){// 左⼦树不为空,中序左⼦树最后⼀个 Node* rightMost = _node->_left;while (rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else{// 孩⼦是⽗亲右的那个祖先 Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}private:Node* _node;Node* _root;
};
对于迭代器这个类,++,--两个操作需要特殊说明一下。
对于++操作,首先,因为我们map和set++是有序数的下一个,对应的就是红黑树的中序遍历的下一个节点。那么我们不可能因为一个++就遍历整个红黑树去找下一个节点吧?所以我们分情况讨论一下,看看红黑树的下一个节点理应出现在哪。如果当前节点右子树不为空,下一个节点就应该是右子树的最左节点。如果当前节点的右子树为空,下一个节点就应该是父节点,并且该父节点一定是他的父节点(父节点的父节点)的左子树。如果一直查找直到nullptr,就说明++之后应该是end,那就返回nullptr就可以了。
对于--操作,首先我们要对当前节点是end()的情况做特殊处理,这个节点的上一个节点应该是整棵树的最右节点。第二种情况就是左子树不为空,如果左子树不为空,说明他的上一个节点为左子树的最右节点。第三种情况就是左子树为空,此时他的上一个节点应该是父节点,并且此父节点一定是他的父节点(父节点的父节点)的右子节点。
接下来我们对红黑树进行改造,新增迭代器的部分:
typedef RBTreeIterator<T,T*,T&> Iterator;
typedef RBTreeIterator<T,const T*,const T&> ConstIterator;
Iterator Begin()
{Node* minleft = _root;while (minleft && minleft->_left)//这棵树可能是空的{minleft = minleft->_left;}return Iterator(minleft,_root);
}
Iterator End()
{return Iterator(nullptr,_root);
}
ConstIterator Begin() const
{Node* minleft = _root;while (minleft && minleft->_left)//这棵树可能是空的{minleft = minleft->_left;}return ConstIterator(minleft, _root);
}
ConstIterator End() const
{return ConstIterator(nullptr, _root);
}
接下来我们对insert进行改造,使他的返回值和源码一样:
pair<Iterator,bool> Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//根节点必须是黑return { Iterator(_root,_root),true };}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){//这访函数返回T值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;//新增的必须是redif (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED)//如果满足这个情况要一直向上走{Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED)//仅调色{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;//继续向上调整parent=cur->_parent;}else{//单边高if (cur == parent->_left){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){uncle->_col = BLACK;parent->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return { Iterator(newnode,_root),false };
}
接下来我们再去map和set的头文件中,对迭代器进行进一步的封装:
map:
typedef typename RBTree < K, pair<K, V>, MapKeyOfT>::Iterator iterator;
typedef typename RBTree < K, pair<K, V>, MapKeyOfT>::ConstIterator const_iterator;
pair<iterator,bool> insert(const pair<K, V>& kv)
{return _t.Insert(kv);
}
iterator begin()
{return _t.Begin();
}
iterator end()
{return _t.End();
}
const_iterator begin() const
{return _t.Begin();
}
const_iterator end() const
{return _t.End();
}
iterator find(const K& key)
{return _t.Find(key);
}
set:
typedef typename RBTree < K, K, SetKeyOfT>::Iterator iterator;
typedef typename RBTree < K, K, SetKeyOfT>::ConstIterator const_iterator;
pair<iterator, bool> insert(const K& key)
{ return _t.Insert(key);
}
iterator begin()
{return _t.Begin();
}
iterator end()
{return _t.End();
}
const_iterator begin() const
{return _t.Begin();
}
const_iterator end() const
{return _t.End();
}
iterator find(const K& key)
{return _t.Find(key);
}
注意这里我们typedef的迭代器一定要是在红黑树的大类中我们已经封装好的迭代器,不然没有对应的Begin,End的操作。并且我们要用typename标记,不然编译器不知道iterator到底是一个类还是一个变量。
3、key不支持修改的问题
这个问题就很好解决了,对于set,我们只需要在第二个参数加上const 就行了:
RBTree<K,const K, SetKeyOfT> _t;
对于map,我们在第二个参数pair中的key值之前加上const:
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
4、operator[]
这里的[]本质还是对insert的复用,因为[]本身也可以插入,如果插入成功,就返回新插入的值对应的value值,如果插入失败,就返回原有的该值的value值(具体可以看map和set的使用这一篇)。那这个操作完全可以通过复用insert来实现:
map:
V& operator[](const K& key)
{pair<iterator, bool> t = insert({ key ,V()});//int的缺省值是0auto it = t.first;return it->second;//这里会省略一个->
}
set:
set不支持operator[]。
好了,到这里我们就基本实现完了map和set,现在解决大部分读者在学习这里的时候会遇到的一个疑问。为啥模板类中要传入一个K再传入一个T呢?假设在set中,我们存储的值类型为int,那么传两个int进来不是妥妥的重复吗?
首先,我们模拟实现要和源码尽可能保持一致,源码是这么写的,所以我们也这么写。那么源码为什么要这么写呢?
确实,对于set来说就是重复传了。但是对于map来说,第一个K的作用是指示key值的类型。因为find和erase操作的函数参数都是K类型的。也就是说我们得用一个类型来指示我们形参是什么类型的。假设map中我们只传一个pair进来,我们根本无法确定key值的类型是什么。
并且,本着能用一套逻辑就用一套逻辑的原则,通过传一个key的类型来解决这个问题,总比写两套逻辑,一个是key类型红黑树,一个是key_value类型红黑树要方便得多。
完整代码放在我的gitee了:
RBTree · 主线学习简单项目 - 码云 - 开源中国
好了,今天的内容就分享到这,我们下期再见!