当前位置: 首页 > article >正文

C++ set map

1.set和map是什么

set和map是 C++ STL 提供的容器,用于高效的查找数据,底层采用红黑树实现,其中set是Key模型,map是Key-Value模型

set和map的基本使用较为简单,这里不再叙述,直接进入实现环节

2.set和map的底层实现

2.1 节点的定义

既然set和map的底层采用红黑树,那就少不了我们之前实现过的红黑树,直接搬过来复用

但我们实现的红黑树是Key-Value模型,难道要针对Key模型再copy一份红黑树,将节点里面的值修改成Key吗?

STL 中采用模板的方式,只需要一份红黑树,就能完成Key和Key-Value模型的统一

既然如何,红黑树节点的定义中,数据就不能只是key或pair对象,而应当是泛型,外部传递什么类型,数据就是什么类型

template<typename T>
struct RBTreeNode
{RBTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Color _col;
};// set.h
template<typename K>
class set
{
private:RBTree<K, K> _tree;
};// map.h
template<typename K, typename V>
class map
{
private:RBTree<K, std::pair<K, V>> _tree;
};

2.2 set和map的设计

同时,插入数据时,插入的值也应当为修改,此时,数据的比较就有问题了,之前的比较都是针对单一的key或pair进行比较,但红黑树这层并不知道外部传递给我的是一个什么类型的数据,只有set/map层知道传递的数据类型是什么,该如何进行数据的比较?

这里的解决方法是使用仿函数,在set/map层传递一个仿函数给红黑树层,该仿函数的功能用于取出key值,如果是set,那就直接取出key值,如果是map,那就取出pair中的first

// set.h
template<typename K>
class set
{
private:struct SetKeyOfT{const K& operator()(const K& key){return key;}};private:RBTree<K, K, SetKeyOfT> _tree;
};// map.h
template<typename K, typename V>
class map
{
private:struct MapKeyOfT{const K& operator()(const std::pair<K, V>& kv){return kv.first;}};private:RBTree<K, std::pair<K, V>, MapKeyOfT> _tree;
};// RBTree.h
template<typename K, typename T, typename KeyOfT>
class RBTree
{
public:bool insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return true;}KeyOfT kot;Node* cur = _root, * parent = nullptr;while (cur){if (kot(data) < kot(cur->_data)){parent = cur;cur = cur->_left;}else if (kot(data) > kot(cur->_data)){parent = cur;cur = cur->_right;}else return false;}Node* newnode = new Node(data);if (kot(data) < kot(parent->_data)) parent->_left = newnode;else parent->_right = newnode;newnode->_parent = parent;// .......return true;}private:Node* _root = nullptr;
};

至此,set/map的整体框架建立完毕

set/map中,红黑树的定义时,还多传递了一个K类型,这样set有两个K类型,会不会很多余?

虽然插入数据不同的类型要采用不同的比较方式,但双方的查找操作都是根据key进行查找的,因此,多添加的K类型是为了方便map进行find操作的

2.3 迭代器

接下来,便是迭代器的设计了

在vector和list中,迭代器都是对指针的封装,再通过重载*和->运算符,达到屏蔽底层,上层统一遍历的效果

这里我们的设计思想也是一样的

template<typename T, typename Ref, typename Ptr>
struct __RBreeIterator
{typedef RBTreeNode<T> Node;typedef __RBreeIterator<T, Ref, Ptr> Self;__RBreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}Node* _node;
};

唯一不同的是对于红黑树,++操作该如何实现?

红黑树的遍历要采用中序遍历才有意义,左子树 根 右子树

遍历到到某一节点时,如果左子树不为空,那么要一直遍历,直到左子树为空

左子树遍历完了,接下来访问根

根访问完了,如果右子树不为空,下一个应该访问右子树的最左节点

如果右子树为空,表明当前子树已经访问完了,此时,如果父亲是祖父的右子树,表明父亲的那颗子树也访问完了,要一直向上返回,直到当前节点是其父亲的左子树

在这里插入图片描述

在这里插入图片描述

template<typename T, typename Ref, typename Ptr>
struct __RBreeIterator
{typedef RBTreeNode<T> Node;typedef __RBreeIterator<T, Ref, Ptr> Self;// ...Self& operator++(){if (_node->_right != nullptr){_node = _node->_right;while (_node->_left) _node = _node->_left;}else{Node* parent = _node->_parent;while (parent && _node == parent->_right){_node = parent;parent = parent->_parent;}_node = parent;}return *this;}Node* _node;
};

set和map在 STL 中是双向迭代器,意味着能迭代器不仅支持++操作,也支持–操作,而–操作无非反过来遍历

在这里插入图片描述

在这里插入图片描述

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

规定迭代器的begin为红黑树的最左节点,end为空

template<typename K, typename T, typename KeyOfT>
class RBTree
{
public:typedef RBTreeNode<T> Node;typedef __RBreeIterator<T, T&, T*> iterator;iterator begin(){Node* left = _root;while (left && left->_left) left = left->_left;return iterator(left);}iterator end(){return iterator(nullptr);}// ...
}

而set/map,无论是插入操作,还是迭代器,无非是对红黑树的接口的封装罢了

// set.h
template<typename K>
class set
{
private:struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, const K, SetKeyOfT>::iterator iterator;bool insert(const K& key){return _tree.insert(key);}iterator begin(){return _tree.begin();}iterator end(){return _tree.end();}private:RBTree<K, const K, SetKeyOfT> _tree;
};// map.h
template<typename K, typename V>
class map
{
private:struct MapKeyOfT{const K& operator()(const std::pair<K, V>& kv){return kv.first;}};public:typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::iterator iterator;bool insert(const std::pair<K, V>& kv){return _tree.insert(kv);}iterator begin(){return _tree.begin();}iterator end(){return _tree.end();}private:RBTree<K, std::pair<const K, V>, MapKeyOfT> _tree;
};

2.4 map中operator[]

STL 中map支持使用[]运算符进行value的修改,使用起来相当方便

实际上,其内部设计利用了insert操作,将insert的返回值修改为std::pair<iterator, bool>

使用[]时,会传递一个key值,希望拿到key对应value的引用,这样就能方便修改value

因此,可以在[]内部调用insert,如果key不存在则插入成功,返回新节点的迭代器 + true的pair对象,否则返回值中的pair的second为false,表示插入失败

template<typename K, typename V>
class map
{// ...
public:typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::iterator iterator;V& operator[](const K& key){std::pair<iterator, bool> ret = this->insert(std::make_pair(key, V()));return ret.first->second;}// ...
};

2.5 拷贝构造 + 赋值重载

set和map都只有RBTree一个成员,直接调用红黑树的拷贝构造和赋值重载即可

template<typename K, typename T, typename KeyOfT>
class RBTree
{
public:typedef RBTreeNode<T> Node;typedef __RBreeIterator<T, T&, T*> iterator;typedef __RBreeIterator<T, const T&, const T*> const_iterator;~RBTree(){this->destroy(_root);_root = nullptr;}RBTree() = default;RBTree(const RBTree<K, T, KeyOfT>& t){_root = this->create(t._root);_root->_parent = nullptr;}RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t){std::swap(_root, t._root);return *this;}// ...private:void destroy(Node* root){if (root == nullptr) return;destroy(root->_left);destroy(root->_right);delete root;root = nullptr;}Node* create(Node* root){if (root == nullptr) return nullptr;Node* newnode = new Node(root->_data);newnode->_col = root->_col;newnode->_left = create(root->_left);if (newnode->_left) newnode->_left->_parent = newnode;newnode->_right = create(root->_right);if (newnode->_right) newnode->_right->_parent = newnode;return newnode;}private:Node* _root = nullptr;
}

剩下的还有const迭代器和find的实现了,由于较为简单,不再详讲

**注意:**还有个细节,我们在传递红黑树的模板参数时,将第二个模板参数中的K加上了const

如果是set,那就是const K,如果是map,那就是pair<const K, V>,这是为了防止使用迭代器修改节点中的值,为什么不直接使用const迭代器呢?如果使用const迭代器,那么map中的Key和Value都不能修改,而map是要能支持修改Value的

2.6 测试

void Test_set()
{set<int> s;std::vector<int> v = { 1, 3, 2, 8, 11, 9, 4, 5, 7, 10, 6 };for (auto& e : v) s.insert(e);std::cout << "正向遍历: ";for (auto& e : s) std::cout << e << " ";std::cout << std::endl;std::cout << "反向遍历: ";set<int>::iterator it = s.find(11);while (it != s.end()){std::cout << *it << " ";--it;}std::cout << std::endl;std::cout << "拷贝构造: s2->";set<int> s2(s);for (auto& e : s2) std::cout << e << " ";std::cout << std::endl;std::cout << "赋值重载: ";set<int> s3(s);s3.insert(1);s3 = s;std::cout << "s3->";for (auto& e : s3) std::cout << e << " ";std::cout << "    s->";for (auto& e : s) std::cout << e << " ";
}

在这里插入图片描述

void Test_map()
{std::vector<std::string> v = { "apple", "tassel", "watermelon", "apple", "banana", "tassel", "orange", "apple" };map<std::string, int> m;for (auto& e : v) m[e] ++;for (auto& it : m){std::cout << it.first << " " << it.second << std::endl;}
}

在这里插入图片描述

相关文章:

C++ set map

1.set和map是什么 set和map是 C STL 提供的容器&#xff0c;用于高效的查找数据&#xff0c;底层采用红黑树实现&#xff0c;其中set是Key模型&#xff0c;map是Key-Value模型 set和map的基本使用较为简单&#xff0c;这里不再叙述&#xff0c;直接进入实现环节 2.set和map的…...

Spring AI Alibaba 对话记忆使用

一、对话记忆 (ChatMemory)简介 1、对话记忆介绍 ”大模型的对话记忆”这一概念&#xff0c;根植于人工智能与自然语言处理领域&#xff0c;特别是针对具有深度学习能力的大型语言模型而言&#xff0c;它指的是模型在与用户进行交互式对话过程中&#xff0c;能够追踪、理解并利…...

Ubuntu24.04 离线安装 MySQL8.0.41

一、环境准备 1.1 官方下载MySQL8.0.41 完整包 1.2 上传包 & 解压 上传包名称是&#xff1a;mysql-server_8.0.41-1ubuntu24.04_amd64.deb-bundle.tar # 切换到上传目录 cd /home/MySQL8 # 解压&#xff1a; tar -xvf mysql-server_8.0.41-1ubuntu24.04_amd64.deb-bundl…...

SOME/IP-SD -- 协议英文原文讲解10

前言 SOME/IP协议越来越多的用于汽车电子行业中&#xff0c;关于协议详细完全的中文资料却没有&#xff0c;所以我将结合工作经验并对照英文原版协议做一系列的文章。基本分三大块&#xff1a; 1. SOME/IP协议讲解 2. SOME/IP-SD协议讲解 3. python/C举例调试讲解 5.1.5 Non…...

Ubuntu上给AndroidStudio创建桌面图标

最近使用了Ubuntu开发了&#xff0c;默认的android studio没有桌面图标&#xff0c;还是很不方便&#xff0c;每次都要cd到bin目录启动studio.sh。 步骤1&#xff1a;cd /usr/share/applications linux系统里面&#xff0c;所有的应用启动入口都在 /usr/share/applications …...

简单视图函数

视图函数 文章目录 视图函数[toc]一、什么是视图函数二、简单视图函数三、返回错误视图 一、什么是视图函数 所谓视图函数&#xff08;简称视图&#xff09;&#xff0c;本质上就是一个Python函数&#xff0c;用于接收Web请求并且返回Web响应。Web响应可以包含很多类型&#x…...

Flutter 2025生态全景:从跨端到嵌入式开发的新机遇

一、技术演进&#xff1a;从"一次编写多端运行"到"全场景覆盖" 1.1 渲染引擎革命&#xff1a;Impeller 2.0的性能突破 // 启用Impeller的配置示例&#xff08;android/app/build.gradle&#xff09; def enableImpeller true android {defaultConfig {…...

【sylar-webserver】7 定时器模块

文章目录 设计知识点 设计 #mermaid-svg-RbjvgaHrVWa5mA9X {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-RbjvgaHrVWa5mA9X .error-icon{fill:#552222;}#mermaid-svg-RbjvgaHrVWa5mA9X .error-text{fill:#552222;s…...

蓝桥杯备考----》完全背包模板

其实这个完全背包的步骤和01背包也是差不多滴&#xff0c;不过他有一些优化是我们必须要说一说的 老样子&#xff0c;我们先定义一下状态表示 step1: f[i][j]表示从1到i个物品里选出体积不超过j的最大价值 step2:状态转移方程 写成一行就是 我们再写一下f[i][j-v[i]]的表达…...

小白入门机器学习概述

文章目录 一、引言二、机器学习的基础概念1. 机器学习的定义2. 机器学习的类型&#xff08;1&#xff09;监督学习&#xff08;Supervised Learning&#xff09;&#xff08;2&#xff09;无监督学习&#xff08;Unsupervised Learning&#xff09;&#xff08;3&#xff09;半…...

深入了解 MySQL 锁机制

MySQL作为一个常用的关系型数据库系统&#xff0c;其事务处理能力强大&#xff0c;并提供了丰富的锁机制以保障数据的一致性和并发操作的有效性。在多用户并发操作的环境中&#xff0c;锁是控制资源访问的重要工具。本文将详细介绍MySQL中锁的分类及其具体应用&#xff0c;包括…...

ubuntu的ubuntu--vg-ubuntu--lv磁盘扩容

在我们安装ubuntu时&#xff0c;如果选择的是自动分区&#xff0c;就会按照逻辑卷的形式来分区&#xff0c;并且只分配100G其余的并不会被分配&#xff0c;这对我们大多数情况来说都是不合理的&#xff0c;所以&#xff0c;如何扩充呢 下面以一个小的案例来说明如何扩充 问题…...

音视频开发---常用工具

一、VLC播放器 1. 简介 VLC多媒体播放器(最初命名为VideoLAN客户端)是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘、VCD影音光盘和各类流式协议。它也能作为unicast或multicast的流式服务器在IPv4或IPv6的高速连接下使用。 它融…...

9、tlm 事务交互通信

1、TLM&#xff08;Transaction-Level Modeling&#xff09; 是 SystemC 的高级建模方法&#xff0c;用于描述系统的通信行为&#xff0c;特别是在硬件设计和验证中。TLM 是 SystemC 的一部分&#xff0c;用于提高仿真的效率和抽象性。以下是 TLM 的核心知识以及关键概念。 2、…...

Kotlin 基础语法解析

详细的 Kotlin 基础语法解析&#xff0c;结合概念说明和实用场景&#xff1a; 一、变量与常量 1. 变量类型 val&#xff08;不可变变量&#xff09;&#xff1a;声明后不可重新赋值&#xff0c;类似 Java 的 final。 val name “Kotlin” // 类型自动推断为 String// name …...

MaxEnt模型进阶:基于R语言自动化与GIS空间建模的物种栖息地精准预测

生物多样性的空间分布规律及其对环境变化的响应机制&#xff0c;是生态学与地理学研究的前沿议题。在气候变化加剧和人类活动干扰的双重压力下&#xff0c;如何精准预测物种潜在分布范围、识别关键环境驱动因子&#xff0c;已成为制定生物保护策略的核心科学问题。物种分布模型…...

微软 GraphRAG 项目学习总结

微软2024年4月份发布了一篇《From Local to Global: A GraphRAG Approach to Query-Focused Summarization》&#xff08;GraphRAG&#xff1a;从局部到全局的查询式摘要方法&#xff09;论文&#xff0c;提出了一种名为GraphRAG的检索增强生成&#xff08;RAG&#xff09;方法…...

C# dataGridView 自动生成几行几列及手动输入整型字符

C# dataGridView生成12号4列的表格 private void Form1_Load(object sender, EventArgs e) {// 清除默认列dataGridView1.Columns.Clear();// 添加4列&#xff08;首列为序号列&#xff09;dataGridView1.Columns.Add("ColIndex", "序号");dataGridView1.…...

CSS层叠顺序

介绍 在 CSS 中&#xff0c;元素的层叠顺序决定了当多个元素重叠时&#xff08;跟布局没有完全的关系&#xff0c;也就是说层叠顺序只会在几个叠放元素上进行比较&#xff0c;而不会改变布局&#xff09;&#xff0c;哪个元素显示在最上面&#xff0c;哪个元素显示在最下面。 …...

QtAV入门

QtAV 是一个基于 FFmpeg 和 Qt 的高性能多媒体播放框架,提供强大的音视频解码、渲染和处理能力,适合开发跨平台的播放器、视频编辑和流媒体应用。 1. 核心功能 多格式支持 支持 H.264/H.265、VP9、AV1 等视频编码。 支持 MP3、AAC、Opus 等音频编码。 封装格式:MP4、MKV、…...

Day17 -实例:利用不同语言不同框架的特征 进行识别

前置&#xff1a;我们所需的web站点&#xff0c;都可以利用fofa去搜索&#xff0c;例如&#xff1a;app"flask"这样的语句去找对应的站点&#xff0c;找到后&#xff0c;我们模拟不知道是什么框架&#xff0c;再根据特征去判断它的框架。 ***利用工具可以再去结合大…...

Pycharm(八):字符串切片

一、字符串分片介绍 对操作的对象截取其中一部分的操作&#xff0c;比如想要获取字符串“888666qq.com前面的qq号的时候就可以用切片。 字符串、列表、元组都支持切片操作。 语法&#xff1a;字符串变量名 [起始:结束:步长] 口诀&#xff1a;切片其实很简单&#xff0c;只顾头来…...

leetcode 53.Maximum Subarray

分治法 //lSum表示[left,right]内以left为左端点的最大子段和 //rSum表示[left,right]内以right为右端点的最大字段和 //iSum表示[left,right]的区间和 int divide_conquer(int* nums,int left,int right,int *lSum,int *rSum,int *iSum){int maxSum;//表示[left,right]内的最…...

Mysql从入门到精通day5————子查询精讲

本文主要讲述子查询的几种方法&#xff0c;读者注意体会它们的不同场合的适用情况及功能&#xff0c;本篇文章也融入了小编实践过程遇到的坑&#xff0c;希望读者不要再踩坑 一.带IN关键字的子查询 in关键字可以检测结果集中是否存在某个特定的值&#xff0c;检测成功则执行外…...

虫洞数观系列二 | Python+MySQL高效封装:为pandas数据分析铺路

目录 系列文章 1. 引言 2. 常规写法mysql 3. 封装设计接口mysql 3.1dbname.py文件 3.1.1. 导入和基类定义 3.1.2. 具体表定义类 3.1.3. 表定义整合函数 3.1.4. 全局字典和测试代码 3.2mysql_dao文件 3.2.1. 模块导入与配置 3.2.2. 数据库连接池初始化 3.2.3. Comm…...

算法刷题-最近公共祖先-LCA

AcWing 1172 祖孙询问 一、题目描述 给定一棵包含 n 个节点的有根无向树&#xff0c;节点编号互不相同&#xff0c;但不一定是 1∼n。 有 m 个询问&#xff0c;每个询问给出了一对节点的编号 x 和 y&#xff0c;询问 x 与 y 的祖孙关系。 输入格式 第一行一个整数 n 表示节…...

MySQl之Binlog

前言 Binlog&#xff08;Binary Log&#xff09;是MySQL中至关重要的日志模块&#xff0c;它直接关系到数据恢复、主从复制等高阶架构设计。无论你是刚入门的新手还是有一定经验的开发者&#xff0c;掌握Binlog的原理和应用都是进阶的必经之路。 BinLog是什么&#xff1f; Bin…...

开源项目解读(https://github.com/zjunlp/DeepKE)

1.DeepKE 是一个开源的知识图谱抽取与构建工具&#xff0c;支持cnSchema、低资源、长篇章、多模态的知识抽取工具&#xff0c;可以基于PyTorch实现命名实体识别、关系抽取和属性抽取功能。同时为初学者提供了文档&#xff0c;在线演示, 论文, 演示文稿和海报。 2.下载对应的de…...

「MethodArgumentTypeMismatchException:前端传递 ‘undefined‘ 导致 Integer 类型转换失败」

遇到的问题&#xff1a; Failed to convert value of type java.lang.String to required type java.lang.Integer; nested exception is java.lang.NumberFormatException: For input string: "undefined" 原因分析&#xff1a; 大致意思就是我传递的参数到后端没…...

LabVIEW故障诊断数据处理方法

在LabVIEW故障诊断系统中&#xff0c;数据处理直接决定诊断的准确性和效率。工业现场常面临噪声干扰、数据量大、实时性要求高等挑战&#xff0c;需针对性地选择处理方法。本文结合电机故障诊断、轴承损伤检测等典型案例&#xff0c;详解数据预处理、特征提取、模式识别三大核心…...