【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 红黑树的迭代器
迭代器的好处是可以方便遍历。如果想要给红黑树增加迭代器,需要考虑以前问题:
- 迭代器的框架如何实现,才能满足泛型编程的需求??
- STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,这里为了方便就给nullptr。
- 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:
- 首先最底层的节点结构体只使用一个模版参数
value
,用来表明储存什么数据类型,上层的红黑树通过什么value
就使用使用什么 - 红黑树这层主要使用
Key Value KeyOfValue
:- KEY:表示键值的类型,在Findj函数里有大用处(利用Key值来寻找是否存在)!!!
- Value:表示储存的数据类型
- KeyOfValue:这是一个仿函数,用来从Value取出Key值。
- map与set这层分别有
K V
和K
分别要提供给红黑树Key Value KeyOfValue
:- map:就传给红黑树
<K , pair<K,V> ...>
- set: 就传给红黑树
<K , K ...>
- map:就传给红黑树
这样实现了上层的map
与set
的模版参数并不一样,却可以使用同一个底层红黑树代码!!!十分巧妙!!!
我们按照源码改进我们的红黑树:
//-------------------------------------------
//---------------- 红黑树实现 -----------------
//-------------------------------------------
//-------- 适配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的封装
实现了红黑树的改进,接下来就简单了!
在上层操作我们只需要调用对应的底层代码,给予对应的模版参数就好了!!!
- map 要满足
K V
的模版参数的传入 - map 要实现一个仿函数用来取出Key
- map 类里要有一个底层红黑树类,传入对应的模版参数
<K , pair<const K , V> , MapOfValue>
(注意键值不可更改哦,所以使用pair<const K , V>) - 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 的封装
在上层操作我们只需要调用对应的底层代码,给予对应的模版参数就好了!!!
- set 要满足
K
的模版参数的传入 - set 要实现一个仿函数用来取出Key
- set 类里要有一个底层红黑树类,传入对应的模版参数
<K , const K , MapOfValue>
(注意键值不可更改哦,所以使用 const K ) - 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♪(・ω・)ノ谢谢阅读!!!
下一篇文章见!!!
相关文章:

【C++】从零开始map与set的封装
送给大家一句话: 今日的事情,尽心、尽意、尽力去做了,无论成绩如何,都应该高高兴兴地上床恬睡。 – 三毛 《亲爱的三毛》 🌃🌃🌃🌃🌃🌃🌃&#x…...
Python可以声明并赋值一个hash类型变量吗?
在Python中,不能直接声明一个变量为hash类型,因为Python是一种动态类型语言,不需要(也不能)在声明变量时指定其类型。变量的类型是根据赋给它的值自动推断的。 将一个哈希值(即一个整数)赋值给…...

苗情灾情监控系统—提高农业生产效率
TH-MQ2苗情灾情监控系统是一种用于监测农作物生长状况和灾情的设备,通过实时监测和数据分析,帮助农民及时了解作物生长情况,采取相应的管理措施,提高农业生产效率和降低生产成本。 该系统通常由多种传感器、摄像头、数据传输模块等…...
wpf自定义按钮样式
在WPF中,自定义按钮样式可以通过创建一个ControlTemplate来实现。以下是一个简单的自定义按钮样式的例子: 首先,在你的WPF项目资源字典中定义按钮的ControlTemplate。 <Window.Resources><ControlTemplate x:Key"CustomButto…...

Meme币总市值突破630亿美元 以太坊ETF获批意味着代币化资产“完全安全”
近日,数字货币市场再次掀起轩然大波。一方面,Meme币总市值突破了630亿美元,令人瞠目结舌;另一方面,以太坊ETF的获批也引发了市场的广泛关注,被视为代币化资产的“完全安全”标志。 Meme币总市值飙升 Meme币…...

MySQL数据库语法(二)
一、数据库的创建 创建数据库CRATE DATABASE语法:CREATE DATABASE [IF NOT EXISTS]数据库名;功能:用给定的名字创建一个数据库如果数据库已经存在,发生一个错误。查看创建数据库:SHOW CREATE DATABASE <数据库名>ÿ…...

Linux makefile
Linux makefile 用makefile去自动编译和删除静态库和动态库 在实际开发中,项目的源代码文件比较多,按类型、功能、模块分别存放在不同的目录和文件中,哪些文件需要先编译,那些文件后编译,那些文件需要重新编译…...
信息安全基础知识
信息安全基础知识 安全策略表达模型是一种对安全需求与安全策略的抽象概念表达,一般分为自主访问控制模型(HRU)和强制访问控制模型(BLP、Biba)IDS基本原理是通过分析网络行为(访问方式、访问量、与历史访问…...

【数据结构】链式二叉树(超详细)
文章目录 前言二叉树的链式结构二叉树的遍历方式二叉树的深度优先遍历前序遍历(先根遍历)中序遍历(中根遍历)后序遍历(后根遍历) 二叉树的广度优先遍历层序遍历 二叉树链式结构接口实现二叉树结点个数二叉树叶子结点个数二叉树的深度(高度)二叉树第k层结…...
排序题目:最小绝对差
文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题:最小绝对差 出处:1200. 最小绝对差 难度 2 级 题目描述 要求 给定整数数组 arr \texttt{arr} arr,其中每个元素都不相同&…...

沃飞携AE200真机亮相澳门,全方位赋能城市低空出行
5月22日-25日,第四届BEYOND国际科技创新博览会(BEYOND Expo 2024)在澳门盛大举行。吉利沃飞长空携旗下全自研产品AE200真机亮相,吸引了现场众多领导嘉宾以及媒体、观众的关注。 作为亚洲顶尖的年度科技盛会,本届BEYOND…...
判断当前系统是linux、windows还是MacOS (python)
在很多情况下,需要在python中获取当前系统的类型,用于判断是unix/windows/mac或者java虚拟机等,python中提供了os.name, sys.platform, platform.system等方式 sys sys.platform会返回当前系统平台的标识符ÿ…...
Minikube部署单节点Kubernetes
1.1 Minikube部署单节点K8s Minikube是由Kubernetes社区维护的单机版的Kubernetes集群,支持macOS, Linux, andWindows等多种操作系统平台,使用最新的官方stable版本,并支持Kubernetes的大部分功能,从基础的容器编排管理࿰…...

leetcode-顺时针旋转矩阵-111
题目要求 思路 1.假设现在有一个矩阵 123 456 789 2.我们可以根据19这个对角线将数据进行交换,得到矩阵 147 258 369 3.然后将矩阵每一行的数据再翻转,得到矩阵 741 852 963 代码实现 class Solution { public:vector<vector<int> > rot…...

解决GoLand无法Debug
goland 调试的的时候提示如下错误 WARNING: undefined behavior - version of Delve is too old for Go version 1.22.3 (maximum supported v 其实个原因是因为正在使用的Delve调试器版本太旧,无法兼容当前的Go语言版本1.22.3。Delve是Go语言的一个调试工具&#…...
云原生周刊:K8s 上的 gRPC 名称解析和负载平衡
开源项目推荐 Kraken Kraken 是一个基于 P2P 的 Docker 注册表,专注于可扩展性和可用性。它专为混合云环境中的 Docker 镜像管理、复制和分发而设计。借助可插拔的后端支持,Kraken 可以轻松集成到现有的 Docker 注册表设置中作为分发层。 E2E Framewo…...

从0开始回顾ElasticSearch
1 elasticsearch概述 1.1 elasticsearch简介 官网: https://www.elastic.co/ ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的…...
小阿轩yx-Shell编程之条件语句
小阿轩yx-Shell编程之条件语句 条件测试操作 Shell 环境根据命令执行后的返回状态值($?)来判断是否执行成功 返回值 为 0 时:表示成功否则(非 0 值)表示失败或异常 测试工具 test 命令,对特定条件进行…...

MyBatis-Plus 从入门到精通
MyBatis-Plus 从入门到精通 前言快速入门创建一个SpringBoot项目导入依赖配置数据库创建一个实体类创建一个mapper接口在SpringBoot启动类上配置mapper接口的扫描路径在数据库中创建表编写一个SpringBoot测试类 核心功能注解CRUD接口Mapper CRUD接口Service CRUD 接口条件构造器…...

爬虫利器Frida RPC入门——夜神模拟器环境篇
Frida是一款轻量级HOOK框架,可用于多平台上,例如android、windows、ios等。 frida分为两部分,服务端运行在目标机上,通过注入进程的方式来实现劫持应用函数,另一部分运行在系统机器上。frida上层接口支持js、python、…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...

android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...