【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();}};
}

四、STL中的红黑树结构

  • 为了后续实现关联式容器简单,STL红黑树的实现中增加一个头结点,因为根节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的_parent域指向红黑树的根节点,_left域指向红黑树中最小的节点,_right域指向红黑树中最大的节点。

  • STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?答案是行不通的,因为对end()位置的迭代器进行–操作,必须要能找最后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置:

在这里插入图片描述

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

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

相关文章

Linux 进程基础概念-进程状态、进程构成、进程控制

目录 Linux 进程 进程基础概念 进程状态 进程的构成 进程控制 进程创建和终止 Linux 进程 参考&#xff1a; 「linux操作系统」进程的切换与控制到底有啥关系&#xff1f; - 知乎 (zhihu.com)&#xff0c;Linux进程解析_deep_explore的博客-CSDN博客&#xff0c;腾讯面试…

蓝桥杯打卡Day1

文章目录 全排列八皇后 一、全排列IO链接 本题思路:本题是一道经典的全排列问题&#xff0c;深度优先搜索即可解决。 #include <bits/stdc.h>constexpr int N10;std::string s; std::string ans; int n; bool st[N];void dfs(int u) {if(un){std::cout<<ans<…

mysql与msql2数据驱动

mysql基本使用 数据库操作&#xff08;DDL&#xff09; -- 数据考操作 -- 1.查询所有数据库 SHOW DATABASES;-- 2.选择数据库 USE learn_mysql;-- 3.当前正在使用的数据库 SELECT DATABASE();-- 4.创建数据库 CREATE DATABASE IF NOT EXISTS learn_mysql;-- 5.删除数据库 DRO…

《TCP/IP网络编程》阅读笔记--基于TCP的服务器端/客户端

目录 1--TCP/IP协议栈 2--TCP服务器端默认函数调用顺序 3--TCP客户端的默认函数调用顺序 4--Linux实现迭代回声服务器端/客户端 5--Windows实现迭代回声服务器端/客户端 6--TCP原理 7--Windows实现计算器服务器端/客户端 1--TCP/IP协议栈 TCP/IP协议栈共分 4 层&#xf…

聊聊如何玩转spring-boot-admin

前言 1、何为spring-boot-admin&#xff1f; Spring Boot Admin 是一个监控工具&#xff0c;旨在以良好且易于访问的方式可视化 Spring Boot Actuators 提供的信息 快速开始 如何搭建spring-boot-admin-server 1、在服务端项目的POM引入相应的GAV <dependency><grou…

kali 安装cpolar内网穿透实现 ssh 远程连接

文章目录 1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 简单几步通过cpolar 内网穿透软件实现ssh 远程连接kali! 1. 启动kali ssh 服务 默认新安装的kali系统会关闭ssh 连接服务,我们通…

接入 NVIDIA A100、吞吐量提高 10 倍!Milvus GPU 版本使用指南

Milvus 2.3 正式支持 NVIDIA A100&#xff01; 作为为数不多的支持 GPU 的向量数据库产品&#xff0c;Milvus 2.3 在吞吐量和低延迟方面都带来了显著的变化&#xff0c;尤其是与此前的 CPU 版本相比&#xff0c;不仅吞吐量提高了 10 倍&#xff0c;还能将延迟控制在极低的水准。…

【sgLazyTree】自定义组件:动态懒加载el-tree树节点数据,实现增删改、懒加载及局部数据刷新。

特性 可以自定义主键、配置选项支持预定义节点图标&#xff1a;folder文件夹|normal普通样式多个提示文本可以自定义支持动态接口增删改节点 sgLazyTree源码 <template><div :class"$options.name" v-loading"rootLoading"><div class&qu…

我使用的Vim插件

2023年9月5日&#xff0c;周二下午 为了方便以后还原自己的Vim插件配置&#xff0c;于是写这篇博客来记录一下 不定期更新 目录 语法检查Syntastic文件树The NERD tree自动补全括号auto-pairs超轻量级自动补全vim-auto-popmenu 我使用的插件管理器是vim-plug 语法检查Syntas…

Java学习笔记之----I/O(输入/输出)二

【今日】 孩儿立志出乡关&#xff0c;学不成名誓不还。 文件输入/输出流 程序运行期间&#xff0c;大部分数据都在内存中进行操作&#xff0c;当程序结束或关闭时&#xff0c;这些数据将消失。如果需要将数据永久保存&#xff0c;可使用文件输入/输出流与指定的文件建立连接&a…

GPT引领前沿与应用突破之GPT4科研实践技术与AI绘图

GPT对于每个科研人员已经成为不可或缺的辅助工具&#xff0c;不同的研究领域和项目具有不同的需求。如在科研编程、绘图领域&#xff1a;1、编程建议和示例代码: 无论你使用的编程语言是Python、R、MATLAB还是其他语言&#xff0c;都可以为你提供相关的代码示例。2、数据可视化…

操作系统强化认识之Shell编程学习与总结

目录 1.Shell的概述 2.Shell脚本入门 3.变量 3.1.系统预定义变量 3.2.自定义变量 3.3.特殊变量 4.运算符 5.条件判断 6.流程控制 6.1.if判断 6.2.case语句 6.3.for循环 6.4.while循环 7.read读取控制台输入 8.函数 8.1.系统函数 8.2.自定义函数 9.正则表示式入…

Python,如何安装lap,pip安装lap出现问题

Linux可以&#xff1a; pip install cpython pip install gitgit://github.com/gatagat/lap.gitwindows可以&#xff1a; 下载https://github.com/gatagat/lap 后解压&#xff0c; 安装pip install cpython 安装VS2019企业版&#xff1a; key BF8Y8-GN2QH-T84XB-QVY3B-RC4D…

Python 之 match 表达式

Python 从 3.10 版本开始增加了 match 语句&#xff0c;和其他语言常见的 switch 语句极其相似&#xff0c;但功能更加强大。 本文通过实例&#xff0c;了解下其用法。 基本的 match 语句 def http_code(status): match status: case 400 | 404 | 418: …

1801. 积压订单中的订单总数;1567. 乘积为正数的最长子数组长度;923. 三数之和的多种可能

1801. 积压订单中的订单总数 核心思想&#xff1a;维护一个最小堆sell和一个最大堆buy&#xff0c;然后模拟即可。 1567. 乘积为正数的最长子数组长度 核心思想:动态规划&#xff0c;z表示以当前数字num结尾的乘积为正的最长子数组长度&#xff0c;f表示以当前数字num结尾的乘…

C#循环定时上传数据,失败重传解决方案,数据库标识

有些时候我们需要定时的上传一些数据库的数据&#xff0c;在数据不完整的情况下可能上传失败&#xff0c;上传失败后我们需要定时在重新上传失败的数据&#xff0c;该怎么合理的制定解决方案呢&#xff1f;下面一起看一下&#xff1a; 当然本篇文章只是提供一个思路&#xff0…

postman json复杂数据的模拟

先设置路径 然后可以定义下边数据&#xff08;Key value&#xff09; 也可以不定义 看你的情况 [{"mac": "4C-77-66-19-50-65","addressPattern": "98jd","platform": "ios","registrationId": "…

android studio安卓模拟器高德SDK定位网络连接异常

背景 使用了高德SDK创建了一个 project, 下面是运行界面: 点击 "开始定位"按钮, 结果并没有返回定位信息, 而是报错了: 根据错误提示打开这个网址: https://lbs.amap.com/api/android-location-sdk/guide/utilities/errorcode, 并且找到错误码 4 的信息, 显示的是网…

【transformer】自注意力源码解读和复杂度计算

Self-attention A t t e n t i o n ( Q , K , V ) s o f t m a x ( Q K T d k ) V Attention(Q,K,V) softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)softmax(dk​ ​QKT​)V 其中&#xff0c; Q Q Q为查询向量&#xff0c; K K K和 V V V为键向量和值向量&#xff0c;…

油田钻井平台三维应急仿真系统降低事故发生概率

海上钻井是供海上作业人员进行生产作业或者其他活动使用的仿陆地区域&#xff0c;被称为“流动的国土”&#xff0c;主要由上部平台、下浮体(沉垫浮箱)和中部立柱三部分组成&#xff0c;平台上安装钻井、动力、通讯、导航等设备&#xff0c;以及安全救生和人员生活等设施&#…