利用红黑树封装map和set

目录

  • 一、正向迭代器
    • 1.1 operator++
    • 1.2 operator--
    • 1.3 参考代码
  • 二、反向迭代器
  • 三、封装set
  • 四、封装map
  • 五、底层红黑树的实现

一、正向迭代器

我们之前vector,list这些都是容器的迭代器都是简单的指针++或者_node=_node->next这样的,那是因为它们要么是连续的空间,要么是一个节点中存放着下一个节点的地址,但是我们这里的红黑树的迭代器就没有那么简单了,因为这里的红黑树的各个节点既不是连续的内存,也没有在节点内存放着下一个节点的地址,所以红黑树的迭代器的++和–操作该如何完成呢?

1.1 operator++

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1.2 operator–

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.3 参考代码

	enum Colour{RED,BLACK};//当RBTree封装set的时候T是K类型//当RBTree封装map的时候T是pair<K,V>类型//这样设计RBTreeNode就可以让T接受什么类型//就是什么类型,符合泛型编程template <class T>struct RBTreeNode{public://红黑树存放的值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), _col(RED){}};template <class T,class Ref,class Ptr>struct __RBTree_iterator{//由于这里的iterator的传的参数是T,T&,T*,跟Self是不一样的,// 所以无论如何这里的iterator就是普通迭代器typedef __RBTree_iterator<T, T&, T*> iterator;typedef __RBTree_iterator<T, Ref, Ptr> Self;typedef RBTreeNode<T> Node;public://构造函数__RBTree_iterator(Node* node):_node(node){}//当我们是实例化出const迭代器的时候,这里是一个构造函数,是利用普通迭代器构造一个const迭代器的构造函数//当我们是实例化出一个普通迭代器的时候,这里是一个拷贝构造函数,即利用普通迭代器拷贝构造出一个普通迭代器//这个函数必须提供,因为后面封装set必须要用到这个,否则会报错__RBTree_iterator(const iterator& it):_node(it._node){}public:Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){//找下一个节点Node* subR = _node->_right;//如果右子树不为空,那么下一个节点就是右子树的最左节点if (subR != nullptr){Node* leftmost = subR;while (leftmost->_left){leftmost = leftmost->_left;}//最后把_node更新成右子树的最左节点_node = leftmost;}else{//如果右子树为空,说明我的左子树访问完了,我也访问完了,那么下一个是访问我的父节点吗?//其实不是的如果我是父亲的右孩子节点,那么我访问完了,但是因为我是父亲的右孩子节点,//所以我访问完了,并且我的父亲也访问完了,所以这时的下一个需要访问的节点并不是我的//父节点,而是要沿祖先路径查找我是父亲的左孩子的那个父亲,这个父节点才是我们下一个//需要访问的节点,因为我是父亲的左孩子,那么我访问完了,下一个就是访问我的父节点了Node* cur = _node;Node* parent = _node->_parent;while (parent){if (cur == parent->_left){break;}else{//找我是父亲左孩子的那个父亲cur = parent;parent = parent->_parent;}}//这个父节点就是我们下一个需要访问的节点,如果parent是nullptr,那么//说明找到根节点都没有找到我是父亲左孩子的那个父亲,说明这棵树已经访问//完了,我们是用nullptr作为迭代器的end的_node = parent;}return *this;}//找上一个节点Self& operator--(){Node* subL = _node->_left;//如果左子树存在,那么当前节点的上一个节点就是左子树的最右节点if (subL != nullptr){/*找左子树的左右节点*/Node* rightmost = subL;while (rightmost->_right){rightmost = rightmost->_right;}_node = rightmost;}//如果左子树为nullptr,那么沿祖先路径查找我是父亲的右孩子的那个父亲,//因为我是父亲的右孩子,那么我的前一个节点就是这个父亲else{Node* cur = _node;Node* parent = _node->_parent;while (parent){if (cur == parent->_right){break;}else{cur = parent;parent = parent->_parent;}}_node = parent;}return *this;}bool operator!=(const Self& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}public:Node* _node;};

二、反向迭代器

	//反向迭代器是使用了适配器模式,利用已有的正向迭代器适配出来的//传参时传的是正向迭代器Itreator,然后利用正向迭代器的接口封装//出反向迭代器的接口template <class Iterator,class Ref,class Ptr>struct __RBTree_reverse_iterator{typedef __RBTree_reverse_iterator<Iterator, Ref, Ptr> Self;public://构造函数,利用正向迭代器构造出一个反向迭代器__RBTree_reverse_iterator(const Iterator& it):_it(it){}//通过运算符重载得到我们想要的效果,因为反向迭代器的++就是正向迭代器的--,//所以直接调用正向迭代器的--接口即可完成反向迭代器的++操作Self& operator++(){--_it;return *this;}//同理,反向迭代器的--就是正向迭代器的++,所以直接调用正向迭代器的++即可Self& operator--(){++_it;return *this;}bool operator!=(const Self& it){return _it != it._it;}Ref operator*(){return *_it;}Ptr operator->(){return &(operator*());}private:Iterator _it;};

三、封装set

在这里插入图片描述

#pragma once#include "RBTree.h"namespace kb
{template <class K>class MySet{//仿函数,用于取出红黑树的节点的key值,方便在插入节点的时候作大小比较,//如果是set的话取出来的就是key本身struct SetKeyOfT{const K& operator()(const K& key){return key;}};//对于set来说,set的模型和map的模型是一样的,都是key-value模型,set的key-value//对应的是key-key,map的key-value对应的是key-pair<K,V>//对于set我们知道,无论是set的key还是set的value都是不允许修改的,所以要把通过迭代器//修改的行为直接禁掉,所以对于set来说,无论是iterator还是const_iterator的底层都是// 用红黑树中的const_iterator,所以我们在使用set的iterator的时候看似使用的是普通//迭代器,实际上底层也是const迭代器,这样做就能从根本上杜绝通过普通迭代器修改set的key//或者value的行为了,这个库里面的实现方式,这个做法是真的绝typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;//反向迭代器也是按照正向迭代器那样设计来达到不让别人通过普通迭代器修改set的key或者value值的typedef typename RBTree<K, K, SetKeyOfT>::const_reverse_iterator reverse_iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_reverse_iterator const_reverse_iterator;public:iterator begin(){return _set.begin();}iterator end(){return _set.end();}const_iterator cbegin()const{return _set.begin();}const_iterator cend()const{return _set.end();}reverse_iterator rbegin(){return _set.rbegin();}reverse_iterator rbegin() const{return _set.rbegin();}reverse_iterator rend(){return _set.rend();}reverse_iterator rend() const{return _set.rend();}//这个返回值的pair中的iterator是27行typedef出来的iterator,所以这个iterator看起来是//普通迭代器,实际上是const迭代器pair<iterator,bool> insert(const K& x){//重点理解//调用Insert函数返回的pair中的first是普通迭代器,而这里的作为返回值的pair中的// iterator实际上是const_iterator,所以无法从普通的迭代器直接转化成const_iterator,// 所以需要先用一个临时的pair接受Insert的返回值,这个临时的pair的第一个参数必须要显式// 地指明是红黑树中的普通迭代器,然后再利用临时的pair中的第一个参数的这个普通迭代器构造// 一个const迭代器,注意需要在iterator中提供一个构造const迭代器的构造函数,否则会报错pair<typename RBTree<K, K, SetKeyOfT>::iterator,bool> ret = _set.Insert(x);return pair<iterator, bool>(ret.first, ret.second);//也可以像这样子写,因为我们已经提供了一个用普通迭代器构造const迭代器的构造函数,但是// //不建议这样子写,因为这样子写我们对这个需要注意的细节就隐藏了,降低了可读性//return _set.Insert(x);}void clear(){_set.clear();}private://对于set,key和value都是K类型,并且需要把比较的仿函数也作为模板参数传过去RBTree<K, K, SetKeyOfT> _set;};
}

四、封装map

#pragma once#include "RBTree.h"namespace kb
{template <class K,class V>class MyMap{//仿函数,用于取出红黑树的节点的key值,方便在插入节点的时候作大小比较,//如果是map的话取出来的就是pair中的key值struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};//对于map,因为map的key值是不能被修改的,但是它的value值是允许被修改的,所以这里的//迭代器就不能像set那里的迭代器那样设计了,因为如果像那样设计的话,无论是key还是value//都没办法进行修改了,那需要怎么控制住key不能被修改,而value要能被修改呢?库里面的操作//也很牛啊,它是直接在传pair的key时直接传一个const K构造pair,而V是普通的类型,这样设计//就能使pair中的key不能被修改,而value能被修改。//const迭代器就是pair的K和V都传const,这样pair的K,V也就都不能被修改了typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;typedef typename RBTree<K, pair<const K, const V>, MapKeyOfT>::const_iterator const_iterator;//反向迭代器也是同样的设计typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::reverse_iterator reverse_iterator;typedef typename RBTree<K, pair<const K, const V>, MapKeyOfT>::const_reverse_iterator const_reverse_iterator;public://这里的插入函数的返回值的pair中的iterator和Insert的返回值的pair的iterator//都是相同的类型,都是普通迭代器,不存在类型转换,所以就无需像set中的insert那样设计pair<iterator,bool> insert(const pair<K, V>& x){return _map.Insert(x);}//[]的运算符的重载就是利用insert的返回值实现的V& operator[](const K& key){pair<iterator, bool> ret = _map.Insert(make_pair(key, V()));return ret.first->second;}void clear(){_map.clear();}//封装迭代器iterator begin(){return _map.begin();}iterator end(){return _map.end();}const_iterator begin() const{return _map.begin();}const_iterator end() const{return _map.end();}const_reverse_iterator rbegin() const{return _map.end();}const_reverse_iterator rend() const{return _map.begin();}private://直接从这里传参实例化这棵树的时候把pair的K设置为const,这样//对于map的key就不能被修改了,而value依然能够被修改RBTree<K, pair<const K,V>, MapKeyOfT> _map;};
}

五、底层红黑树的实现

#pragma once#include <iostream>
using namespace std;
#include <assert.h>namespace kb
{enum Colour{RED,BLACK};//当RBTree封装set的时候T是K类型//当RBTree封装map的时候T是pair<K,V>类型//这样设计RBTreeNode就可以让T接受什么类型//就是什么类型,符合泛型编程template <class T>struct RBTreeNode{public://红黑树存放的值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), _col(RED){}};template <class T,class Ref,class Ptr>struct __RBTree_iterator{//由于这里的iterator的传的参数是T,T&,T*,跟Self是不一样的,// 所以无论如何这里的iterator就是普通迭代器typedef __RBTree_iterator<T, T&, T*> iterator;typedef __RBTree_iterator<T, Ref, Ptr> Self;typedef RBTreeNode<T> Node;public://构造函数__RBTree_iterator(Node* node):_node(node){}//当我们是实例化出const迭代器的时候,这里是一个构造函数,是利用普通迭代器构造一个const迭代器的构造函数//当我们是实例化出一个普通迭代器的时候,这里是一个拷贝构造函数,即利用普通迭代器拷贝构造出一个普通迭代器//这个函数必须提供,因为后面封装set必须要用到这个,否则会报错__RBTree_iterator(const iterator& it):_node(it._node){}public:Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){//找下一个节点Node* subR = _node->_right;//如果右子树不为空,那么下一个节点就是右子树的最左节点if (subR != nullptr){Node* leftmost = subR;while (leftmost->_left){leftmost = leftmost->_left;}//最后把_node更新成右子树的最左节点_node = leftmost;}else{//如果右子树为空,说明我的左子树访问完了,我也访问完了,那么下一个是访问我的父节点吗?//其实不是的如果我是父亲的右孩子节点,那么我访问完了,但是因为我是父亲的右孩子节点,//所以我访问完了,并且我的父亲也访问完了,所以这时的下一个需要访问的节点并不是我的//父节点,而是要沿祖先路径查找我是父亲的左孩子的那个父亲,这个父节点才是我们下一个//需要访问的节点,因为我是父亲的左孩子,那么我访问完了,下一个就是访问我的父节点了Node* cur = _node;Node* parent = _node->_parent;while (parent){if (cur == parent->_left){break;}else{//找我是父亲左孩子的那个父亲cur = parent;parent = parent->_parent;}}//这个父节点就是我们下一个需要访问的节点,如果parent是nullptr,那么//说明找到根节点都没有找到我是父亲左孩子的那个父亲,说明这棵树已经访问//完了,我们是用nullptr作为迭代器的end的_node = parent;}return *this;}//找上一个节点Self& operator--(){Node* subL = _node->_left;//如果左子树存在,那么当前节点的上一个节点就是左子树的最右节点if (subL != nullptr){/*找左子树的左右节点*/Node* rightmost = subL;while (rightmost->_right){rightmost = rightmost->_right;}_node = rightmost;}//如果左子树为nullptr,那么沿祖先路径查找我是父亲的右孩子的那个父亲,//因为我是父亲的右孩子,那么我的前一个节点就是这个父亲else{Node* cur = _node;Node* parent = _node->_parent;while (parent){if (cur == parent->_right){break;}else{cur = parent;parent = parent->_parent;}}_node = parent;}return *this;}bool operator!=(const Self& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}public:Node* _node;};//反向迭代器是使用了适配器模式,利用已有的正向迭代器适配出来的//传参时传的是正向迭代器Itreator,然后利用正向迭代器的接口封装//出反向迭代器的接口template <class Iterator,class Ref,class Ptr>struct __RBTree_reverse_iterator{typedef __RBTree_reverse_iterator<Iterator, Ref, Ptr> Self;public://构造函数,利用正向迭代器构造出一个反向迭代器__RBTree_reverse_iterator(const Iterator& it):_it(it){}//通过运算符重载得到我们想要的效果,因为反向迭代器的++就是正向迭代器的--,//所以直接调用正向迭代器的--接口即可完成反向迭代器的++操作Self& operator++(){--_it;return *this;}//同理,反向迭代器的--就是正向迭代器的++,所以直接调用正向迭代器的++即可Self& operator--(){++_it;return *this;}bool operator!=(const Self& it){return _it != it._it;}Ref operator*(){return *_it;}Ptr operator->(){return &(operator*());}private:Iterator _it;};template <class K,class T,class KeyOfT>class RBTree{typedef RBTreeNode<T> Node;public:typedef __RBTree_iterator<T, T&, T*> iterator;		//正向普通迭代器typedef __RBTree_iterator<T, const T&, const T*> const_iterator;  //正向const迭代器typedef __RBTree_reverse_iterator<iterator, T, T*> reverse_iterator; //反向普通迭代器typedef __RBTree_reverse_iterator<const_iterator, const T, const T*> const_reverse_iterator;//反向const迭代器public:RBTree():_root(nullptr){}//拷贝构造RBTree(const RBTree& rbt):_root(nullptr),_num(rbt._num){_root = Copy(rbt._root);}Node* Copy(Node* root){if (root == nullptr){return nullptr;}Node* ret = new Node(root->_data);ret->_col = root->_col;ret->_left = Copy(root->_left);if (ret->_left){ret->_left->_parent = ret;}ret->_right = Copy(root->_right);if (ret->_right){ret->_right->_parent = ret;}return ret;}void Swap(Node*& tmp){swap(_root, tmp);}//赋值重载RBTree& operator=(RBTree root){Swap(root._root);return *this;}//析构函数~RBTree(){clear();}Node* Find(const K& key){KeyOfT kot;Node* cur = _root;while (cur){if (key < kot(cur->_data)){cur = cur->_left;}else if (key > kot(cur->_data)){cur = cur->_right;}else{return cur;}}return nullptr;}pair<iterator,bool> Insert(const T& data){KeyOfT kot;//如果是空树,那么就直接插入一个黑色节点做根即可//注意要改颜色,因为节点的颜色默认是红色的if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root), true);}//根据二叉搜索树的规则插入节点,同时记录父节点Node* parent = nullptr;Node* cur = _root;while (cur){if (kot(data) < kot(cur->_data)){parent = cur;cur = cur->_left;}else if (kot(data) > kot(cur->_data)){parent = cur;cur = cur->_right;}else{return make_pair(cur, false);}}cur = new Node(data);Node* newNode = cur;if (kot(data) < kot(parent->_data)){parent->_left = cur;cur->_parent = parent;}else if (kot(data) > kot(parent->_data)){parent->_right = cur;cur->_parent = parent;}//走到这里已经插入完成,后面是检查新插入的节点有没有破坏红黑树的规则的逻辑//检查新插入的节点是否满足红黑树的规则,如果不满足就要进行变色/变色+旋转//因为新插入的cur节点的颜色一定是红色的,当cur的父节点存在并且为红色//时说明出现了连续的两个红色节点,这时需要进行变色/变色+旋转,如果父节点// 不存在或者存在且为黑色时就无需再处理了。// 为什么父节点有可能不存在?因为这是一个循环,循环更新往祖先处理可能到达根节点,//到了根节点就无需再处理了while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (cur == parent->_left){//             grandfather//     parent//cur//画图//叔叔存在且为红if (uncle && uncle->_col == RED){//变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续沿祖先路径检查cur = grandfather;parent = cur->_parent;//这里曾经漏写了}//叔叔不存在或者存在且为黑else//(uncle==nullptr||uncle->_col==BLACK){//单纯的左边高,进行右单旋+变色RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;//旋转完之后无需再沿祖先路径处理break;}}else//cur == parent->_right{//              grandfather//       parent//              cur//画图//叔叔存在且为红if (uncle && uncle->_col == RED){//变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续往上调整cur = grandfather;parent = cur->_parent;//这里曾经忘记写}//叔叔不存在或者存在且为黑else//(uncle==nullptr || uncle->_col == BLACK){//左右双旋RotateL(parent);RotateR(grandfather);//变色grandfather->_col = RED;cur->_col = BLACK;//旋转后就无需再沿祖先路径检查了,具体原因画图理解break;}}}else//(parent == grandfather->_right){Node* uncle = grandfather->_left;if (cur == parent->_right){//          grandfather//                         parent//                                    cur//画图//叔叔存在且为红if (uncle && uncle->_col == RED){//变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续沿祖先路径检查cur = grandfather;parent = cur->_parent;}//叔叔不存在或者存在且为黑else//(uncle==nullptr || uncle->_col == BLACK){//单纯的右边高,进行左单旋+变色RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;//旋转+变色后就不需要再沿祖先路径检查了break;}}else//(cur == parent->_left){//          grandfather//                          parent//          cur//叔叔存在且为红if (uncle && uncle->_col == RED){//变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续沿祖先路径检查cur = grandfather;parent = cur->_parent;}//叔叔不存在或者存在且为黑else//(uncle==nullptr || uncle->_col == BLACK){//根据模型可知需要右左双旋+变色RotateR(parent);RotateL(grandfather);grandfather->_col = RED;cur->_col = BLACK;//旋转后就不需要再沿祖先路径检查了break;}}}}_num++;//最后记得把根节点的颜色改成黑色_root->_col = BLACK;return make_pair(iterator(newNode), true);}void clear(){Destroy(_root);_root = nullptr;_num = 0;}bool empty(){return _root == nullptr;}iterator begin(){Node* leftmost = _root;while (leftmost && leftmost->_left){leftmost = leftmost->_left;}return iterator(leftmost);}const_iterator begin() const{Node* leftmost = _root;while (leftmost && leftmost->_left){leftmost = leftmost->_left;}return const_iterator(leftmost);}iterator end(){return iterator(nullptr);}const_iterator end() const{return const_iterator(nullptr);}reverse_iterator rbegin(){Node* rightmost = _root;while (rightmost && rightmost->_right){rightmost = rightmost->_right;}return reverse_iterator(rightmost);}const_reverse_iterator rbegin() const{Node* rightmost = _root;while (rightmost && rightmost->_right){rightmost = rightmost->_right;}return const_reverse_iterator(rightmost);}reverse_iterator rend(){return reverse_iterator(nullptr);}const_reverse_iterator rend() const{return const_reverse_iterator(nullptr);}size_t Size(){return _num;}bool Isbalance(){return _Isbalance(_root);}private:void Destroy(Node* root){if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}bool CheckColour(Node* root, int blackNum, int BenchMark){//走到空树说明这条路径已经走完了,需要检查黑色节点的个数和基准值相不相等if (root == nullptr){//如果不相等,证明不平衡,返回falseif (blackNum != BenchMark){return false;}//如果相等,则说明本条路径没有出事,还要检查其它路径return true;}//如果出现连续红色节点证明这棵树出问题了,返回falseif (root->_col == RED && root->_parent && root->_parent->_col == RED){return false;}if (root->_col == BLACK){blackNum++;}//递归检查所有路径的颜色return CheckColour(root->_left, blackNum, BenchMark)&& CheckColour(root->_right, blackNum, BenchMark);}bool _Isbalance(Node* root){//空树可以认为是平衡的if (root == nullptr){return true;}//根节点不是黑色说明这棵树出事了if (root->_col != BLACK){return false;}//先算出一条路径的黑色节点的个数作为基准值,检查其它路径的黑色节点数目是否跟这个标准值//是否一样,如果不一样就证明这棵树不平衡了,那么如果这个基准值本身就是错的呢,那也没有关系,//只要有路径的黑色节点和其它路径不相等,就说明肯定有其中一条路径出问题了,至于是哪条路径//出问题就不重要了int BenchMark = 0;Node* cur = root;while (cur){if (cur->_col == BLACK){BenchMark++;}cur = cur->_left;}//检查所有路径中是否有连续红色节点和各路径中黑色节点的数目是否相等return CheckColour(root, 0, BenchMark);}//旋转的细节如果不清楚的话请看上一篇关于AVL树的旋转,红黑树的旋转和AVL树的旋转是一样的void RotateL(Node* parent){assert(parent);Node* cur = parent->_right;Node* curleft = cur->_left;Node* parentParent = parent->_parent;parent->_right = curleft;cur->_left = parent;if (curleft){curleft->_parent = parent;}parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = cur;}else{parentParent->_right = cur;}cur->_parent = parentParent;}}void RotateR(Node* parent){assert(parent);Node* cur = parent->_left;Node* curright = cur->_right;Node* parentParent = parent->_parent;parent->_left = curright;cur->_right = parent;if (curright){curright->_parent = parent;}parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = cur;}else{parentParent->_right = cur;}cur->_parent = parentParent;}}private:Node* _root = nullptr;int _num = 0;//统计树的节点个数};}

以上就是利用红黑树封装map和set的内容啦,因为map和set的接口太多了,这里只是把插入接口和迭代器封装出来,其它的接口就不一一封装了,感兴趣的老铁可以自己尝试一下封装其它有用的接口哟。以上就是今天想要跟大家分享的内容咯,你学会了吗?如果你感觉到有所收获,可以点点小心心,点点关注哦,后期还会持续更新C++的相关知识哦,我们下期见!!!!!!!!!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/79195.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

背包问题---怎么选取物品,可以使得背包装的物品价值最大?

原文&#xff1a; https://zhuanlan.zhihu.com/p/567560364 1&#xff09;0-1背包问题的描述 现在有四种物品&#xff0c;每种物品只有1件&#xff0c;它们的重量与价值如下表。 现在有一个背包&#xff0c;总容量为8。问怎么选取物品&#xff0c;可以使得背包装的物品价值…

yolov5添加ECA注意力机制

ECA注意力机制简介 论文题目&#xff1a;ECA-Net: Efficient Channel Attention for Deep Convolutional Neural Networks 论文地址&#xff1a;here 基本原理 &#x1f438; ECANet的核心思想是提出了一种不降维的局部跨通道交互策略&#xff0c;有效避免了降维对于通道注意…

【力扣每日一题】2023.9.12 课程表Ⅳ

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 今天是课程表系列题目的最后一题&#xff0c;因为我在题库里找不到课程表5了&#xff0c;所以今天的每日一题就是最后一个课程表了。 题…

关于Arcgis这62个常用技巧,你知道几个?

小编推荐&#xff1a;ArcGIS类全领域教程推荐https://mp.weixin.qq.com/s?__bizMzU0MDQ3MDk3NA&mid2247560279&idx5&sn8ac52cabeb8c7e3b2c083e07ef6056da&chksmfb3b1786cc4c9e909bab16dd99e88e5f5f9816eb2349d6d73a68a137a5264aa606b2035d7b3e&token16747…

c++ 中的函数指针

以下图片演示了c中函数指针的用法。如下图可见&#xff0c;把函数地址赋值给函数指针&#xff0c;用函数名或者函数名的地址&#xff0c;都可以&#xff0c;c编译器不报错。即 ptr f 和 ptr &f 都对。但准确的话&#xff0c;函数名就是地址&#xff0c;在编译时候&#x…

2023-9-14 最长公共子序列

题目链接&#xff1a;最长公共子序列 #include <iostream> #include <algorithm>using namespace std;const int N 1010;int n, m; char a[N], b[N]; int f[N][N];int main() {cin >> n >> m;cin >> a 1 >> b 1;for(int i 1; i < n…

代理HTTP使用不当会出现哪些问题?如何正确使用代理服务?

代理HTTP是一种常见的网络代理方式&#xff0c;它为客户端和服务器之间提供中间层&#xff0c;转发上下游的请求和响应。正确使用代理HTTP可以提高采集效率、增加网络安全性、加速网络速度、保护用户隐私。但是&#xff0c;使用不当就难以达到预期的效果&#xff0c;在使用代理…

langchain+GPT+neo4j 图数据库

neo4j版本是5.11.0,langchain的版本 0.0.288下载apoc插件 https://neo4j.com/docs/apoc/current/installation/ neo4j.conf文件把apoc.*添加到dbms.security.procedures.unrestricted配置项 使用return apoc.version()来查看是否安装成功 pip install neo4j图 参考官网&…

vue中预览xml并高亮显示

项目中有需要将接口返回的数据流显示出来&#xff0c;并高亮显示&#xff1b; 1.后端接口返回blob,类型为xml,如图 2.页面中使用pre code标签&#xff1a; <pre v-if"showXML"><code class"language-xml">{{xml}}</code></pre> …

Spring源码分析(三) bean的生命周期 getBean()和doGetBean()

b、在中篇会正式经历一套生命周期流程 getBean() -> doGetBean() -> createBean() -> doCreateBean() -> createBeanInstance() -> populateBean() -> initializeBean() 流程 1、AbstractBeanFactory#getBean() 此处开始进行对象的获取也就是核心步骤 Overr…

shell脚本学习笔记02(小滴课堂)

可以在home目录下创建一个shell.sh文件。 按w进入命令行模式。按i进入插入模式。如果想返回命令行模式&#xff0c;按esc即可。然后可以使用x和dd进行删除内容。 在插入模式下我们点击esc键&#xff0c;再去按:键&#xff0c;我们就可以进入到底行模式了&#xff1a; 可以设…

PHP实现微信小程序状态检测(违规、暂停服务、维护中、正在修复)

实现原理 进入那些状态不正常的小程序会被重定向至一个Url&#xff0c;使用抓包软件抓取这个Url&#xff0c;剔除不必要参数&#xff0c;使用cURl函数请求网页获得HTML内容&#xff0c;根据内容解析出当前APPID的小程序的状态。 代码 <?php// 编码header(Content-type:ap…

数据库被攻击需要注意什么

没想到自己用了一个简单的腾讯虚拟机&#xff0c;里面自己安装了一个 MySQL 数据库也会被黑客攻击。 一、问题现象 小程序访问不了&#xff0c;后台程序报数据库相关的错误。 查看数据库&#xff0c;发现数据库被篡改。 二、问题原因 1、为了可以远程能够访问数据库&#xf…

基于Qt4开发曲线绘制交互软件Plotter

目前市面上有很多曲线绘制软件,但其交互功能较差。比如,想要实现数据的交互,同步联动等,都需要大量繁琐的人工操作。所以讲想开发一款轻量级的曲线绘制交互软件。下面就以此为案例,记录一下基于Qt4的开发过程。 目录 1 需求 2 技术路线 3 开发流程 1 框架搭建 2 菜单…

首家!亚信科技AntDB数据库完成中国信通院数据库迁移工具专项测试

近日&#xff0c;在中国信通院“可信数据库”数据库迁移工具专项测试中&#xff0c;湖南亚信安慧科技有限公司&#xff08;简称&#xff1a;亚信安慧科技&#xff09;数据库数据同步平台V2.1产品依据《数据库迁移工具能力要求》、结合亚信科技AntDB分布式关系型数据库产品&…

[每周一更]-(第61期):Rust入门策略(持续更新)

一门语言的学习&#xff0c;就要从最基本的语法开始认识&#xff0c;再分析不同语言的区别&#xff0c;再加上实战&#xff0c;才能更快的学会&#xff0c;领悟到作者的设计思想&#xff1b; 介绍 Rust编程练习 开发工具VSCode及插件 社区驱动的 rust-analyzerEven Better T…

Java基于SpringBoot的闲一品交易平台

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 大家好&#xff0c;我是程序员徐师兄、今天给大家谈谈基于android的app开发毕设题目&#xff0c;以及基于an…

slickEdit 2022 (v27.0.2)Ubuntu安装以及破解

1去官网下载安装包 SlickEdit 也可以从我这里下载源码包 https://download.csdn.net/download/m0_38012470/88343180 2.解压压缩包并进入根目录 3.sudo ./vsinst 4按住回车不松手一直到显示需要你输入yes的时候 5.一路通过需要输入Y的时候就输入 6.一直到弹出对话框关闭…

Linux的常见指令

目录 pwd命令ls 指令mkdir指令touch指令cd 指令rmdir指令 && rm 指令man指令nanocp指令mv指令cat指令more指令less指令head指令tail指令grep指令热键zip/unzip指令tar指令uname –r指令输出重定向 图形化界面和命令行操作本质都是对操作系统进行直接或间接的操作 pwd命…

在微信公众号怎么实现每日签到功能

在微信公众号中实现每日签到功能&#xff0c;可以为企业或公众号运营者带来许多好处。每日签到功能不仅可以增加用户粘性&#xff0c;提高用户参与度&#xff0c;还可以为公众号带来更多的流量和曝光度。那么&#xff0c;如何在微信公众号中实现每日签到功能呢&#xff1f;本文…