【STL】模拟实现map和set {map和set的封装;核心结构;插入和查找;红黑树的迭代器;STL中的红黑树结构}

模拟实现map和set

  • map和set是红黑树的两种不同封装形式,底层使用同一颗泛型结构的红黑树,只是存储类型不同。
  • set是红黑树的K模型,存储key;map是红黑树的KV模型,存储pair<key,value>。

下面的代码和讲解着重体现红黑树的底层实现和map\set上层封装的衔接。红黑树的具体结构,基本操作,实现原理等内容请阅读下面几篇文章:

  1. 【高阶数据结构】二叉搜索树 {概念;实现:核心结构,增删查,默认成员函数;应用:K模型和KV模型;性能分析;相关练习}
  2. 【STL】map和set的介绍和使用 {关联式容器;键值对;map和set;multimap和multiset;OJ练习}
  3. 【高阶数据结构】AVL树 {概念及实现;节点的定义;插入并调整平衡因子;旋转操作:左单旋,右单旋,左右双旋,右左双旋;AVL树的验证及性能分析}
  4. 【高阶数据结构】红黑树 {概念及性质;红黑树节点的定义;红黑树插入操作详细解释;红黑树的验证}

一、核心结构

  • 问题一:map和set底层使用同一颗泛型结构的红黑树,如何处理map(pair<key,value>)和set(key)存储值不同的问题?

    解决方案:泛型底层红黑树的存储类型,通过不同的实例化参数,实现出map和set。

  • 问题二:在进行查找、插入、删除操作时,要对key值进行比较。在同一模版中,如何区别比较map和set中的key值?

    解决方案:通过传入仿函数KofT(KeyofTree)解决。如果是set,KofT对象返回data的值;如果是map,KofT对象返回data.first的值;

    注意:pair中重载了关系运算符,但first和second都参与运算,不符合要求。要求只比较pair.first (key)。

1.1 RBTreeNode && RBTree

// RBTree.hpp
enum Color{RED,BLACK
};template <class T>
struct RBTreeNode{RBTreeNode<T> *_left;RBTreeNode<T> *_right;RBTreeNode<T> *_parent;T _data; //泛型底层红黑树的存储类型,通过不同的实例化参数,实现出map和set。Color _color;RBTreeNode(const T &data = T(), Color color = RED):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_color(color){}
};//
// K: key的类型,
// T: 如果是map,则为pair<K, V>; 如果是set,则为K
// KofT: 通过T类型的data来获取key的一个仿函数类
template <class K, class T, class KofT>
class RBTree{typedef RBTreeNode<T> Node; //第二个模版参数T,决定红黑树的存储类型Node *_root = nullptr;//......
};

1.2 set & map封装

// Set.hpp
namespace zty{template <class K>class set{struct SetKofT{ //用于取出data中的keyconst K& operator()(const K &k){return k; } };typedef RBTree<K, K, SetKofT> RBT; //set是K模型的红黑树,只存储key值RBT _rbt;//......};
}//
// Map.hpp
namespace zty{template <class K, class V>class map{struct MapKofT{ //用于取出data中的keyconst K& operator()(const pair<K,V>& kv){return kv.first;}};typedef RBTree<K, pair<K,V>, MapKofT> RBT; //map是KV模型的红黑树,存储pair<K,V>键值对RBT _rbt;//......};
}

二、插入和查找

2.1 RBTree::insert

// RBTree.hpp
template <class K, class T, class KofT>
pair<typename RBTree<K,T,KofT>::iterator, bool> RBTree<K,T,KofT>::Insert(const T &data)
{KofT kot; //创建KofT对象,用于取出data中的keyif(_root == nullptr){_root = new Node(data, BLACK);return make_pair(iterator(_root), true); //返回pair<iterator, bool>,方便实现operator[]。}Node *cur = _root;Node *parent = nullptr;while(cur != nullptr){if(kot(data) > kot(cur->_data)) //不管是map还是set都能正确的取出key进行比较。{parent = cur;cur = cur->_right;}else if(kot(data) < kot(cur->_data)){parent  = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false);}}cur = new Node(data,RED);Node *newnode = cur; //在调整红黑树的过程中cur的指向会改变,所以要提前记录新节点的指针。if(kot(data) > kot(parent->_data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//上一次循环中grandparent 为根节点,此次循环parent == nullptrwhile(parent != nullptr && parent->_color == RED) {//......具体内容请参考红黑树章节内容} //end of whileif(cur == _root)cur->_color = BLACK;return make_pair(iterator(newnode), true); //返回pair<iterator, bool>,方便实现operator[]。
}

2.2 RBTree::find

//RBTree.hpp
template <class K, class T, class KofT>
typename RBTree<K,T,KofT>::iterator RBTree<K,T,KofT>::Find(const K &k){KofT kot;if(_root == nullptr){return end();}Node *cur = _root;  while(cur != nullptr){if(k > kot(cur->_data)){cur = cur->_right;}else if(k < kot(cur->_data)){cur = cur->_left;}else{return cur;}}return end();
}

2.3 set & map封装

//Set.hpp
namespace zty{template <class K>class set{//......bool Insert(const K& k){return _rbt.Insert(k).second;}iterator Find(const K& k){return _rbt.Find(k);}};
}//
// Map.hpp
namespace zty{template <class K, class V>class map{//......pair<iterator, bool> Insert(const pair<K,V>& kv){return _rbt.Insert(kv);}V& operator[](const K& k){ //Insert返回pair<iterator, bool>,方便实现operator[]。pair<iterator, bool> ret = Insert(make_pair(k, V()));return ret.first->second;}iterator Find(const K& k){return _rbt.Find(k);}};
}

三、迭代器

问题三:map和set(红黑树)的迭代器如何实现?

在这里插入图片描述

  1. 红黑树的迭代器底层封装一个指向节点的指针,基本操作请参照list迭代器的实现。

  2. 红黑树迭代器的实现难点在于++和–操作。

  3. 通过二叉树的中序遍历规则得出:(中序:左子树,根,右子树)

    1. begin是中序遍历的第一个节点,即二叉树的最左(最小)节点。

    2. end是中序遍历最后一个节点的下一个位置(左闭右开),这里我们设为nullptr。

    3. ++操作:(中序:左子树,根,右子树)

      1. 如果当前节点的右子树不为空,++就是找右子树中序的第一个节点(最左节点)。

      2. 如果当前节点的右子树为空,++就是找孩子不是右节点的那个祖先

        提示:右子树为空或孩子是右节点,说明这棵子树已经遍历访问完了。

    4. –操作:和++相反(右子树,根,左子树)

      1. 如果当前节点的左子树不为空,–就是找左子树的最右节点。

      2. 如果当前节点的左子树为空,–就是找孩子不是左节点的那个祖先

        提示:左子树为空或孩子是左节点,说明这棵子树已经遍历访问完了。

3.1 RBT_iterator

// RBTree.hpp
template<class T, class Ref, class Ptr>
class RBT_iterator{typedef RBT_iterator<T, Ref, Ptr> iterator;typedef RBTreeNode<T> Node;Node *_pnode; //红黑树的迭代器底层封装一个指向节点的指针
public://基本操作请参照list迭代器的实现,不做过多解释RBT_iterator(Node *pnode = nullptr):_pnode(pnode){}Ref operator*() const{ return _pnode->_data;}Ptr operator->() const{return &_pnode->_data;}bool operator==(const iterator &it) const{return _pnode == it._pnode;}bool operator!=(const iterator &it) const{return _pnode != it._pnode;}//红黑树的迭代器的实现难点在于++和--操作iterator& operator++(){if(_pnode->_right != nullptr) //如果当前节点的右子树不为空,++就是找右子树中序的第一个节点(最左节点)。{Node *left = _pnode->_right;while(left->_left != nullptr){left= left->_left;}_pnode = left;}else{ //如果当前节点的右子树为空,++就是找孩子不是右节点的那个祖先。Node *parent = _pnode->_parent;Node *cur = _pnode;while(parent != nullptr && cur == parent->_right) //parent == nullptr表示遍历到尾{parent = parent->_parent;cur = cur->_parent;}_pnode = parent;}return *this;}iterator& operator--(){if(_pnode->_left != nullptr) //如果当前节点的左子树不为空,--就是找左子树的最右节点。{Node *right = _pnode->_left;while(right->_right != nullptr){right = right->_right;}_pnode = right;}else{ //如果当前节点的左子树为空,--就是找孩子不是左节点的那个祖先。Node *parent = _pnode->_parent;Node *cur = _pnode;while(parent != nullptr && cur == parent->_left) //parent == nullptr表示遍历到头{parent = parent->_parent;cur = cur->_parent;}_pnode = parent;}return *this;}
};//template <class K, class T, class KofT>
class RBTree{
//......
public:typedef RBT_iterator<T, T&, T*> iterator; //普通迭代器typedef RBT_iterator<T, const T&, const T*> const_iterator; //const迭代器iterator begin(){ //begin是中序遍历的第一个节点,即二叉树的最左(最小)节点。Node *left = _root;while(left != nullptr && left->_left != nullptr){left = left->_left;}return iterator(left);}iterator end(){return iterator(nullptr); //end是中序遍历最后一个节点的下一个位置(左闭右开),这里我们设为nullptr。}
};

3.2 set & map封装

// Set.hpp
namespace zty{template <class K>class set{//......typedef RBTree<K, K, SetKofT> RBT;   public:typedef typename RBT::iterator iterator; //set的迭代器类型typedef typename RBT::const_iterator const_iterator; //const迭代器iterator begin(){return _rbt.begin();}iterator end(){return _rbt.end();}};
}//
// Map.hpp
namespace zty{template <class K, class V>//......typedef RBTree<K, pair<K,V>, MapKofT> RBT;public:typedef typename RBT::iterator iterator; //map的迭代器类型typedef typename RBT::const_iterator const_iterator; //const迭代器iterator begin(){return _rbt.begin();}iterator end(){return _rbt.end();}};
}

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

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

相关文章

【漏洞复现】网御ACM上网行为管理系统bottomframe.cgi接口存在SQL注入漏洞

漏洞描述 网御上网行为管理系统(简称Leadsec ACM)是网御为互联网接入用户在信息内容安全、网络应用管理、组织运营效率、网络资源利用、法律风险规避及网络投资回报等方面提供的全方位解决方案。网御上网行为管理系统存在SQL注入漏洞。 网御 ACM上网行为管理系统 bottomfram…

python实现pdf双页文档转png图片,png图片裁剪为左右两等分,再合并为新的pdf单页文档

一、问题引入 现有pdf双页文档如下&#xff1a; 现按照以下页码次序对pdf双页文档进行裁剪和拼接&#xff0c;其中有两点需要特别注意&#xff0c;一是封面页只裁剪中间部分&#xff0c;二是文档是从右往左的顺序排版的 二、python程序 import os import office from PIL …

Android中的view绘制流程,简单理解

简单理解 Android中的View类代表用户界面中基本的构建块。一个View在屏幕中占据一个矩形区域、并且负责绘制和事件处理。View是所有widgets的基础类&#xff0c;widgets是我们通常用于创建和用户交互的组件&#xff0c;比如按钮、文本输入框等等。子类ViewGroup是所有布局&…

【网络安全带你练爬虫-100练】第23练:文件内容的删除+写入

目录 0x00 前言&#xff1a; 0x02 解决&#xff1a; 0x00 前言&#xff1a; 本篇博文可能会有一点点的超级呆 0x02 解决&#xff1a; 你是不是也会想&#xff1a; 使用pyrhon将指定文件夹位置里面的1.txt中数据全部删除以后---->然后再将参数req_text的值写入到1.txt …

HDFS HA 高可用集群搭建详细图文教程

目录 一、高可用&#xff08;HA&#xff09;的背景知识 1.1 单点故障 1.2 如何解决单点故障 1.2.1 主备集群 1.2.2 Active、Standby 1.2.3 高可用 1.2.4 集群可用性评判标准&#xff08;x 个 9&#xff09; 1.3 HA 系统设计核心问题 1.3.1 脑裂问题 1.3.2 数据状…

Pytorch从零开始实战01

Pytorch从零开始实战——MNIST手写数字识别 文章目录 Pytorch从零开始实战——MNIST手写数字识别环境准备数据集模型选择模型训练可视化展示 环境准备 本系列基于Jupyter notebook&#xff0c;使用Python3.7.12&#xff0c;Pytorch1.7.0cu110&#xff0c;torchvision0.8.0&…

【二等奖方案】大规模金融图数据中异常风险行为模式挖掘赛题「冀科数字」解题思路

第十届CCF大数据与计算智能大赛&#xff08;2022 CCF BDCI&#xff09;已圆满结束&#xff0c;大赛官方竞赛平台DataFountain&#xff08;简称DF平台&#xff09;正在陆续释出各赛题获奖队伍的方案思路&#xff0c;欢迎广大数据科学家交流讨论。 本方案为【大规模金融图数据中…

Github 下载指定文件夹(git sparse-checkout)

比如要下载这里的 data_utils 步骤 1、新建空文件夹&#xff0c;并进入新建的空文件夹。 2、git init 初始化 3、git remote add origin 添加远程仓库 4、git config core.sparsecheckout true 允许稀疏检出 5、git sparse-checkout set 设置需要拉取的文件夹&#xff08;可…

Docker Desktop 设置镜像环境变量

点击run 展开Optional settings container name &#xff1a;容器名称 Ports&#xff1a;根据你需要的端口进行输入&#xff0c;不输入则默认 后面这个 比如我这个 5432 Volumes&#xff1a;卷&#xff0c;也就是做持久化 需要docker 数据保存的地方 Environment variables…

解决C++ 遇笔试题输入[[1,2,3,...,],[5,6,...,],...,[3,1,2,...,]]问题

目录 0 引言1 思路2 测试结果3 完整代码4 总结 0 引言 现在面临找工作问题&#xff0c;做了几场笔试&#xff0c;遇到了一个比较棘手的题目就是题目输入形式如下&#xff1a; [ [3,1,1], [3,5,3], [3,2,1] ] 当时遇到这个问题还是比较慌的&#xff0c;主要是之前没有遇到这样的…

【STM32】锁存器

问题背景 在学习FSMC控制外部NOR存储器时&#xff0c;看到在NOR复用接口模式下&#xff0c;AD信号[15:0]是复用的。也就是说&#xff0c;若不使用锁存器:当NADV为低时&#xff0c;ADx(x0…15)上出现地址信号Ax&#xff0c;当NADV变高时&#xff0c;ADx上出现数据信号Dx。若使用…

9.3.3网络原理(网络层IP)

一.报文: 1.4位版本号:IPv4和IPv6(其它可能是实验室版本). 2.4位首部长度:和TCP一样,可变长,带选项,单位是4字节. 3.8位服务类型 4.16位总长度:IP报头 IP载荷 传输层是不知道载荷长度的,需要网络层来计算. IP报文 - IP报头 IP载荷 TCP报文 TCP载荷 IP载荷(TCP报文) …

Golang编写客户端SDK,并开源发布包到GitHub,供其他项目import使用

目录 编写客户端SDK&#xff0c;并开源发布包到GitHub1. 创建 GitHub 仓库2. 构建项目&#xff0c;编写代码Go 代码示例&#xff1a;项目目录结构展示&#xff1a; 3. 提交代码到 GitHub仓库4. 发布版本5. 现在其他人可以引用使用你的模块包了 编写客户端SDK&#xff0c;并开源…

Vue项目案例-头条新闻

目录 1.项目介绍 1.1项目功能 1.2数据接口 1.3设计思路 2.创建项目并安装依赖 2.1创建步骤 2.2工程目录结构 2.3配置文件代码 3.App主组件开发 3.1设计思路 3.2对应代码 4.共通组件开发 4.1设计思路 4.2对应代码 5.头条新闻组件开发 5.1设计思路 5.2对应代码 …

Xcode打包ipa文件,查看app包内文件

1、Xcode发布ipa文件前&#xff0c;在info中打开如下两个选项&#xff0c;即可在手机上查看app包名文件夹下的文件及数据。

postman9.12.汉化版(附有下载链接)

想用英文版本的可以直接点击下载最新版本 这里直接付上9.12.2版本的下载链接&#xff0c;如果大家要下载别的版本&#xff0c;可以直接修改链接里面的版本号即可 &#xff0c;下面是汉化包下载 链接&#xff1a;https://pan.baidu.com/s/1izK3HfqlfXJdq6KIYeJ2zw?pwdpetk 提…

【数据结构】2015统考真题 6

题目描述 【2015统考真题】求下面的带权图的最小&#xff08;代价&#xff09;生成树时&#xff0c;可能是Kruskal算法第2次选中但不是Prim算法&#xff08;从v4开始&#xff09;第2次选中的边是&#xff08;C&#xff09; A. (V1, V3) B. (V1, V4) C. (V2, V3) D. (V3, V4) …

【计算机组成 课程笔记】5.1 处理器的设计步骤

课程链接&#xff1a; 计算机组成_北京大学_中国大学MOOC(慕课) 5 - 1 - 501-处理器的设计步骤&#xff08;14-49--&#xff09;_哔哩哔哩_bilibili 处理器&#xff0c;或者说是CPU&#xff0c;是现代计算机中最为复杂的一个部件。不过先不要劝退&#xff0c;要设计一个简单但是…

如何检测勒索软件攻击

什么是勒索软件 勒索软件又称勒索病毒&#xff0c;是一种特殊的恶意软件&#xff0c;又被归类为“阻断访问式攻击”&#xff08;denial-of-access attack&#xff09;&#xff0c;与其他病毒最大的不同在于攻击方法以及中毒方式。 攻击方法&#xff1a;攻击它采用技术手段限制…

若依 MyBatis改为MyBatis-Plus

主要内容&#xff1a;升级成mybatis-plus&#xff0c;代码生成也是mybatis-plus版本 跟着我一步一步来&#xff0c;就可完成升级&#xff01; 检查&#xff1a;启动程序&#xff0c;先保证若依能启动 第一步&#xff1a;添加依赖 这里需要在两个地方添加&#xff0c;一个是最…