C++ Map Set的模拟实现

C++ Map Set的模拟实现

文章目录

  • 前言
  • 一、Map 和 Set是什么?
    • 1.Set
    • 2.Map
  • 二、困难点
    • 困难一、set和map中值的类型不同
    • 困难二、Map和Set中值不可修改
    • 困难三、红黑树中迭代器的++和--
      • 1.++
      • 2.- -
    • 困难四、map中[ ] 运算符重载的实现
      • 1.修改红黑树以及Map和Set中insert的返回值
        • 1.修改set
        • 2.修改Map
      • 2.[ ]的重载
  • 总结


前言

随着平衡二叉树和红黑树插入(旋转+变色)的实现,我开始进一步学习Map和Set的模拟实现。


一、Map 和 Set是什么?

1.Set

  1. set是按照一定次序存储元素的容器
  2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。
    set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
  3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行
    排序。
  4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对
    子集进行直接迭代。
  5. set在底层是用二叉搜索树(红黑树)实现的。

2.Map

  1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元
    素。
  2. 在map中, 键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:typedef pair<const key, T> value_type;
  3. 在内部,map中的元素总是按照键值key进行比较排序的。
  4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序
    对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
  5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
  6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))

二、困难点

困难一、set和map中值的类型不同

由于Set只是Key,Map是KV结构。但其底层都是用红黑树去实现的,那么我们该如何去构造这颗红黑树呢?

  1. 我们通过在红黑树中传三个模板参数来实现这个问题。
template<class K,class V,class Value>

这里的K为Map和Set中Key的类型,而V(真实存储值的地方)对于Map来说是一个pair类型,对于Set来说是Key,Value是一个类模板,它通过在类中重写()括号运算符来使红黑树拿到要比较的值。(Set为Key,Map也为Key)

  1. 对于Set来说
	template <class K>
class set
{
public:class SetOfVal{public:const K& operator()(const K& data){return data;}};
private:RBTree<K,K,SetOfVal> _t;
};

我们通过在SetOfVal类重写括号运算符,实现在红黑树中用K进行比较。

  1. 对于Map来说
template <class K,class V>
class map
{
public:class MapOfVal{public:const K& operator()(const pair<const K, V>& data){return data.first;}};
private:RBTree< K,pair<const K,V>,MapOfVal> _t ;
};

我们通过MapOfVal类重写括号运算符,实现在红黑树中用V.first进行比较。

困难二、Map和Set中值不可修改

  1. 对于Map来说,其Key值是不可修改的,而Value值是可以修改的。
    这里我们借鉴了Stl库,其巧妙的在构造V类型模板参数时,对key传了一个const来解决这个问题。
RBTree< K,pair<const K,V>,MapOfVal> _t ;
  1. 对于Set来说,其值也是不可修改的。
    如果允许修改,则就不能满足它是中序有序的了。
    这里通过定义迭代器来实现。
    它直接将const_iterator 定义为 iterator来保证不可修改。
typedef typename RBTree<K, K, SetOfVal>::const_iterator iterator;
typedef typename RBTree<K, K, SetOfVal>::const_iterator const_iterator;iterator begin()const
{return _t.begin();
}iterator end()const
{return _t.end();
}

注意这里我们在写普通迭代器的begin,end时,需要让变量加一个const(如果不加,则它会调用红黑树中普通迭代器的begin和end,从而报错)

困难三、红黑树中迭代器的++和–

首先我们规定begin返回这棵树中 中序遍历的第一个结点。
end返回空指针。
在这里插入图片描述

1.++

对于一个结点来说,++就是要找它在中序遍历中的下一个结点,我们令这个结点为*p。
两种情况

  1. 如果这个结点有右孩子,则p = 右子树中最小的那个结点。即右子树的最左边结点。
  2. 如果这个结点没有右孩子,则令parent = p->parent。
    如果 p为parent的左孩子,表示p这颗树已经遍历完成,则下一个应该为parent。
    如果p为parent的右孩子,则表示parent这颗子树已经全部遍历完成。
    令p = parent, parent = parent->parent ,继续向上遍历。(最后若根节点,则返回nullptr)
Self& operator++()
{if (_node->_right != nullptr){Node* p = _node->_right;while (p->_left != nullptr)p = p->_left;_node = p;return *this;}else{Node* parent = _node->_parent;while (parent != nullptr){if (parent->_left == _node){_node = parent;return *this;}else{_node = parent;parent = parent->_parent;}}_node = nullptr;return *this;}
}Self operator++(int)
{Self temp(_node);++*this;return temp;
}

2.- -

减减的逻辑和加加刚好是相反的。
去判断有没有左孩子,若有,则为左孩子的最右结点。
若没有,则取判断parent和p的关系。
在此不过多赘述。

Self& operator--()
{if (_node->_left != nullptr){Node* p = _node->_left;while (p->_right != nullptr)p = p->_right;_node = p;return *this;}else{Node* parent = _node->_parent;while (parent != nullptr){if (parent->_right == _node){_node = parent;return *this;}else{_node = parent;parent = parent->_parent;}}}
}Self operator--(int)
{Self temp(_node);--*this;return temp;
}

困难四、map中[ ] 运算符重载的实现

在这里插入图片描述
我们可知如何k已经存在于Map中,则返回它Value的引用。
如果不存在,我们可知需要将其先插入进去,然后返回它Value的引用。
而Insert函数的返回值是一个pair类型,其first为一个指向该元素的迭代器,second是一个bool类型,表示插入成功与否。
所以,我们需要先修改Insert的其返回值。

1.修改红黑树以及Map和Set中insert的返回值

1.修改set
pair<iterator,bool> insert(const K& data)
{pair<typename RBTree<K, K, SetOfVal>::iterator, bool> ret = _t.Insert(data);return pair<iterator, bool>(ret.first, ret.second);
}

这里需要注意,set中的iterator其实是一个const_iterator。
而红黑树返回的是一个正常的iterator,如果直接这样写会报错。
所以我们需要在迭代器中写一个拷贝构造函数。
其作用是

  1. 在我们需要正常迭代器时进行拷贝构造。
  2. 在我们需要const迭代器时用正常迭代器来初始化const迭代器。
typedef __RBTreeIterator<T, T*, T&> iterator;__RBTreeIterator(const iterator& it):_node(it._node)
{}

注意这里iterator直接用T,T*,T&构造的,所以它肯定是一个正常迭代器。

2.修改Map
pair<iterator,bool> insert(const pair<const K, V>& kv)
{return _t.Insert(kv);
}

直接调用即可

2.[ ]的重载

V& operator[](const  K& data )
{pair<iterator, bool> ret = _t.Insert(make_pair(data,V()));return ret.first->second;
}

注意map的插入是一个pair类型,所以对于Value我们传了一个默认值进去。

总结

以上就是Map和Set模拟实现中的主要问题所在。
完整版代码存放在Git-ee上:Map,Set模拟实现
本人小白一枚,有问题还望各位大佬指正!!!

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

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

相关文章

Three.js投射光线实现三维物体交互

<template><div id"webgl"></div> </template><script setup> import * as THREE from three //导入轨道控制器 import { OrbitControls } from three/examples/jsm/controls/OrbitControls // 导入 dat.gui import { GUI } from thre…

k8s v1.30 完整安装过程及CNI安装过程总结

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G技术研究。 博客内容主要围绕…

【ffmpeg命令入门】添加水印

文章目录 前言什么是水印&#xff1f;为什么要添加水印&#xff1f;ffmpeg添加水印添加图片水印添加文字水印基本使用方法drawtext的参数 总结 前言 在视频制作和编辑的过程中&#xff0c;添加水印是一个常见且重要的步骤。水印不仅可以保护版权&#xff0c;还能用于品牌宣传和…

使用LLaMA-Factory对Llama3-8B-Chinese-Chat进行微调

文章目录 模型及数据&#xff1a;模型下载数据 LLaMA-Factory启动拉取代码启动webui 模型训练数据导入数据预览设置模型路径配置参数及参数的保存开始训练 过程观察加载模型、对话模型导出、再次加载 模型及数据&#xff1a; 模型下载 使用基于中文数据训练过的 LLaMA3 8B 模…

同步状态的广播事件

定向活动广播 你可以直接将事件从一个状态广播到另一个状态&#xff0c;以同步同一图表中的并行&#xff08;AND&#xff09;状态。以下规则适用&#xff1a; 在事件广播期间&#xff0c;接收状态必须处于活动状态。 一个图表中的操作无法将事件广播到另一个图表的状态。 与无定…

大坝安全监测设备有哪些主要功能?

推荐型号&#xff1a;TH-WY1】大坝安全监测设备的主要功能包括以下几个方面&#xff1a; 1. **实时监测大坝的各项物理参数**&#xff1a;包括应变、位移、水位、流量等<sup>1</sup><sup>2</sup>。 2. **数据处理和分析**&#xff1a;对监测数据进行处…

[Javascript】前端面试基础3【每日学习并更新10】

Web开发中会话跟踪的方法有那些 cookiesessionurl重写隐藏inputip地址 JS基本数据类型 String&#xff1a;用于表示文本数据。Number&#xff1a;用于表示数值&#xff0c;包括整数和浮点数。BigInt&#xff1a;用于表示任意精度的整数。Boolean&#xff1a;用于表示逻辑值…

【React1】React概述、基本使用、脚手架、JSX、组件

文章目录 1. React基础1.1 React 概述1.1.1 什么是React1.1.2 React 的特点声明式基于组件学习一次,随处使用1.2 React 的基本使用1.2.1 React的安装1.2.2 React的使用1.2.3 React常用方法说明React.createElement()ReactDOM.render()1.3 React 脚手架的使用1.3.1 React 脚手架…

c生万物系列(封装)

为了对c语言进行封装&#xff0c;笔者参考了lw_oopc等开源库&#xff0c;决定使用宏对结构体进行封装。 先说一下大致思想&#xff1a;通过宏&#xff0c;结构体和文件来实现封装。 大概步骤&#xff1a;抽象出类-> 使用lw_oopc库进行封装->定义接口封装底层实现 ->…

【常见开源库的二次开发】基于openssl的加密与解密——SHA算法源码解析(六)

目录 一、SHA-1算法分析&#xff1a; 1.1 Merkle Tree可信树 1.2 源码实现&#xff1a; 1.3 哈希计算功能 1.4 两种算法的区别&#xff1a; 1.4.1 目的 1.4.2 实现机制 1.4.3 输出 1.4.4 应用场景&#xff1a; 1.4 运行演示&#xff1a; 二、SHA-2算法分析&#xff1a; 2.1哈…

责任链模式的应用与解析

目录 责任链模式责任链模式结构责任链模式适用场景责任链模式优缺点练手题目题目描述输入描述输出描述题解 责任链模式 责任链模式&#xff0c;亦称职责链模式、命令链&#xff0c;是一种行为设计模式&#xff0c;允许你将请求沿着处理者链进行发送。收到请求后&#xff0c;每…

26.8 Django多表操作

1. 表关系 表关系在数据库中指的是表与表之间的连接和依赖关系. 这种关系定义了数据如何在不同的表之间进行交互和关联, 是实现数据一致性和完整性的重要手段.1.1 关系分类 多表关系在数据库中通常分为以下几种类型: * 1. 一对一(One-to-One)关系: 在数据库中, 这种关系通常通…

模拟建造游戏:城市:天际线2(都市天际线2)中文免安装,解压即撸

《城市&#xff1a;天际线2》&#xff08;Cities: Skylines II&#xff09;是一款模拟经营游戏&#xff0c;由Colossal Order开发&#xff0c;Paradox Interactive发行。 下载地址&#xff1a;https://pan.quark.cn/s/84e69332ec3e 更多游戏&#xff1a;https://kdocs.cn/l/cuH…

spring 中的属性解析器 PropertyResolver

我们知道&#xff0c;在 spring 中可以使用占位符&#xff0c;格式如 "${}"&#xff0c;大括号中间放置待替换的占位符&#xff0c;待使用值时根据配置的属性解析器进行解析。但具体是如何操作的&#xff0c;且看本文来进行分析。 PropertyResolver 这是一个针对任…

基于Neo4j将知识图谱用于检索增强生成:Knowledge Graphs for RAG

Knowledge Graphs for RAG 本文是学习https://www.deeplearning.ai/short-courses/knowledge-graphs-rag/这门课的学习笔记。 What you’ll learn in this course Knowledge graphs are used in development to structure complex data relationships, drive intelligent sea…

GDAL访问HDFS集群中的数据

1.集群搭建 参考文章&#xff1a;hadoop2.10.0完全分布式集群搭建 HA(QJM)高可用集群搭建_hadoop 2.10 ha-CSDN博客 创建文件夹 hdfs dfs -mkdir -p hdfs://192.168.80.132:9000/test 开放权限 hdfs dfs -chmod -R 777 /test 上传文件 hadoop fs -put /home/wh/data/res…

卡夫卡(Kafka)框架详解:从背景到应用实践

卡夫卡&#xff08;Kafka&#xff09;框架详解&#xff1a;从背景到应用实践 引言 在大数据和分布式系统日益普及的今天&#xff0c;数据处理和消息传递成为了支撑复杂业务系统的关键基础设施。Apache Kafka&#xff0c;作为一个高性能的分布式消息队列系统&#xff0c;因其高…

CSS技巧专栏:一日一例 11 -纯CSS实现多彩渐变按钮系列特效

CSS技巧专栏&#xff1a;一日一例 11 -纯CSS实现多彩渐变按钮系列特效 本篇&#xff0c;推荐给你几个按钮&#xff0c;先看一下图片 本例图片 案例分析 这是一个系列的按钮&#xff0c;它们具有共同的特点&#xff1a; 底层按钮层&#xff0c;具有一个彩色的渐变边框&#…

第十四章 数据库

第十四章 数据库 14.1 引言 数据存储在传统上是使用单独的没有关联的文件&#xff0c;称为平面文件 14.1.1 定义 定义&#xff1a;数据库是一个组织内被应用程序使用的逻辑相一致的相关数据的集合 14.1.2 数据库的优点 数据库的优点&#xff1a; 冗余少避免数据的不一致…

docker产生日志过大优化

1、Docker容器启动后日志存放位置 #cat /var/lib/docker/containers/容器ID/容器ID-json.log #echo >/var/lib/docker/containers/容器ID/容器ID-json.log临时清除日志 注&#xff1a;echo一个空进去&#xff0c;不需要重启容器&#xff0c;但如果你直接删除这个日志&…