STL容器 —— map和set的模拟实现

news/2024/5/3 12:01:57/文章来源:https://blog.csdn.net/lyzzs222/article/details/127362919

文章目录

    • 1. 红黑树的框架
    • 2. 模板参数的一些细节
    • 3. 红黑树支持迭代器
      • 3.1 迭代器的实现
        • 3.1.1 解引用
        • 3.1.2 == 和 !=
        • 3.1.3 ++ 和 - -
      • 3.2 红黑树封装迭代器
        • 3.2.1 修改一下insert
        • 3.2.2 迭代器的 begin(),end()
        • 3.2.3 修改一下find函数,返回迭代器
    • 4. 红黑树继续完善
      • 4.1 红黑树的拷贝构造和赋值重载
      • 4.2 红黑树的析构函数
    • 5. 封装红黑树的map和set
      • 5.1 map的模拟实现
      • 5.2 set的模拟实现
    • 6. 总结

前言: map和set的底层是用红黑树实现的,红黑树的大框架,在我的上一篇博客: 红黑树的实现 中已经详细的讲解了,但是对于支持实现map和set还是欠缺的,比如 :迭代器,模板参数等,还需要再继续完善。这样才能够 模拟实现 map和set。


1. 红黑树的框架

#include<iostream>
#include<assert.h>namespace RB_tree
{enum Color{Red,Black};template<class K, class V>struct RBtree_node{RBtree_node<K, V>* left_;RBtree_node<K, V>* right_;RBtree_node<K, V>* parents_;std::pair<K, V> kv_;Color col_;RBtree_node(const std::pair<K, V>& kv):left_(nullptr),right_(nullptr),parents_(nullptr),kv_(kv),col_(Red){}};template<class K, class V>class RBtree{typedef RBtree_node<K, V> Node;private:Node* _root;public:RBtree():_root(nullptr){}public:bool insert(const std::pair<K, V>& node){if (_root == nullptr){_root = new Node(node);_root->col_ = Black;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (node.first > cur->kv_.first){parent = cur;cur = cur->right_;}else if (node.first < cur->kv_.first){parent = cur;cur = cur->left_;}else{assert(false);}}cur = new Node(node);cur->col_ = Red;if (parent->kv_.first > cur->kv_.first){parent->left_ = cur;cur->parents_ = parent;}else{parent->right_ = cur;cur->parents_ = parent;}while (parent && parent->col_ == Red){Node* grand = parent->parents_;/// 插入到左树if (parent == grand->left_){Node* uncle = grand->right_;/// 情况一if (uncle && uncle->col_ == Red){parent->col_ = Black;uncle->col_ = Black;grand->col_ = Red;/// 往上更新cur = grand;parent = cur->parents_;}/// 情况二 或情况三else{ 右单旋if (cur == parent->left_){revolov_R(grand);parent->col_ = Black;grand->col_ = Red;}else{revolov_L(parent);revolov_R(grand);cur->col_ = Black;grand->col_ = Red;}break;}}/// 插入到右树else{Node* uncle = grand->left_;/// 情况一if (uncle && uncle->col_ == Red){parent->col_ = Black;uncle->col_ = Black;grand->col_ = Red;/// 往上更新cur = grand;parent = cur->parents_;}else{ 左单旋if (cur == parent->right_){revolov_L(grand);parent->col_ = Black;grand->col_ = Red;}else{revolov_R(parent);revolov_L(grand);cur->col_ = Black;grand->col_ = Red;}break;}}}_root->col_ = Black;return true;}private:void revolov_R(Node* issue_node){Node* issue_Lnode = issue_node->left_;Node* issue_Lnode_R = issue_Lnode->right_;Node* issue_node_parent = issue_node->parents_;issue_node->left_ = issue_Lnode_R;if (issue_Lnode_R){issue_Lnode_R->parents_ = issue_node;}issue_Lnode->right_ = issue_node;issue_node->parents_ = issue_Lnode;if (issue_node_parent == nullptr){_root = issue_Lnode;issue_Lnode->parents_ = nullptr;}else{if (issue_node_parent->left_ == issue_node)issue_node_parent->left_ = issue_Lnode;elseissue_node_parent->right_ = issue_Lnode;issue_Lnode->parents_ = issue_node_parent;}}void revolov_L(Node* issue_node){Node* issue_Rnode = issue_node->right_;Node* issue_Rnode_L = issue_Rnode->left_;Node* issue_node_parent = issue_node->parents_;issue_node->right_ = issue_Rnode_L;if (issue_Rnode_L){issue_Rnode_L->parents_ = issue_node;}issue_Rnode->left_ = issue_node;issue_node->parents_ = issue_Rnode;if (issue_node_parent == nullptr){_root = issue_Rnode;issue_Rnode->parents_ = nullptr;}else{if (issue_node_parent->left_ == issue_node)issue_node_parent->left_ = issue_Rnode;elseissue_node_parent->right_ = issue_Rnode;issue_Rnode->parents_ = issue_node_parent;}}public:void InOrder(){_InOrder(_root);}private:void _InOrder(Node* root){if (root == NULL)return;_InOrder(root->left_);std::cout << root->kv_.first << ":" << root->kv_.second << std::endl;_InOrder(root->right_);}};}

2. 模板参数的一些细节

底层 set中红黑树的节点 不是键值对,单一的一个类型;map中红黑树的节点 是一个键值对。所以需要创建两个版本的红黑树吗?答案是不需要,可以利用模板来支持泛型的。

我们需要对 红黑树的节点的 模板参数 做一下修改:
这样 就能够通过模板参树 来控制 节点的类型

    template<class T>struct RBtree_node{RBtree_node<T>* left_;RBtree_node<T>* right_;RBtree_node<T>* parents_;T kv_;Color col_;RBtree_node(const T & kv):left_(nullptr),right_(nullptr),parents_(nullptr),kv_(kv),col_(Red){}};

  1. set的基本框架
template<class K>class set{private:RBtree<K,K,setketofT> t_;public:struct setketofT{const K& operator()(const K& k){return k;}};};
  1. map的基本框架
template<class K,class V>class map{private:RBtree<K, std::pair<K, V>,mapkofT> t_;public:struct mapkofT{const K& operator()(const std::pair<K,V>& t){return t.first;}};};

可以看到在基本框架里面:给红黑树多传了一个模板参数 分别是setketofT,mapkofT。它们是仿函数,目的是 让红黑树顺利的取出,节点的类型,然后可以进行插入等操作。map传的是 pair的first;set传的是 k。这个有点难理解,下面还会讲到的。

  • set的底层:RBtree<K,K,setketofT> t_;
  • map的底层:RBtree<K, std::pair<K, V>,mapkofT> t_;

红黑树的模板参数 也需要变动:

   // 第三个模板参数用于接收 仿函数template<class K, class T,class keyofT>class RBtree{typedef RBtree_node<T> Node;}

keyofT就是用来接收 仿函数的,它的作用就是 拿出 构建红黑树 需要比较的那个值,比如 set就是 拿的 k 做比较,map拿的是 pair<>的first进行比较。

3. 红黑树支持迭代器

首先一般情况下迭代器是个指针,比如 vector,string的迭代器,但是对于某些容器,需要对迭代器进行封装,从而使得此迭代器达到类似指针的功能,比如list,链表 对它指针 执行 ++ 操作,它是不能够 指向 下一个节点的,所以 它的迭代器就不能是一个单纯的指针,需要封装出一个类(迭代器),来实现 指针的操作,比如 ++ 迭代器 ,它就能指向list中下一个节点位置。

红黑树也是如此,我们也需要封装一个迭代器,实现 ++ ,- -,*,-> 的操作。红黑树迭代器走的是一个中序,这很关键,有了这个概念,我们才能开始 实现 迭代器的基本框架。

3.1 迭代器的实现

    template<class T,class Ref,class Ptr>struct RBtree_Iterator{typedef RBtree_node<T> Node;typedef RBtree_Iterator<T, Ref, Ptr> self;Node* node_;RBtree_Iterator(Node* node):node_(node){}self& operator++(){/// 如果右树不为空,那么下一个位置就是右树的最左节点if (node_->right_ != nullptr){Node* min = node_->right_;while (min->left_){min = min->left_;}node_ = min;}// 如果右树为空,那么有两种情况else{Node* cur = node_;Node* parent = cur->parents_;while (parent && cur == parent->right_){cur = parent;parent = cur->parents_;}node_ = parent;}return *this;}self& operator--(){/// 如果左树不为空,那么下一个位置就是左树的最右节点if (node_->left_ != nullptr){Node* max = node_->left_;while (max->right_){max=max->right_;}node_ = max;}// 如果左树为空,那么有两种情况else{Node* cur = node_;Node* parent = cur->parents_;while (parent && cur == parent->left_){cur = parent;parent = cur->parents_;}node_ = parent;}return *this;}Ref operator*(){return node_->kv_;}Ptr operator->(){return &node_->kv_;}bool operator ==(const self& n) const{return  n.node_ == node_;}bool operator !=(const self& n) const{return n.node_ != node_;}};

上面就是迭代器的实现,它的底层就是 个节点指针:

在这里插入图片描述
然后 实现具体的功能:

3.1.1 解引用

        Ref operator*(){return node_->kv_;}Ptr operator->(){return &node_->kv_;}

大家可能对 这个返回值 Ref 和 Ptr 有疑问,我们先来看个这个迭代器类的模板参数:
template<class T,class Ref,class Ptr>

第一个 模板参数T 是用来 定义迭代器中 底层指针 的类型的。
第二个模板参数Ref,以及第三个模板参数 Ptr是为了支持 const版本的迭代器所使用的。

如果 不需要支持 const版本,那么 就可以这样写:

        T& operator*(){return node_->kv_;}T* operator->(){return &node_->kv_;}

但是要求支持const迭代器,怎么办?有两种方式,可以在写个const版本的迭代器,但是代码冗余;或者通过模板参数传参,来控制这块的返回值。

于是红黑树中,定义迭代器可以这样:

typedef RBtree_Iterator<T, T&, T*> iterator;
typedef RBtree_Iterator<T, const T&, const T*> const_iterator;

3.1.2 == 和 !=

        bool operator ==(const self& n) const{return  n.node_ == node_;}bool operator !=(const self& n) const{return n.node_ != node_;}

这个比较简单,唯一有点需要理解的就是 self:
typedef RBtree_Iterator<T, Ref, Ptr> self;

这就是 迭代器 本身,所以 == ,!= 比较的是 两迭代器是否相等。

3.1.3 ++ 和 - -

self& operator++(){/// 如果右树不为空,那么下一个位置就是右树的最左节点if (node_->right_ != nullptr){Node* min = node_->right_;while (min->left_){min = min->left_;}node_ = min;}// 如果右树为空,那么有两种情况else{Node* cur = node_;Node* parent = cur->parents_;while (parent && cur == parent->right_){cur = parent;parent = cur->parents_;}node_ = parent;}return *this;}self& operator--(){/// 如果左树不为空,那么下一个位置就是左树的最右节点if (node_->left_ != nullptr){Node* max = node_->left_;while (max->right_){max=max->right_;}node_ = max;}// 如果左树为空,那么有两种情况else{Node* cur = node_;Node* parent = cur->parents_;while (parent && cur == parent->left_){cur = parent;parent = cur->parents_;}node_ = parent;}return *this;}

这是实现这个迭代器比较难的部分,当然应该都知道,map和set迭代器走的是中序,所以这里也得按照中序的规则,来进行迭代器的++ 、- -。

中序就是 左子树 根 右子树.

我们通过画图,来理解 :
在这里插入图片描述

先是 ++ :
(1) 如果当下节点的右树存在,那么 此节点的下一个节点就是 其右树的最左节点:

比如当下节点 是 6节点,那么它的右子树还存在,但是右子树的左子树 不存在,所以右子树就是它的下一个节点。

比如当下节点是 8节点,那么它的右子树还存在,右子树的最左节点是10节点,所以它的下一个节点是10节点。
(2) 如果当下节点的右树不存在,那么 此节点的下一个节点就是 孩子在父亲的左边的 第一个祖先

这句话有点绕口,不过 看例子也能明白:

比如 5节点 它的右树为空 ,它下一个节点 就是它的父亲,因为它在 父亲的左。
比如 7节点 它的右树为空,它的下一个节点是谁?是它的父亲吗?

7节点的下一个节点是 8节点,8节点是它的祖先,并且7节点在它的左。代码实现的话,靠的是更新 当下节点,一直等到 祖先为空,或者第一次出现 当下节点在父亲节点的左。

        self& operator++(){/// 如果右树不为空,那么下一个位置就是右树的最左节点if (node_->right_ != nullptr){Node* min = node_->right_;while (min->left_){min = min->left_;}node_ = min;}// 如果右树为空,那么有两种情况else{Node* cur = node_;Node* parent = cur->parents_;while (parent && cur == parent->right_){// 往上更新cur = parent;parent = cur->parents_;}node_ = parent;}return *this;}

然后看 - -:

其实它逻辑就是上面 ++ 反一下,它也分两种情况:

(1) 如果当下节点的左树存在,那么 此节点的 上一个节点就是 其左树的最右节点
(2) 如果当下节点的右树不存在,那么 此节点的上一个节点就是 孩子在父亲的右边的 第一个祖先

        self& operator--(){/// 如果左树不为空,那么下一个位置就是左树的最右节点if (node_->left_ != nullptr){Node* max = node_->left_;while (max->right_){max=max->right_;}node_ = max;}// 如果左树为空,那么有两种情况else{Node* cur = node_;Node* parent = cur->parents_;while (parent && cur == parent->left_){cur = parent;parent = cur->parents_;}node_ = parent;}return *this;}

3.2 红黑树封装迭代器

红黑树对迭代器进行封装,也就是说 要利用上面实现的迭代器 搞点事情:

template<class K, class T,class keyofT>struct RBtree{typedef RBtree_node<T> Node;public:typedef RBtree_Iterator<T, T&, T*> iterator;typedef RBtree_Iterator<T, const T&, const T*> const_iterator;private:Node* _root;public:iterator begin(){Node* cur = _root;while(cur && cur->left_){cur = cur->left_;}return iterator(cur);}iterator end(){return iterator(nullptr);}public:RBtree():_root(nullptr){}~RBtree(){destroy(_root);_root = nullptr;}RBtree(const RBtree<K,T,keyofT>& n){_root = copy(n->_root);}RBtree<K,T,keyofT>& operator = (RBtree<K,T,keyofT> n){std::swap(_root,n._root);return *this;}public:void destroy(Node* root){if (root == nullptr){return ;}destroy(root->left_);destroy(root->right_);delete root;}Node* copy(Node* root){if (_root == nullptr){return nullptr;}Node* newnode = new Node(root->kv_);newnode->col_ = root->col_;newnode->left_ = copy(root->left_);newnode->right_ = copy(root->right_);if (newnode->left_){newnode->left_->parents_ = newnode;}if (newnode->right_){newnode->right_->parents_ = newnode;}return newnode;}iterator find(const K& key){Node* cur = _root;keyofT kot;while (cur){if (key > kot(cur->kv_)){cur = cur->right_;}else if (key > kot(cur->kv_)){cur = cur->left_;}else{return iterator(cur);}}return end();}std::pair<iterator,bool> insert(const T& node){if (_root == nullptr){_root = new Node(node);_root->col_ = Black;return std::make_pair(iterator(_root),true);}keyofT kot;Node* cur = _root;Node* parent = nullptr;while (cur){if (kot(node)> kot(cur->kv_)){parent = cur;cur = cur->right_;}else if (kot(node) < kot(cur->kv_)){parent = cur;cur = cur->left_;}else{return std::make_pair(iterator(cur), false);}}cur = new Node(node);Node* newnode = cur;cur->col_ = Red;if (kot(parent->kv_) > kot(cur->kv_)){parent->left_ = cur;cur->parents_ = parent;}else{parent->right_ = cur;cur->parents_ = parent;}while (parent && parent->col_ == Red){Node* grand = parent->parents_;/// 插入到左树if (parent == grand->left_){Node* uncle = grand->right_;/// 情况一if (uncle && uncle->col_ == Red){parent->col_ = Black;uncle->col_ = Black;grand->col_ = Red;/// 往上更新cur = grand;parent = cur->parents_;}/// 情况二 或情况三else{ 右单旋if (cur == parent->left_){revolov_R(grand);parent->col_ = Black;grand->col_ = Red;}else{revolov_L(parent);revolov_R(grand);cur->col_ = Black;grand->col_ = Red;}break;}}/// 插入到右树else{Node* uncle = grand->left_;/// 情况一if (uncle && uncle->col_ == Red){parent->col_ = Black;uncle->col_ = Black;grand->col_ = Red;/// 往上更新cur = grand;parent = cur->parents_;}else{ 左单旋if (cur == parent->right_){revolov_L(grand);parent->col_ = Black;grand->col_ = Red;}else{revolov_R(parent);revolov_L(grand);cur->col_ = Black;grand->col_ = Red;}break;}}}_root->col_ = Black;return std::make_pair(iterator(newnode),true);}};

在这里插入图片描述
这里 typedef 两个版本的迭代器。

3.2.1 修改一下insert

首先,用过map和set中的insert的朋友,应该知道 insert的返回类型是一个 pair<>,我之前的博客也有详细讲过,所以我们的insert也不例外,返回类型 pair<iterator,bool>

然后需要改动的地方就是 之前插入都是直接进行比较的那种,但是 set和map 的比较方式是不一样的,set是用 key直接比,map是用 pair<first,second>中的first来比,所以需要利用第三个模板仿函数来取中要比较的值。

定义一个 仿函数对象:

在这里插入图片描述
比较的时候:
if (kot(node)> kot(cur->kv_))
else if (kot(node) < kot(cur->kv_))

最后我们控制一下返回值:

  • 在空树里面插入:
    return std::make_pair(iterator(_root),true);
  • 插入相同key的节点
    return std::make_pair(iterator(cur), false);
    -插入成功
    return std::make_pair(iterator(newnode),true);
std::pair<iterator,bool> insert(const T& node){if (_root == nullptr){_root = new Node(node);_root->col_ = Black;return std::make_pair(iterator(_root),true);}keyofT kot;Node* cur = _root;Node* parent = nullptr;while (cur){if (kot(node)> kot(cur->kv_)){parent = cur;cur = cur->right_;}else if (kot(node) < kot(cur->kv_)){parent = cur;cur = cur->left_;}else{return std::make_pair(iterator(cur), false);}}cur = new Node(node);Node* newnode = cur;if (kot(parent->kv_) > kot(cur->kv_)){parent->left_ = cur;cur->parents_ = parent;}else{parent->right_ = cur;cur->parents_ = parent;}while (parent && parent->col_ == Red){Node* grand = parent->parents_;/// 插入到左树if (parent == grand->left_){Node* uncle = grand->right_;/// 情况一if (uncle && uncle->col_ == Red){parent->col_ = Black;uncle->col_ = Black;grand->col_ = Red;/// 往上更新cur = grand;parent = cur->parents_;}/// 情况二 或情况三else{ 右单旋if (cur == parent->left_){revolov_R(grand);parent->col_ = Black;grand->col_ = Red;}else{revolov_L(parent);revolov_R(grand);cur->col_ = Black;grand->col_ = Red;}break;}}/// 插入到右树else{Node* uncle = grand->left_;/// 情况一if (uncle && uncle->col_ == Red){parent->col_ = Black;uncle->col_ = Black;grand->col_ = Red;/// 往上更新cur = grand;parent = cur->parents_;}else{ 左单旋if (cur == parent->right_){revolov_L(grand);parent->col_ = Black;grand->col_ = Red;}else{revolov_R(parent);revolov_L(grand);cur->col_ = Black;grand->col_ = Red;}break;}}}_root->col_ = Black;return std::make_pair(iterator(newnode),true);}

3.2.2 迭代器的 begin(),end()

这里的begin(),就是中序遍历红黑树的第一个节点,也就是最左节点。
end(),给空的迭代器就好了。

        iterator begin(){Node* cur = _root;while(cur && cur->left_){cur = cur->left_;}return iterator(cur);}iterator end(){return iterator(nullptr);}

3.2.3 修改一下find函数,返回迭代器

       iterator find(const K& key){Node* cur = _root;keyofT kot;while (cur){if (key > kot(cur->kv_)){cur = cur->right_;}else if (key > kot(cur->kv_)){cur = cur->left_;}else{return iterator(cur);}}return end();}

这个简单的很,玩的写。

4. 红黑树继续完善

4.1 红黑树的拷贝构造和赋值重载

默认情况下,是浅拷贝,这完全不能用于 封装map和set,会出大问题。所以需要我们自己来写 。

       RBtree(const RBtree<K,T,keyofT>& n){_root = copy(n._root);}RBtree<K,T,keyofT>& operator = (RBtree<K,T,keyofT> n){std::swap(_root,n._root);return *this;}Node* copy(Node* root){if (root == nullptr){return nullptr;}Node* newnode = new Node(root->kv_);newnode->col_ = root->col_;newnode->left_ = copy(root->left_);newnode->right_ = copy(root->right_);if (newnode->left_){newnode->left_->parents_ = newnode;}if (newnode->right_){newnode->right_->parents_ = newnode;}return newnode;}

这里最关键的是理解 copy函数,用递归完成的。

走的是一个前序,如果root为空,就返回。不为空就以root的数据构造一个新的节点,这里就是深拷贝,然后 它的左树深拷贝root的左树,它的右树深拷贝root的右树。但是 有个问题:红黑树是一个三叉链,它的父子关系也需要维护,怎么才能链接上父亲呢?

它递归结束 返回的上一层的newnode 就是它的父亲,这里还得判断 是否为空。

画个图:

在这里插入图片描述
假如我要拷贝上面那颗树:

  1. 先是深拷贝根节点
    在这里插入图片描述
  2. 递归深拷贝它的左子树

在这里插入图片描述

  1. 继续往下递归左子树,其左子树为空,然后返回空。然后递归右子树发现也为空,返回空

在这里插入图片描述

  1. 然后检测其左右子树返回都是空,所以不需要维护父子关系,直接返回 这个节点

在这里插入图片描述
现在就返回到上一层了。

  1. 递归这一层的右子树,和上面情况一样

在这里插入图片描述

  1. 检查这一层的左子树和右子树,发现不为空,所以 管理父子关系。

在这里插入图片描述
也就是:

newnode->left_->parents_ = newnode;
newnode->right_->parents_ = newnode;

拷贝构造的话,直接调用 copy传入根就好了。

赋值重载的话,这里是有细节的,它是通过传参,这是形参的拷贝构造,然后交换一下 形参的root_ ,就完成了赋值,并且 出了函数调用结束后,它会释放形参,此时的形参中的数据是交换后的数据。


4.2 红黑树的析构函数

红黑树的析构,直接释放掉根节点 行吗?答案是不行,必须递归着 把所有节点都释放掉才可以。

        void destroy(Node* root){if (root == nullptr){return ;}destroy(root->left_);destroy(root->right_);delete root;}~RBtree(){destroy(_root);_root = nullptr;}

上面的递归删除,是一个后序。当然必须是后序。这个大家自行体会吧。


5. 封装红黑树的map和set

其实上文很重要,map和set的底层是红黑树,所以我们必须完善红黑树才能够支持map和set的一些操作。比如 迭代器,拷贝构造,赋值重载,析构等

就相当于 map和set的find,insert,迭代器都是对红黑树中的复用,下面就能很清楚的感觉到了。

5.1 map的模拟实现

template<class K,class V>class map{public:struct mapkofT{const K& operator()(const std::pair<K, V>& t){return t.first;}};typedef typename RBtree<K, std::pair<K, V>, mapkofT>::iterator iterator;iterator begin(){return t_.begin();}iterator end(){return t_.end();}std::pair<iterator,bool> insert(const std::pair<K,V> &i){return t_.insert(i);}iterator find(const K& key){return t_.find(key);}V& operator[](const K& key){auto i = t_.insert(std::make_pair(key, V()));return i.first->second;}private:RBtree<K, std::pair<K, V>,mapkofT> t_;};

对吧,真的比较简单,但是上面还有细节:
typedef typename RBtree<K, std::pair<K, V>, mapkofT>::iterator
在这里插入图片描述
必须加上 typename 否则就会报个错误:iterator 依赖名称不是类型。

这个报错说实话,开始我也一头雾水:
在这里插入图片描述
它的意思就是 不认为 RBtree<K, std::pair<K, V>, mapkofT>::iterator 是个类型,主要是为什么呢?还没来得及实例化,所以它不是个类型,必须得等到 实例化成功后,它才能算为类型。所以呢 加上个 typename ,告诉编译器,它是一个类型,先让我编译通过。

在这里插入图片描述

还有个[]的实现,我之前写了一篇博客,里面很详细的介绍了这个,但是 没有讲实现,现在模拟实现了一下,发现不是想象中的那么高级:

调用了insert,利用了它的返回值

        V& operator[](const K& key){auto i = t_.insert(std::make_pair(key, V()));return i.first->second;}

5.2 set的模拟实现

template<class K>class set{public:struct setketofT{const K& operator()(const K& k){return k;}};typedef typename RBtree<K, K, setketofT>::iterator itetator;// 迭代器itetator begin(){return t_.begin();}itetator end(){return t_.end();}std::pair<itetator, bool> insert(const K& key){return t_.insert(key);}itetator find(const K& key){return t_.find(key);}private:RBtree<K,K,setketofT> t_;};

6. 总结

有问题的私信,或评论。感觉有帮助的朋友,可以点一个小赞,支持一下。

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

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

相关文章

UE4技能系统GameplayAbilitySystem

注:本分享主要面向策划,重点介绍GAS框架的思想,以期拓展技能机制的设计思路,其中设计技术实现的部分,可参见: 在文中如果出现UE4中实现的注意事项,会用(UE_Note)标记。 https://blog.csdn.net/pirate310/article/details/106311256 GasShooter演示项目的示例文档。 ht…

【Flink 实战系列】如何给 Flink 任务设置合理的并行度?

如何给 Flink 任务设置合理的并行度? 背景介绍 最近看到很多朋友都在问这个问题,当我在开发 Flink 实时计算任务的时候,如何给每个算子设置合理的并行度呢?如果设置多了可能会出现资源浪费的情况,如果设置少了任务可能会出现反压,所以给 Flink 任务设置一个合理的并行度…

初识数据结构 堆(一)

初识数据结构 堆一. 堆的概念和性质1. 堆的概念2. 堆的性质3. 小题目练练手二. 代码实现以及堆的部分接口函数1. 结构体代码2. 初始化以及销毁3. 增加数据 &#xff08;大堆为例&#xff09;一. 堆的概念和性质 我们在上一篇博客介绍存储二叉树的两种方式 分别是顺序结构和链…

Docker-compose启动mysql

前提&#xff1a;安装docker-compose curl -L https://get.daocloud.io/docker/compose/releases/download/1.29.1/docker-compose-uname -s-uname -m > /usr/local/bin/docker-compose docker-compose.yml version: 3 services: mysql: image: mysql:5.7.22 container_n…

css flex布局 —— 项目属性 flex-shrink

定义 flex-shrink 属性定义了项目的收缩规则。 flex-shrink 主要处理当 flex 容器空间不足时候&#xff0c;单个元素的收缩比例。当父元素的宽度小于子元素宽度之和并且超出了父元素的宽度时&#xff0c;flex-shrink 就会按照一定的比例进行收缩&#xff1a;将子元素宽度之和…

django+pyecharts制作工单系统实时刷新可视化仪表盘并设置报表定时发送

目录 仪表盘整体项目文件夹结构 demo应用效果 demo应用 demo应用的sql语句 demo应用定义的查询mysql类 在demo/views.py文件中 demo应用部分完整代码 urls.py views.py index.html 没有模糊背景 bindex.html 有模糊背景 demo2应用 demo2应用效果 2,将demo和demo2应用结…

Servlet入门学习笔记

目录 一、前置知识&#xff1a;Maven &#x1f34e;初识Maven &#x1f34e;Maven的使用 二、Servlet &#x1f351; 第一个Servlet程序&#xff1a;hello world 1、创建Maven项目 2、引入依赖 3、创建目录结构 4、编写servlet代码 5、打包 6、部署 7、验证程序 &a…

【Python】Python下载及安装(windows系统)

Python下载及安装&#xff08;windows系统&#xff09;下载安装包安装程序配置PATH其他问题下载安装包 浏览器访问下载地址&#xff0c;下载windows的最新版本 安装程序 双击程序安装 1、立即安装&#xff0c;会直接在下面的安装路径下安装&#xff0c;默认C盘 2、自定义安装…

Day7——四数相加||、赎金信、三数之和、四数之和

算法训练的第七天 目录 前言 一、四数相加|| 暴力解法思路&#xff1a; 哈希解法思路&#xff1a; 二、赎金信 解题思路&#xff1a; 三、三数之和 解题思路&#xff1a; 四、四数之和&#xff1a; 解题思路&#xff1a; 总结 前言 今日文案&#xff1a; 许多事情看…

在哪能查到英文论文?

不论是撰写英文论文还是引用外文文献&#xff0c;写论文的过程中想必缺不了检索合适的英文论文这个步骤&#xff0c;在本篇内容里&#xff0c;不仅教会你如何查到英文论文&#xff0c;还要教会你怎么样快速找到合适的英文论文&#xff01;听起来是不是令人心驰神往&#xff0c;…

facebook、Netflix 10倍速工程效能提升实践

工程效能是什么呢&#xff1f;工程效能是研发团队能够持续为用户产生有效价值的效率&#xff0c;包括有效性、效率和可持续性三个方面。一提到工程效能&#xff0c;大家脑子里马上会浮现持续构建、持续发布、开发流程改进等词汇&#xff0c;往往会忽略有效性。有效性&#xff0…

若依微服务项目本地启动

1.项目地址 https://gitee.com/y_project/RuoYi-Cloud 使用git本地克隆 git clone https://gitee.com/y_project/RuoYi-Cloud2.导入数据库 1.将下图的两个数据库导入ry-cloud数据库 2.导入nacos和seata的数据库里面有键数据库语句直接运行即可 3.下载nacos 1.下载地址 http…

05-运算符

文章目录算数运算符算数运算符执行的优先级顺序赋值运算符一元运算符自增运算符使用比较运算符逻辑运算符运算符优先级 *算数运算符 掌握算数运算符&#xff0c;能写出一些具备运算能力的小程序 数学运算符也叫算数运算符&#xff0c;主要包括加、减、乘、除、取余&#xff0…

ArcGIS中高风险地区热力图制作

一、数据来源及介绍 吉林省长春市中高风险地区名录 登陆微信&#xff0c;查找国家政务服务平台小程序&#xff0c;点击各地疫情风险等级查询&#xff0c;即可查看各地区中高风险地区所在地。 长春市行政边界数据 行政边界数据来源于阿里云数据可视化平台&#xff08;DataV…

后缀数组原理

一 点睛 在字符串处理中&#xff0c;后缀树和后缀数组都是非常有力的工具&#xff0c;后缀数组是后缀树的一个非常精巧的替代品&#xff0c;比后缀树更容易实现&#xff0c;可以实现后缀树的很多功能&#xff0c;时间复杂度也不逊色&#xff0c;比后缀树所占用的空间也小很多。…

0 引言和准备

14天阅读挑战赛 努力是为了不平庸&#xff01;这句话可能有些道理 本文概要&#xff1a; 本专栏是想挑战下阅读《趣味算法》一书&#xff1b; 本文主要是开读前&#xff0c;记录一下对本书的理解&#xff0c;和设定一个计划目标。同时&#xff0c;也简单总结了下&#xff0c;对…

DES加密原理描述与分析

目录1.简介2.加密原理2.1 加密步骤2.2 子密钥生成3.解密原理4.安全性5. 3DES 1.简介数据加密标准(英语:Data Encryption Standard,缩写为 DES)是一种对称密钥加密块密码算法,1976年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),随后在国际上广泛流传开来。…

【linux】 第4回 Xshell安装操作

1. 虚拟机关键配置名词解释 1. 虚拟⽹络编辑器说明桥接模式(可以访问互联⽹!!!)配置的地址信息和物理主机⽹段地址信息相同, 容易造成地址冲突NAT模式(可以访问互联⽹!!!)配置的地址信息和物理主机⽹段地址信息不同, 造成不了地址冲突仅主机模式 (不可以访问互联⽹)获取…

GIS Office国产基础软件,助力移动通信基础资源管理建设工程

万物互联&#xff0c;移动5G时代的蓬勃发展&#xff0c;为我们带来高速率、低时延、大连接的网络与通信体验&#xff0c;这离不开移动通信的基础资源管理建设工程。 面对种类繁多、设备资源管理要求极高且庞大的设备量&#xff0c;如何建立一个简单、高效的设备管理流程&#x…

AWS云服务器申请

目录 一、云服务器申请 &#xff08;一&#xff09;前言 &#xff08;二&#xff09;准备工作 &#xff08;三&#xff09;申请 &#xff08;四&#xff09;创建实例 &#xff08;五&#xff09;配置弹性IP &#xff08;六&#xff09;连接服务器实例 &#xff08;七&am…