当前位置: 首页 > news >正文

【C++】用红黑树封装set和map

在C++标准库中,set容器和map容器的底层都是红黑树,它们的各种接口都是基于红黑树来实现的,我们在这篇文章中已经模拟实现了红黑树 ->【C++】红黑树,接下来我们在此红黑树的基础上来看看如何封装set和map。

一、共用一颗红黑树

我们在这篇文章中 ->【C++】set容器和map容器的基本使用,清楚set是key搜索场景的结构,map是key/value搜索场景的结构。所以要用红黑树封装它们两个就需要2棵红黑树,这两颗红黑树整体逻辑差不多一样,只是结点中的元素数据类型不一样,一个是K类型,一个是pair<K,V>类型。两棵极为相似的树,只是一点不同,如果实现两个会显得冗余,那么我们就可以考虑使用模板来将结点中的数据类型泛型化,使set和map底层共用一个红黑树。

我在先前的文章中已经实现了红黑树,它是解决key/value搜索场景的,我将那篇文章的源代码裁剪了一些重要的部分,大家这里只需看懂这部分的功能:

template<class K, class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;	Colour _col;RBTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr){}
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;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;elsereturn false;}cur = new Node(kv);cur->_col = RED; if (parent->_kv.first < kv.first)parent->_right = cur;elseparent->_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){RotateR(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){parent->_col = uncle->_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 true;}
private:Node* _root = nullptr; 
};

此时,结点中数据类型是pair,这样我们就把这个结点的数据类型写"死"了,set容器底层就不能用这个红黑树,我们要实现set和map共用一棵红黑树,所以就需要把结点的数据类型泛型化(根据传来的类型来确定具体的类型):

template<class T>  //如果是set就传K,如果是map就传pair<K,V>
struct RBTreeNode
{T _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;	Colour _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr){}
};

那么,RBTree也要跟着改:

template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:bool Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_data.first < data.first)parent = cur,cur = cur->_right;else if (cur->_data.first > data.first)parent = cur,cur = cur->_left;elsereturn false;}cur = new Node(data);cur->_col = RED; if (parent->_data.first < data.first)parent->_right = cur;elseparent->_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){RotateR(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){parent->_col = uncle->_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 true;}
private:Node* _root = nullptr; 
};

封装set:

//MySet.h
#include "RBTree.h"
namespace blue
{template<class K>class set{public:bool insert(const K& key){return _t.Insert(key);}private:RBTree<K> _t;};
}

封装map:

//MyMap.h
#include "RBTree.h"
namespace blue
{template<class K,class V>class map{public:bool insert(const pair<K, V>& kv){return _t.Insert(kv);}private:RBTree<pair<K, V>> _t;};
}

这样我们就可以通过模板参数T来控制set和map共用一棵红黑树了!

二、 红黑树中Insert带来的问题

通过观察上面红黑中Insert中的代码,其实会有问题:

我们的data可能是K类型,也可能是pair<K,V>类型。如果是后者,那么这样比较当然没问题,那如果是前者,就不能这样比较,所以我们要对这个部分进行修改。

怎么修改呢?去掉.first吗?

我们如果去掉.first,那么不管是K还是pair<K,V>都可以进行比较,以pair<K,V>类型为例,两个pair类型进行比较时先比较K,如果K相同则比较V,这与我们的本意相违背,我们的要求是只比较K的值,不能让V的值参与比较!所以我们要解决问题,不能仅仅是去掉.first。

我们可以在set和map类中写一个仿函数,通过仿函数来获取K,具体做法如下:

#include "RBTree.h"
namespace blue
{template<class K>class set{struct KeyOfSet{const K& operator()(const K& key){return key;}};public:bool insert(const K& key){return _t.Insert(key);}private:RBTree<K,KeyOfSet> _t;};
}#include "RBTree.h"
namespace blue
{template<class K,class V>class map{struct KeyOfMap{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:bool insert(const pair<K, V>& kv){return _t.Insert(kv);}private:RBTree<pair<K, V>,KeyOfMap> _t;};
}

 这时我们就需要在红黑树中加一个模板参数:

我们明确知道map是需要这个仿函数来获取K值的,但是在set中也写一个仿函数来获取K值,有人开始会觉得这是多余的,因为set中的元素类型本身就是K类型,但是不要忽略一点,set和map是共用同一份模板的,map需要这个仿函数,那么set也必须有这个仿函数,set这里的仿函数就是为了兼容map,这时,红黑树的第二个模板参数KeyOfT就出现了。

三、erase和find带的来问题

我们知道set容器和map容器都有erase和find接口,这两个接口都是根据K值来进行的,如果我们要在红黑树中实现这两个接口那么必须知道K的值,我们根据K的值来进行删除和查找。但是现在,我们自己实现的这个红黑树中并没有办法拿到K的值,也就是说在我们目前这棵红黑树中若要实现erase和find是做不到的!

为了获取K的值,我们可以在RBTree中再添加一个模板参数K,专门用来获取K的值:

在set中:

在map中:

其实,这里的set中传了两个K,第一个K就是兼容map的。因为map需要这个K,那么在TRBTree中就必须增加一个模板参数,所以set就必须传一个K。

那么在RBTree中erase和find接口就可以这样写:

bool find(const K& key)
{//...
}bool erase(const K& key)
{//...
}

 四、迭代器

红黑树的迭代器和我们在模拟实现list时思路是一样的,因为红黑树的底层空间是不连续的,所以我们不能直接"typedef Node* iterator",我们要单独用一个类型封装结点的指针,再通过重载运算
符实现,让迭代器具有像指针一样访问的行为:

//Ref和Ptr是为了兼容普通迭代器和const迭代器
template<class T,class Ref,class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node; //当前结点指针RBTreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &(_node->_data);}bool operator==(const Self& s){return _node == s._node;}bool operator!=(const Self& s){return _node != s._node;}
};

因为set和map都是双向迭代器,所以它们的迭代器应该要支持++和--操作,由于它们的迭代器是复用红黑树的,所以红黑树中的迭代器也要实现++和--操作,list迭代器的++或--操作实现起来非常简单,只需利用next指针就好;反观红黑树,它的++/--操作要求必须满足中序遍历规则,红黑树的中序遍历是有序的,假设某棵红黑树的中序遍历是"1   3    5    6    7   9" ,如果迭代器在3这个位置,那么++后就必须到5,--后就必须到1。迭代器++的核心逻辑就是不看全局,只看局部,只考虑当前中序局部要访问的下⼀个结点。

那么如何实现++操作呢?(分为以下几种情况)

假设当前迭代器"指向"cur位置,cur的父结点为parent。

1、cur的右子树存在

++后,只需找到右子树的最左结点即可。

2、cur的右子树为空

(1)cur是parent的左孩子

直接返回parent。(此时parent就是我们需要找的结点)

(2)cur是parent的右孩子

说明以parent为根的子树已经遍历完了,接下来让cur=parent,parent=cur->_parent,然后在做判断。最坏情况下,cur = _root,parent=nullptr,这时直接返回parent即可,我们这里让结束位置为nullptr。

代码实现:

Self& operator++(){Node* cur = _node;Node* parent = cur->_parent;if (cur->_right)  //右子树不为空,找右子树的最左结点{cur = cur->_right;while (cur->_left)cur = cur->_left;_node = cur;}else{while (parent && parent->_right == cur){cur = parent;parent = cur->_parent;}//直到cur是parent的左,那么parent就是我们的目标   //极端情况下,parent走到nullptr,这说明整棵树已经访问完了,直接返回nullptr(parent)_node = parent;}return  *this;}

那么如何实现--操作呢?(分为以下几种情况) 

迭代器--的实现跟++的思路完全类似,逻辑正好反过来即可,因为它访问顺序是右子树->根结点->
左子树。

假设当前迭代器"指向"cur位置,cur的父结点为parent。

1、cur的左子树存在

--后,只需找到左子树的最右结点即可。

2、cur的左子树为空

(1)cur是parent的右孩子

直接返回parent。(此时parent就是我们需要找的结点)

(2)cur是parent的左孩子

说明以parent为根的子树已经遍历完了,接下来让cur=parent,parent=cur->_parent,然后在做判断。

代码实现:

Self& operator--(){Node* cur = _node;Node* parent = cur->_parent;if (cur->_left) //左子树不为空,找左子树的最右结点{cur = cur->_left;while (cur->_right)cur = cur->_right;_node = cur;}else{while (parent && parent->_left == cur){cur = parent;parent = cur->_parent;}//直到cur是parent的右,那么parent就是我们的目标   _node = parent;}return *this;}

完成这一步,我们迭代器的封装任务差不多已经完成了,接下来我们需要在红黑树中实现Begin和End:

public:typedef RBTreeIterator<T, T&, T*> Iterator;  //普通迭代器typedef RBTreeIterator<T, const T&,const T*> ConstIterator; //const迭代器Iterator Begin(){Node* cur = _root;while (cur->_left)cur = cur->_left;return Iterator(cur);}Iterator End(){return Iterator(nullptr);  //我们这里让nullptr当最终结点}ConstIterator Begin() const{Node* cur = _root;while (cur->_left)cur = cur->_left;return ConstIterator(cur);}ConstIterator End() const{return ConstIterator(nullptr);}

在set中:

public:typedef typename RBTree<K, K, KeyOfSet>::Iterator iterator;typedef typename RBTree<K, K, KeyOfSet>::ConstIterator const_iterator;iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}

在map中:

public:typedef typename RBTree<K, pair<K, V>, KeyOfMap>::Iterator iterator;typedef typename RBTree<K, pair<K, V>, KeyOfMap>::ConstIterator const_iterator;iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}

 我们大致的任务已经完成了,接下来我们来写一段代码验证一下(看看是否会出现错误):

//Test.cpp
#include "MyMap.h"
#include "MySet.h"void TestSet()
{blue::set<int> s;s.insert(5);s.insert(1);s.insert(3);s.insert(2);s.insert(6);blue::set<int>::iterator sit = s.begin();while (sit != s.end()){cout << *sit << " ";++sit;}cout << endl;
}void TestMap()
{blue::map<string, string> dict;dict.insert({ "sort", "排序" });dict.insert({ "left", "左边" });dict.insert({ "right", "右边" });blue::map<string, string>::iterator it = dict.begin();while (it != dict.end()){cout << it->first << ":" << it->second << endl;++it;}cout << endl;
}int main()
{TestSet();cout << "-----------------------" << endl;TestMap();return 0;
}

运行结果:

运行结果暂时没有什么问题。

这里有一个bug,看下面测试代码:

void Print(const blue::set<int>& s)
{bit::set<int>::const_iterator it = s.end();while (it != s.begin()){--it;cout << *it << " ";}cout << endl;
}

我们上面将end()指向nullptr,那么如果逆向遍历,--it就会出现错误,所以我们在--it时先判断一下,如果指向空,那么就修改_node为红黑树的最后一个结点,那么,问题又来了,怎么获得最后一个结点呢?所以我们需要知道红黑树的头结点,所以,我们在RBTreeIterator这个类中还需要一个成员变量来记录根节点:

修改operator--如下:

Self& operator--() {if (_node == nullptr){Node* cur = _root;while (cur->_right)cur = cur->_right;_node = cur;return *this;}Node* cur = _node;Node* parent = cur->_parent;if (cur->_left) //左子树不为空,找左子树的最右结点{cur = cur->_left;while (cur->_right)cur = cur->_right;_node = cur;}else{while (parent && parent->_left == cur){cur = parent;parent = cur->_parent;}//直到cur是parent的右,那么parent就是我们的目标   _node = parent;}return *this;}

解决完上述问题,还有一个问题,set和map中的K的值是不允许修改的,因为若修改就可能会破坏红黑树的结构,但我们写的代码中通过普通迭代器是能够修改的,如果要解决可以这样修改:

在set中:

在map中:

五、[]

好了,好了,到这里我们将上面的坑都踩完了,接下来最后最后一个功能,也是比较重要的,就是处理map中[]的实现逻辑:

map中的[]的逻辑其实就是利用了insert接口。

我们在红黑树中实现Insert的返回值是bool,接下来我们将它改为pair<Iterator,bool>类型:

pair<Iterator,bool> Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//return true;return { Iterator(_root, _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 false;return { Iterator(cur, _root),false };}cur = new Node(data);Node* newnode = cur;cur->_col = RED; if (kot(parent->_data) < kot(data))parent->_right = cur;elseparent->_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){RotateR(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){parent->_col = uncle->_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 true;return { Iterator(newnode, _root),true };
}

完成对Insert的调整后,在set和map的insert也要跟着调:

在set中:

在map中:

接下来,我们就着手实现[],在map中:

V& operator[](const K& key)
{pair<iterator, bool> ret = _t.Insert({ key,V() });return (ret.first)->second;
}

是不是非常简单! 

六、其他问题

1、构造函数

RBTree() = default;

2、拷贝构造

RBTree(const RBTree<K, T, KeyOfT>& t)
{_root = Copy(t._root);
}Node* Copy(Node* root)
{if (root == nullptr)return nullptr;Node* newnode = new Node(root->_data);newnode->_left = Copy(root->_left);newnode->_right = Copy(root->_right);return newnode;
}

3、赋值重载

RBTree& operator=(RBTree tmp)
{swap(tmp._root);return *this;
}

4、析构函数

~RBTree()
{Destroy(_root);_root = nullptr;
}void Destroy(Node* root)
{if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;
}

七、源码 

(1)RBTree.h

#pragma once
#include <iostream>
using namespace std;enum Colour
{RED,BLACK
};template<class T>
struct RBTreeNode
{T _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;	Colour _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr){}
};//Ref和Ptr是为了兼容普通迭代器和const迭代器
template<class T,class Ref,class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node; //当前结点指针Node* _root; //记录红黑树的根结点RBTreeIterator(Node* node,Node* root):_node(node),_root(root){}Self& operator++() {Node* cur = _node;Node* parent = cur->_parent;if (cur->_right)   //右子树不为空,找右子树的最左结点{cur = cur->_right;while (cur->_left)cur = cur->_left;_node = cur;}else{while (parent && parent->_right == cur){cur = parent;parent = cur->_parent;}//直到cur是parent的左,那么parent就是我们的目标   //极端情况下,parent走到nullptr,这说明整棵树已经访问完了,直接返回nullptr(parent) _node = parent;}return  *this;}Self& operator--() {if (_node == nullptr){Node* cur = _root;while (cur->_right)cur = cur->_right;_node = cur;return *this;}Node* cur = _node;Node* parent = cur->_parent;if (cur->_left) //左子树不为空,找左子树的最右结点{cur = cur->_left;while (cur->_right)cur = cur->_right;_node = cur;}else{while (parent && parent->_left == cur){cur = parent;parent = cur->_parent;}//直到cur是parent的右,那么parent就是我们的目标   _node = parent;}return *this;}Ref operator*() const{return _node->_data;}Ptr operator->() const{return &(_node->_data);}bool operator==(const Self& s) const{return _node == s._node;}bool operator!=(const Self& s) const{return _node != s._node;}
};template<class K,class T,class KeyOfT>
class RBTree
{
public:RBTree() = default;RBTree(const RBTree<K, T, KeyOfT>& t){_root = Copy(t._root);}RBTree& operator=(RBTree tmp){swap(tmp._root);return *this;}~RBTree(){Destroy(_root);_root = nullptr;}public:typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, T&, T*> Iterator;typedef RBTreeIterator<T, const T&,const T*> ConstIterator;Iterator Begin(){Node* cur = _root;while (cur->_left)cur = cur->_left;return Iterator(cur,_root);}Iterator End(){return Iterator(nullptr,_root);}ConstIterator Begin() const{Node* cur = _root;while (cur->_left)cur = cur->_left;return ConstIterator(cur,_root);}ConstIterator End() const{return ConstIterator(nullptr,_root);}public:pair<Iterator,bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//return true;return { Iterator(_root,_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 false;return { Iterator(cur,_root),false };}cur = new Node(data);Node* newnode = cur;cur->_col = RED; if (kot(parent->_data) < kot(data))parent->_right = cur;elseparent->_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){RotateR(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){parent->_col = uncle->_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 true;return { Iterator(newnode,_root),true };}private:void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* pParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (pParent->_left == parent)pParent->_left = subL;elsepParent->_right = subL;subL->_parent = pParent;}}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left)parentParent->_left = subR;elseparentParent->_right = subR;subR->_parent = parentParent;}}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newnode = new Node(root->_data);newnode->_left = Copy(root->_left);newnode->_right = Copy(root->_right);return newnode;}void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}
private:Node* _root = nullptr; 
};

(2)MySet.h 

#pragma once
#include "RBTree.h"
namespace blue
{template<class K>class set{struct KeyOfSet{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, const K, KeyOfSet>::Iterator iterator;typedef typename RBTree<K, const K, KeyOfSet>::ConstIterator const_iterator;iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}public:pair<iterator,bool> insert(const K& key){return _t.Insert(key);}private:RBTree<K, const K, KeyOfSet> _t;};
}

(3)MyMap.h 

#pragma once
#include "RBTree.h"
namespace blue
{template<class K,class V>class map{struct KeyOfMap{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<const K, V>, KeyOfMap>::Iterator iterator;typedef typename RBTree<K, pair<const K, V>, KeyOfMap>::ConstIterator const_iterator;iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}public:pair<iterator,bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert({ key,V() });return (ret.first)->second;}private:RBTree<K, pair<const K, V>, KeyOfMap> _t;};
}

(4)Test.cpp 

#define _CRT_SECURE_NO_WARNINGS 1#include "MyMap.h"
#include "MySet.h"void Print(const blue::set<int>& s)
{blue::set<int>::const_iterator it = s.end();while (it != s.begin()){--it;cout << *it << " ";}cout << endl;
}void TestSet()
{blue::set<int> s;s.insert(5);s.insert(1);s.insert(3);s.insert(2);s.insert(6);blue::set<int>::iterator sit = s.begin();//*sit += 10;while (sit != s.end()){cout << *sit << " ";++sit;}cout << endl;Print(s);}void TestMap()
{blue::map<string, string> dict;dict.insert({ "sort", "排序" });dict.insert({ "left", "左边" });dict.insert({ "right", "右边" });dict["left"] = "左边,剩余";dict["insert"] = "插入";dict["string"];blue::map<string, string>::iterator it = dict.begin();while (it != dict.end()){// 不能修改first,可以修改second//it->first += 'x';it->second += 'x';cout << it->first << ":" << it->second << endl;++it;}cout << endl;
}int main()
{TestSet();cout << "-----------------------" << endl;TestMap();return 0;
}

八、总结

本篇内容到这里就结束啦,主要讲了如何用红黑树来封装set和map容器,希望对大家有帮助,祝生活愉快!

相关文章:

【C++】用红黑树封装set和map

在C标准库中&#xff0c;set容器和map容器的底层都是红黑树&#xff0c;它们的各种接口都是基于红黑树来实现的&#xff0c;我们在这篇文章中已经模拟实现了红黑树 ->【C】红黑树&#xff0c;接下来我们在此红黑树的基础上来看看如何封装set和map。 一、共用一颗红黑树 我…...

【大数据测试HDFS + Flask详细教程与实例】

大数据测试HDFS Flask 1. 环境准备安装工具安装Hadoop&#xff08;以单机模式为例&#xff09;安装Flask和HDFS Python客户端 2. HDFS Flask基本架构基本文件结构 3. 创建Flask应用与与HDFS交互步骤1&#xff1a;配置HDFS连接步骤2&#xff1a;构建Flask应用 4. 创建前端界面…...

高级java每日一道面试题-2024年10月31日-RabbitMQ篇-RabbitMQ中vhost的作用是什么?

如果有遗漏,评论区告诉我进行补充 面试官: RabbitMQ中vhost的作用是什么? 我回答: 在Java高级面试中&#xff0c;关于RabbitMQ中vhost&#xff08;虚拟主机&#xff09;的作用是一个重要且常见的考点。以下是对vhost的详细解释&#xff1a; 一、vhost的基本概念 vhost&am…...

【日常记录-Java】代码配置Logback

1. 简介 在Logback中&#xff0c;推荐使用配置文件&#xff08;如logback.xml或logback-spring.xml&#xff09;来设置日志记录的行为。但在实际应用中&#xff0c;会有动态配置logback的需求。此时可通过编程的方式直接操作LoggerContext以及相关的Logger、Appender、Encoder等…...

HTTP常见的请求头有哪些?都有什么作用?在 Web 应用中使用这些请求头?

HTTP 请求头&#xff08;Request Headers&#xff09;用于在 HTTP 请求中携带额外的信息&#xff0c;帮助服务器更好地处理请求。以下是一些常见的 HTTP 请求头及其作用&#xff1a; 常见请求头及其作用 1. Accept 作用&#xff1a;告知服务器客户端可以接受的内容类型。示例…...

电信数据清洗案例:利用MapReduce实现高效数据预处理

电信数据清洗案例&#xff1a;利用MapReduce实现高效数据预处理 在大数据时代&#xff0c;电信行业积累了大量的用户通话、短信、上网等行为数据。在数据分析和机器学习模型训练前&#xff0c;对这些数据进行清洗是至关重要的一步。MapReduce 是一种高效的数据处理模型&#x…...

react 中 FC 模块作用

React.FC 是一个泛型类型&#xff0c;用于定义函数组件的类型 一、类型定义和代码可读性 1. 明确组件类型 使用React.FC定义一个组件时&#xff0c;使得组件的输入&#xff08;props&#xff09;和输出&#xff08;返回的 React 元素&#xff09;都有明确的类型定义。 impo…...

多模态大模型(1)--CLIP

CLIP&#xff08;Contrastive Language-Image Pre-training&#xff09;模型是一种多模态预训练神经网络&#xff0c;由OpenAI在2021年发布。它通过对比学习的方式&#xff0c;将图像和文本映射到同一个向量空间中&#xff0c;从而实现跨模态的检索和分类。下面介绍其基础功能&…...

opencv入门学习总结

opencv学习总结 不多bb&#xff0c;直接上代码&#xff01;&#xff01;&#xff01; 案例一&#xff1a; import cv2 # 返回当前安装的 OpenCV 库的版本信息 并且是字符串格式 print(cv2.getVersionString()) """ 作用&#xff1a;它可以读取不同格式的图像文…...

C/C++内存管理 | new的机制 | 重载自己的operator new

一、C/C内存分布 1. 内存分区 栈又叫堆栈–非静态局部变量/函数参数/返回值等等&#xff0c;栈是向下增长的。内存映射段是高效的I/O映射方式&#xff0c;用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存&#xff0c;做进程间通信 .堆用于程序运行时动态内…...

知识库管理系统:企业数字化转型的加速器

在数字化转型的大潮中&#xff0c;知识库管理系统&#xff08;KBMS&#xff09;已成为企业提升效率和创新能力的关键工具。本文将探讨知识库管理系统的定义、企业建立知识库的必要性&#xff0c;以及如何快速搭建企业知识库。 知识库管理系统是什么&#xff1f; 知识库管理系统…...

uniapp 如何使用vuex store (亲测)

首先是安装&#xff1a; npm install vuexnext --save 安装之后&#xff0c;Vue2 这样写 不管在哪里&#xff0c;建立一个JS文件&#xff0c;假设命名&#xff1a;store.js 代码这样写&#xff1a; import Vue from vue; import Vuex from vuex;Vue.use(Vuex);const store…...

[编译报错]ImportError: No module named _sqlite3解决办法

1. 问题描述&#xff1a; 在使用python进行代码编译时&#xff0c;提示下面报错&#xff1a; "/home/bspuser/BaseTools/Source/Python/Workspace/WorkspaceDatabase.py", line 18, in <module>import sqlite3File "/usr/local/lib/python2.7/sqlite3/_…...

【旷视科技-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…...

python学习记录16

字符串总结 python程序使用unicode编码&#xff0c;中文字符与英文字符都占一个字符&#xff0c;但英文字符只占一个字节&#xff0c;中文字符若按照utf-8格式编码占3个字节。 &#xff08;1&#xff09;字符串常用方法 1&#xff09;大小写转化 string.upper()#将所有字母…...

AI 大模型在软件开发中的角色

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/402a907e12694df5a34f8f266385f3d2.png#pic_center> &#x1f393;作者简介&#xff1a;全栈领域优质创作者 &#x1f310;个人主页&#xff1a;百锦再新空间代码工作室 &#x1f4de;工作室&#xff1a;新空间代…...

React中类组件和函数组件的理解和区别

react代码模块分为类组件和函数组件。 从语法和定义、内部状态管理、生命周期、性能、可读性和维护性、上下文、集成状态管理库等角度对比React中类组件和函数组件。 1、语法和定义 类组件&#xff1a; 使用 ES6 的类&#xff08;class&#xff09;语法定义的 React 组件。…...

Day62||prim算法精讲 |kruskal算法精讲

prim算法精讲 53. 寻宝&#xff08;第七期模拟笔试&#xff09; 题目描述 在世界的某个区域&#xff0c;有一些分散的神秘岛屿&#xff0c;每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路&#xff0c;方便运输。 不同岛屿之间&#xff0c;路途距离不同&…...

upload-labs通关练习

目录 环境搭建 第一关 第二关 第三关 第四关 第五关 第六关 第七关 第八关 第九关 第十关 第十一关 第十二关 第十三关 第十四关 第十五关 第十六关 第十七关 第十八关 第十九关 第二十关 总结 环境搭建 upload-labs是一个使用php语言编写的&#xff0c…...

wordpress搭建主题可配置json

网站首页展示 在线访问链接 http://dahua.bloggo.chat/ 配置json文件 我使用的是argon主题&#xff0c;你需要先安装好主题&#xff0c;然后可以导入我的json文件一键配置。 需要json界面配置文件的&#xff0c;可以在评论区回复&#xff0c;看见评论我会私发给你。~...

RWKV-5/6 论文被 COLM 2024 收录

由 Bo PENG 和 RWKV 开源社区共同完成的 RWKV-5/6架构论文《Eagle and Finch: RWKV with Matrix-Valued States and Dynamic Recurrence》被顶级会议 COLM 2024 收录。 这是继 RWKV-4 架构论文《RWKV: Reinventing RNNs for the Transformer Era》被 EMNLP 2023 收录之后&…...

MinIO分片下载超大文件

一、前言 各位亲爱的们&#xff0c;之前介绍过了上传超大文件到MinIO&#xff1a; MinIO分片上传超大文件&#xff08;纯服务端&#xff09;MinIO分片上传超大文件&#xff08;非纯服务端&#xff09; 这里最后再补充一下从MinIO下载超大文件。 二、从MinIO分片下载大文件 …...

Vue3 -- 新组件【谁学谁真香系列6】

Teleport Teleport是什么?–Teleport是一种能够将我们的组件html结构移动到指定位置的技术。 父组件: <template><div calss="outer"><h2>我是App组件</h2><img src="https://z1.ax1x.com/2023/11/19/piNxLo4.jpg" alt=&qu…...

Openstack3--本地仓库搭建(ftp源搭建失败)

上传镜像 后面的ftp源做不了&#xff0c;请将下面的本地openstack源在控制节点和计算节点都配置 在控制节点上传&#xff0c;安装ftp并配置启动后再在计算节点配置 将openStack-train.iso文件通过MobaXterm远程连接软件上传至控制节点 /opt 目录下 挂载 进入 /opt 目录 创建…...

【初阶数据结构与算法】链表刷题之移除链表元素、反转链表、找中间节点、合并有序链表、链表的回文结构

文章目录 一、移除链表元素思路一思路二 二、合并两个有序链表思路&#xff1a;优化&#xff1a; 三、反转链表思路一思路二 四、链表的中间节点思路一思路二 五、综合应用之链表的回文结构思路一&#xff1a;思路二&#xff1a; 一、移除链表元素 题目链接&#xff1a;https:…...

【PGCCC】Postgresql Toast 原理

前言 上篇博客讲述了 postgresql 如何存储变长数据&#xff0c;它的应用主要是在 toast 。Toast 在存储大型数据时&#xff0c;会将它存储在单独的表中&#xff08;称为 toast 表&#xff09;。因为 postgresql 的 tuple&#xff08;行数据&#xff09;是存在在 Page 中的&…...

vue3使用element-plus,树组件el-tree增加引导线

vue3使用element-plus&#xff0c;树组件el-tree增加引导线 vue3项目element-plus&#xff0c;树组件el-tree增加引导线 element-plus组件库的el-tree样式 因为element的样式不满足当前的的需求&#xff0c;UI图&#xff0c;所以对el-tree进行增加了引导线 修改样式如下&am…...

AlphaFold3中文使用说明

目录 1. 在线网站用例1. 使用json输入预测蛋白结构 2. 本地命令行2.1 运行示例2.2 AF3输入输入格式JSON兼容性JSON最外层&#xff08;Top-level&#xff09;结构序列多序列比对MSA结构模板键 用户提供CCDs 2.3 AF3输出 AlphaFold3&#xff08;AF3&#xff09;可以通过在线网站或…...

使用@react-three/fiber,@mkkellogg/gaussian-splats-3d加载.splat,.ply,.ksplat文件

前言 假设您正在现有项目中集成这些包&#xff0c;而该项目的构建工具为 Webpack 或 Vite。同时&#xff0c;您对 Three.js 和 React 有一定的了解。如果您发现有任何错误或有更好的方法&#xff0c;请随时留言。 安装 npm install three types/three react-three/fiber rea…...

Koa进阶:掌握中间件和参数校验的艺术

目录 一、首先下载依赖 二、在index.js中引入koa-parameter&#xff0c;一般挂载这个中间件时会放在注册请求体的后面 三、使用实例 四、如果跟我们所需求的参数不同&#xff0c;返回结果直接会返回422 koa-parameter一般是用来校验请求传过来的参数是否是自己所需要的的 G…...