【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,一经查实,立即删除!

相关文章

WebRTC清晰度和流畅度

WebRTC清晰度和流畅度 flyfish WebRTC提供了4种模式DISABLED&#xff0c;MAINTAIN_FRAMERATE&#xff0c;MAINTAIN_RESOLUTION&#xff0c;BALANCED // Based on the spec in // https://w3c.github.io/webrtc-pc/#idl-def-rtcdegradationpreference. // These options are …

【漏洞复现】网御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 …

面试系列 - String字符串使用详解

String 类是 Java 中最常用的类之一&#xff0c;它用于表示和操作字符串。字符串是一系列字符的序列&#xff0c;可以包含字母、数字、符号等。在 Java 中&#xff0c;String 类是不可变的&#xff0c;这意味着一旦创建了字符串对象&#xff0c;它的内容就不能被修改。 一、常…

python学习--基本数据类型之字典

python中数据类型 第一类&#xff1a;不可变类型、静态数据类型、不支持增删改操作 数字&#xff08;number&#xff09;字符串&#xff08;string&#xff09;元组&#xff08;tuple&#xff09; 第二类&#xff1a;可变类型、动态数据类型、支持增删改操作 列表&#xff…

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

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

java 实现单例模式

单例模式是一种设计模式&#xff0c;用于确保一个类只有一个实例&#xff0c;并提供一种全局访问该实例的方式。在Java中&#xff0c;可以使用多种方式来实现单例模式&#xff0c;下面整理了几种常见的实现方式。 饿汉式单例模式&#xff08;Eager Initialization&#xff09;&…

深度强化学习算法的参数更新时机

深度强化学习算法的参数更新时机 深度强化学习中往往涉及到多个神经网络来拟合策略函数、值函数等&#xff0c;什么时候更新参数因算法而异&#xff0c;与具体算法架构/算法思想紧密相关。 算法参数更新时机架构DQN先收集一定经验&#xff0c;然后每步更新Off Policy Value-B…

day 6

在c和c中static用法 1.static修饰全局变量&#xff0c;默认初始化为0 2.static修饰局部变量&#xff0c;延长生命周期&#xff0c;不改变作用域 3.static修饰函数只能在当前文件使用 4.static修饰的全局变量变量不能被extern引用 5.static修饰的指针不能指向auto类型的地址…

数学建模--层次分析法(AHP)的Python实现

目录 1.算法流程简介 2.算法核心代码 3.算法效果展示 1.算法流程简介 """ AHP:层次分析法,层次分析法还是比较偏向于主观的判断的,所以在建模的时候尽可能不要去使用层次分析法 不过在某些创新的评价方法上,也是能够运用层次分析使得评价变得全面一些,有可…

【网络安全带你练爬虫-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;欢迎广大数据科学家交流讨论。 本方案为【大规模金融图数据中…

Android10 SystemUI系列(一)概述

一、前言 由于笔者之前负责过SystemUI,之前没有抽空把很多东西整理出来,趁着最近不太忙,就慢慢动手梳理一下,顺便把自己遇到的问题也整理一下,当然自己之前主要看的是android11 之后的源码。这次主要是Android10 的源码,当然原理大差不差,也算是自己沉淀一下了 二、Sy…

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;可…

面试问题记录一 --- C++(Qt方向)

以下是我于2023年6~7月间换工作时遇到的面试题目,有需要的小伙伴可以参考下。约100个题目。 1 C和C++的区别 1) 文件区别:C源文件后缀 .c;C++源文件后缀 .cpp 2) 返回值: C默认返回int型;C++ 若无返回值,必须指定为void 3) 参数列表:C默认接收多个…

zookeeper-3.6.4集群搭建

1、上传zookeeper安装包并解压 上传路径&#xff1a;/opt/software/ 解压路径&#xff1a;/opt/module/ 2、创建数据目录及日志目录 #数据目录&#xff1a;/data/zookeeper/data/ #3台机器创建存储目录&#xff1a; sudo mkdir -p /data/zookeeper/data#日志目录&#xff1a…

Docker Desktop 设置镜像环境变量

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

Mysql中having语句与where语句的用法与区别

分析&回答 我们在写sql语句的时候,经常会使用where语句,很少会用到having,其实在mysql中having子句也是设定条件的语句与where有相似之处但也有区别。having子句在查询过程中慢于聚合语句(sum,min,max,avg,count)。而where子句在查询过程中则快于聚合语句(sum,min,max,avg…