【C++】从零开始map与set的封装

在这里插入图片描述
送给大家一句话:
今日的事情,尽心、尽意、尽力去做了,无论成绩如何,都应该高高兴兴地上床恬睡。 – 三毛 《亲爱的三毛》

🌃🌃🌃🌃🌃🌃🌃🌃🌃
🌏🌏🌏🌏🌏🌏🌏🌏🌏


从零开始map与set的封装

  • 1 前言
  • 2 红黑树的迭代器
  • 3 map与set的封装
    • 3.1 红黑树的改进
    • 3.2 map的封装
    • 3.3 set 的封装
  • 4 总结
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 前言

为了map与set 的封装,我们进行了非常充足的知识储备!!!

首先,为了了解map 与 set 的底层原理我们开始学习二叉搜索树,二叉搜索树在二叉树的基础上增添了:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
  • 注意通常二叉搜索树不会有相同的键值

这样可以在一定程度上满足高效搜索的需求,但是在极端的情况(单子树情况)其效率会下降到O(n)。因此就有了改进的二叉搜索树:AVL树和红黑树。他们都增加一些特性使其最大程度上近似平衡二叉树!

AVL 树 和 红黑树 都是在保持二叉搜索树基本性质的基础上,通过旋转和重新平衡等操作,确保树的高度保持在一个相对平衡的状态,从而保证了操作的时间复杂度始终为 O(logn)。它们的出现大大提高了二叉搜索树在实际应用中的性能和稳定性。

AVL树增加了以下特性:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1 / 0 / 1 )

在平衡因子超出要求就会进行旋转,旋转分为:右单旋 ,左单旋,左右双旋,右左双旋。

红黑树加入以下特性:

  • ⚠️每个节点要么是红色,要么是黑色。
  • ⚠️根节点必须是黑色的。
  • ⚠️如果一个节点是红色的,则它的两个子节点必须是黑色的。
  • ⚠️对于任意一个节点,从该节点到其所有后代叶子节点的简单路径上,必须包含相同数目的黑色节点。
  • ⚠️每个叶子节点都是黑色的。这里的叶子节点指的是为空的节点。

在不满足规则时也会进行旋转。但是旋转的频率比AVL树要少很多,红黑树是只是接近平衡,AVL树几乎就是平衡的!

map与set大多数情况是用来检索的工具,我们底层使用红黑树来完成map与set的封装。
进行封装之前,我们先来实现一个非常重要的东西:迭代器

2 红黑树的迭代器

迭代器的好处是可以方便遍历。如果想要给红黑树增加迭代器,需要考虑以前问题:

  1. 迭代器的框架如何实现,才能满足泛型编程的需求??
  2. STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,这里为了方便就给nullptr。
  3. operator++()与operator–()要如何实现?这里的++和–要满足中序遍历的顺序,就不能简单的进行指针的移动了!

接下来我们来逐个实现。
首先我们来搭建一下迭代器的框架

// 迭代器
//T 表示数据类型 Ref为引用 Ptr为指针
template<class T , class Ref , class Ptr>
struct _RBTreeIterator
{//为了方便调用,我们重命名一下typedef RBTreeNode<T> Node;typedef _RBTreeIterator<T, Ref, Ptr> Self;//内部是节点指针Node* _node;_RBTreeIterator(Node* node):_node(node){}//两种指向方式Ref operator*(){return _node->_data;}Ptr operator&(){return &_node->_data;}bool operator!= (const Self& s){return _node != s._node;}
};

接下来我们来实现++和–的操作。
中序遍历的顺序是先遍历左边再遍历当前节点最后是右子树。所以在跌迭代器指向当前节点的时候,说明当前节点的左子树已经遍历完了,如果++,就要去找右边的最左节点。如果没有右子树,说明该节点以下的部分已经遍历完了,接下来要去向上进行,找到是祖先左边的节点:

//迭代器的++ 中序遍历的顺序
Self& operator++()
{//首先,能访问到当前节点说明左子树的都已经访问过啦//所以就要分类讨论//如果右边有子树,就要去寻找右子树的最左节点if (_node->_right){Node* cur = _node->_right;while (cur->_left){cur = cur->_left;}_node = cur;}//如果右边没有子树了,说明该节点以下的子树都已遍历完,那么就要向上进行//找到祖先节点(注意祖先节点右边还没遍历)//此时也要进行分类讨论else{Node* cur = _node;Node* parent = _node->_parent;//_node == parent->_right//说明parent的节点已经访问过了while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}

–与++完全相反。

这样红黑树的迭代器就成功设置好了,我们的红黑树更加完美了!!!

实现了迭代器接下来我们就来实现map与set的封装

3 map与set的封装

3.1 红黑树的改进

我们先来看我们写的红黑树的节点代码:

// 节点结构体
template<class K, class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;color _col;RBTreeNode(pair<K, V> kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(Red){}};

可以发现,这个节点的设置是写死的,里面的数据就设置为了pair<K , V>。如果我们想实现set的封装还要在写一份红黑树代码,因为set的节点数据是K 。这样就太不优雅了!
为了更好实现map与set的封装,我们来看STL源码里是如何实现的吧!
在这里插入图片描述

可以看到STL源码中使用了非常巧妙的模版来支持我们上层的map与set:

  1. 首先最底层的节点结构体只使用一个模版参数value,用来表明储存什么数据类型,上层的红黑树通过什么value就使用使用什么
  2. 红黑树这层主要使用Key Value KeyOfValue:
    • KEY:表示键值的类型,在Findj函数里有大用处(利用Key值来寻找是否存在)!!!
    • Value:表示储存的数据类型
    • KeyOfValue:这是一个仿函数,用来从Value取出Key值。
  3. map与set这层分别有K VK分别要提供给红黑树Key Value KeyOfValue
    • map:就传给红黑树<K , pair<K,V> ...>
    • set: 就传给红黑树<K , K ...>

这样实现了上层的mapset的模版参数并不一样,却可以使用同一个底层红黑树代码!!!十分巧妙!!!

我们按照源码改进我们的红黑树:

//-------------------------------------------
//---------------- 红黑树实现 -----------------
//-------------------------------------------
//-------- 适配map 与 set 的进阶版本 -----------
//-------------------------------------------
#include<utility>
enum color
{Black,Red
};
// 节点结构体
// T在这里是 pair<key , value>
template<class T>
struct RBTreeNode
{	RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;color _col;RBTreeNode(T data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(Red){}};//适配map与set 的版本
// 迭代器
template<class T , class Ref , class Ptr>
struct _RBTreeIterator
{typedef RBTreeNode<T> Node;typedef _RBTreeIterator<T, Ref, Ptr> Self;Node* _node;_RBTreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator&(){return &_node->_data;}bool operator!= (const Self& s){return _node != s._node;}//迭代器的++ 中序遍历的顺序Self& operator++(){}Self& operator--(){}
};
//K 为键值 T 为储存的结构(pair<K ,V>) KeyOfValue 是取出Key的方式
template<class K, class T , class KeyOfValue>
class RBTree
{
public:typedef _RBTreeIterator<T, T&, T*> Iterator;typedef RBTreeNode<T> Node;Iterator begin(){Node* cur = _root;while (cur->_left){cur = cur->_left;}return Iterator(cur);}Iterator end(){return Iterator(nullptr);}//右单旋void RotateR(Node* parent){}//左右双旋void RotateLR(Node* parent){}//右左双旋void RotateRL(Node* parent){}//------------------//返回需要比较的值KeyOfValue kot;//------------------//插入函数	bool Insert(T data){}private:void _IsBalance(Node* root , int num){}bool Check(Node* root, int blackNum, const int refNum){}void _InOrder(Node* cur){}RBTreeNode<T>* _root = nullptr;
};

注意插入函数等里面的比较方式统一改成类似kot(data) < kot(node.data)的样子哦!!!因为map与set的取出key的方式不同!!!

3.2 map的封装

实现了红黑树的改进,接下来就简单了!
在上层操作我们只需要调用对应的底层代码,给予对应的模版参数就好了!!!

  1. map 要满足K V的模版参数的传入
  2. map 要实现一个仿函数用来取出Key
  3. map 类里要有一个底层红黑树类,传入对应的模版参数<K , pair<const K , V> , MapOfValue> (注意键值不可更改哦,所以使用pair<const K , V>)
  4. map 类里要实例化一个迭代器。只需要提供基本的begin()与end()接口(直接调用红黑树的就可以),剩下++ -- !+交给迭代器操作交给红黑树的迭代器。
//----------------------------------
//----------  MAP 的实现  -----------
//----------------------------------
#include"RBTree.h"
#include<utility>//层层递进,
//map 上层要提供 key value 键值对
//相应的要改造红黑树的代码 使其满足泛型编程
template<class K , class V>
class map 
{struct MapOfValue{const K& operator()(const pair<const K, V>& kv){return kv.first;}};
public:typedef typename RBTree<K, pair<const K, V>, MapOfValue>::Iterator iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}bool Insert(pair<const K, V> kv){return _t.Insert(kv);}void InOrder(){_t.InOrder();}
private://底层是红黑树//需要提供对应的键值 储存结构 比较方式RBTree<K, pair<const K, V>, MapOfValue > _t;
};

这样就实现了map 的封装!!!

3.3 set 的封装

在上层操作我们只需要调用对应的底层代码,给予对应的模版参数就好了!!!

  1. set 要满足K 的模版参数的传入
  2. set 要实现一个仿函数用来取出Key
  3. set 类里要有一个底层红黑树类,传入对应的模版参数<K , const K , MapOfValue> (注意键值不可更改哦,所以使用 const K )
  4. set 类里要实例化一个迭代器。只需要提供基本的begin()与end()接口(直接调用红黑树的就可以),剩下++ -- !+交给迭代器操作交给红黑树的迭代器。
//----------------------------------
//----------  SET 的实现  -----------
//----------------------------------#include"RBTree.h"
#include<utility>//层层递进,
//set 上层要提供 key 键值
//相应的要改造红黑树的代码 使其满足泛型编程template<class K>
class set
{struct SetOfValue{const K& operator()(const K& k){return k;}};
public:typedef typename RBTree<K, const K, SetOfValue>::Iterator iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}bool Insert(K kv){return _t.Insert(kv);}void InOrder(){_t.InOrder();}
private://底层是红黑树//需要提供对应的键值 储存结构 比较方式RBTree<K, K, SetOfValue > _t;
};

这样就实现了set的封装!!!

4 总结

通过近一周的学习,我们终于将map和set从零建立起来了,这里不仅需要二叉搜索树的知识还需要AVL树和红黑树的使用!!!甚至还需要对于模版的更深理解!!!

我们写完了发现上层的map和set并没有使用多少代码,大部分是调用底层的代码,所以只有根基稳固才能走到更远!!!

map和set的封装是很值得回味的内容!!!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

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

相关文章

逻辑分析仪的调试使用

调试软件下载&#xff1a;点击跳转 一、接线 逻辑分析仪 设备 GND --- GND CHX&#xff08;数据通道&#xff09; --- 通信引脚 二、数据采集 图中两个可以选择数字大小的地方分别表示 采样深度 &#xff08;10M Samples&a…

【linux】如何优雅的使用vim编辑器

基本指令 【linux】详解linux基本指令-CSDN博客 【linux】详解linux基本指令-CSDN博客 vim的基本概念 vim有很多模式&#xff0c;小编只介绍三种就能让大家玩转vim了&#xff0c; 分别是&#xff1a; 正常/普通/命令模式 插入模式 末行/底行模式 命令模式 控制屏幕光标的…

obsidian Excalidraw 更换字体 最新版 手写字体

背景 Excalidraw 是 obsidian 中最厉害的插件之一&#xff0c;长期霸占插件排行榜第一。以其强悍的性能和灵活的可塑性受到大家的喜爱&#xff0c;可默认的字体对中文并不友好&#xff0c;网上大多数教程要不是过时了&#xff0c;要不是错的&#xff0c;还有就是太复杂&#x…

二、OpenWebUI 使用(.Net8+SemanticKernel+Ollama)

OpenWebUI的github上安装部署已经很详细&#xff0c;直接照着敲命令即可 GitHub&#xff1a;https://github.com/open-webui/open-webui 一、使用配置 1、访问&#xff1a;http://Ip:3000&#xff0c;打开如下OpenWebUI界面。 2、先点击“注册”&#xff0c;注册一个管理员帐号…

信息化赋能:干部监督工作的创新与实践

随着信息技术的迅猛发展&#xff0c;信息化手段在干部监督工作中的应用越来越广泛&#xff0c;为提升监督工作的效率和精准度提供了有力支持。以下是如何利用信息化手段扎实推进干部监督工作的几点建议&#xff1a; 一、搭建信息化平台&#xff0c;实现数据统一管理 要扎实推…

springboot带颜色的日志输出

话不多说直接贴全部代码在作解释 <?xml version"1.0" encoding"utf-8"?> <configuration><property name"pattern" value"%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg %n"/><propert…

Xinstall地推效果大揭秘:洞察用户需求,创新营销策略不再是难题

在互联网流量红利逐渐衰退的今天&#xff0c;企业如何快速搭建起满足用户需求的运营体系&#xff0c;成为了亟待解决的问题。特别是在地推领域&#xff0c;如何在多变的互联网环境下&#xff0c;迅速、有效地触达用户&#xff0c;扩大目标用户基数和流量池&#xff0c;成为了企…

一文了解ai问答机器人:特点、应用、影响

很多人都听过ai问答机器人这个词&#xff0c;也许对于大部分人来说&#xff0c;对它的印象就是智能&#xff01;这是不可置疑的。你在生活中肯定也接触了不少的ai问答机器人。但是关于ai问答机器人&#xff0c;你是否了解它的特点、应用领域和对人类未来的影响呢&#xff1f;Lo…

棱镜七彩荣获2024数字中国创新大赛两大奖项!

5月24日&#xff0c;供应链成熟度与开源技术主题活动暨数字中国创新大赛信创赛道全国总决赛颁奖仪式在福建福州成功举办。棱镜七彩“FOSSEye产品”&#xff0c;凭借在国产化支持、供应链成熟度、核心技术、功能体系、以及作品前景等多个维度的优异表现&#xff0c;荣获2024数字…

Pytorch深度学习实践笔记8(b站刘二大人)

&#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;pytorch深度学习 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质就是极致重复! 《PyTorch深度学习实践》完结合集_哔哩哔哩_bilibi…

视频汇聚管理安防监控平台EasyCVR程序报错“create jwtSecret del server class:0xf98b6040”的原因排查与解决

国标GB28181协议EasyCVR安防视频监控平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;平台支持7*24小时实时高清视频监控&#xff0c;能同时播放多路监控视频流…

java调用科大讯飞在线语音合成API --内附完整项目

科大讯飞语音开放平台基础环境搭建 1.用户注册 注册科大讯飞开放平台账号 2.注册好后先创建一个自己的应用 创建完成后进入应用可以看到我们开发需要的三个参数&#xff1a;APPID&#xff0c;APISecret&#xff0c;APIKey 3.因为平台提供的SDK中只支持了简单的中英两种语言语音…

Redis 可视化工具 RedisInsight 的保姆级安装以及使用(最新)

Redis 可视化工具 RedisInsight 的保姆级安装以及使用 一、下载 RedisInsight二、安装 RedisInsight三、使用 RedisInsight四、新建 Redis 连接 一、下载 RedisInsight 官网 https://redis.io/insight/填写基本信息之后点击 DOWNLOAD 二、安装 RedisInsight 双击安装包 点击下一…

cad角度如何精确到0.1

可以通过更改角度精度的方式把角度的标注精确到小数点后几位&#xff0c;具体方法如下&#xff1a; 1、打开一个CAD文档&#xff0c;在文档中画一个角&#xff0c;如下图&#xff1a; 文章源自设计学徒自学网-https://www.sx1c.com/47920.html 2、给此角进行角度的标注&#…

Java锁的策略

White graces&#xff1a;个人主页 &#x1f649;专栏推荐:Java入门知识&#x1f649; &#x1f649; 内容推荐:<多线程案例(线程池)>&#x1f649; &#x1f439;今日诗词:"你我推心置腹, 岂能相负"&#x1f439; 目录 锁的策略 乐观锁和悲观锁 轻量级锁…

[数据集][目标检测]森林火灾检测数据集VOC+YOLO格式362张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;362 标注数量(xml文件个数)&#xff1a;362 标注数量(txt文件个数)&#xff1a;362 标注类别…

四川音盛佳云电子商务有限公司铸就抖音电商新高度

在数字经济的浪潮中&#xff0c;抖音电商以其独特的魅力迅速崛起&#xff0c;成为新时代消费潮流的引领者。四川音盛佳云电子商务有限公司&#xff0c;作为抖音电商领域的佼佼者&#xff0c;凭借专业的团队和创新的理念&#xff0c;致力于为广大消费者提供优质、便捷的购物体验…

和可被k整除的子数组 ---- 前缀和

题目链接 题目: 分析: 补充知识 1. 同余定理: (a-b) % p 0即a-b能被p整除, > a % p b % p 2. c, java中 [负数 % 正数] 的结果是负数, 想要得到正确结果 > (a%pp)%p这道题和<和为k的子数组>类似, 利用前缀和的思想, 计算以i结尾的所有子数组, 前缀和为sum[i] …

探索编程逻辑中的“卡特牛(continue)”魔法

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;卡特牛逻辑的魅力 二、卡特牛逻辑的解析 三、卡特牛逻辑的应用实例 …

sqlserver——查询(四)——连接查询

目录 一.连接查询 分类&#xff1a; 内连接&#xff1a; 1. select ... from A&#xff0c;B &#xff1b; 2. select ..from A&#xff0c;B where ..&#xff1b; 3.select ...,... from A join B on... 4. where 与 join...on 的区别 5. where位置的先后 导语&#xff1…