【C++】关联容器探秘:Map与Multimap详解

目录

1.映射类 map

0. 引入 pair:

1.定义

2.插入

3. 遍历

4.❗operator[]的实现

5. 插入

 运用

2.Multimap 类

0. 引入:不去重的 Multi

1. Multimap 不支持 Operator[]

2. Multimap 的删除


1.映射类 map

0. 引入 pair:

在C++中,std::pair是一个非常有用的容器适配器,它属于C++标准模板库(STL)的一部分,主要用于存储两个相关联的数据项。std::pair的设计目的是为了方便地处理需要成对出现的数据,比如坐标点(x, y)、键值对(key, value)等。

std::pair<utility>头文件提供,它包含两个成员,分别是firstsecond,这两个成员可以是任意类型的组合。std::pair的声明语法如下:

#include <utility> // 包含std::pair的定义std::pair<Type1, Type2> myPair;

其中,Type1Type2是你想要存储的两种类型。

创建pair实例

可以通过构造函数直接初始化std::pair

std::pair<int, double> p1(1, 2.5);

也可以使用std::make_pair函数:

std::pair<int, double> p2 = std::make_pair(1, 2.5);

访问pair成员

std::pair的成员firstsecond可以直接访问

std::pair<int, double> p(1, 2.5);
int x = p.first;
double y = p.second;

pair的比较

std::pair可以进行比较,比较规则首先比较first成员,如果first相等,则比较second成员。这使得std::pair可以用于std::mapstd::set等容器中的键值对。

示例

下面是一个使用std::pair的简单示例:

#include <iostream>
#include <utility> // 包含std::pair的定义int main() {std::pair<std::string, int> studentGrade("Alice", 90);std::cout << "Student: " << studentGrade.first << ", Grade: " << studentGrade.second << std::endl;std::pair<std::string, int> studentGradeB("Student:Bob", 85);if (studentGradeA.second > studentGradeB.second) {std::cout << "Alice has a higher grade than Bob." << std::endl;
}return 0;
}

在这个示例中,我们创建了一个std::pair实例,存储了学生的名字和成绩,然后比较了两个学生的成绩。

1.定义

key 通常用于排序和唯一地标识元素,value 中存储与 key 关联的内容。

key 和 value 的类型可以不同,在 map 内部,key 和 value 通过成员类型 value_type 绑定

Compare: 比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)

Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器

2.插入

例如简单实现一个字典,我们可以有四种插入方法:

我们可以直接调用 pair 的构造函数来插入

可以用匿名对象的方式来写

调用神奇的 make_pair

直接用 { }

std::map<std::string, std::string> dict;std::pair<std::string, std::string> kv1("insert", "插入");// 传参的四种实现dict.insert(std::pair<std::string, std::string>("sort", "排序")); // 方法一dict.insert(kv1); // 方法二// C++98dict.insert(std::make_pair("string", "字符串")); // 方法三// C++11 多参数的构造函数隐式类型转换// 最常用的dict.insert({ "erase", "删除" }); // 方法四// 输出字典的内容以验证是否插入成功for (const auto& item : dict) {std::cout << "Key: " << item.first << ", Value: " << item.second << std::endl;}

运行:

对于方法四中存在的隐式类型转化:

// 隐式类型的转换string str1 = "hello";A aa1 = { 1, 2 };pair<string, string> kv2 = { "string", "字符串" };
}

3. 遍历

和其他容器的迭代器一样,加上 :: 小尾巴后就可以召唤出属于 map 的迭代器了:

map<string, string>::iterator it = dict.begin();
//有请auto
auto it=dict.begin();

返回值需要返回迭代器中节点的数据,节点的数据是 pair,可惜 pair 并不支持 (stream)

有问题就有解决方法:使用 it-> 中分别提取 first 和 second

//map<string, string>::iterator it = dict.begin();//迭代器返回pair指针auto it = dict.begin();while (it != dict.end()){//it->first = "xxx";//it->second = "xxx";//cout << (*it).first << ":" << (*it).second << endl;cout << it->first << ":" << it->second << endl;//结构体指针调用++it;}cout << endl;

map 同样支持甜甜的 范围 for 来遍历,这里建议加上 & 提效:

for (const auto& kv : dict){cout << kv.first << ":"<<kv.second<<endl;}
}

4.❗operator[]的实现

mapped_type& operator[] (const key_type& k) {pair<iterator, bool> ret = insert(make_pair(k, mapped_type()));// return (*(ret.first)).second;return ret.first->second;
}

判断 pair 的 first 就是 map 的迭代器即指针类型 , map 指针->second 实现计数

  • key 已经在树里面,返回 pair<树里面 key 所在节点的 iterator, false>
  • key 不在树里面,返回 pair <新插入 key 所在节点的 iterator, true>

5. 插入

注意:

// 不插入,不覆盖;插入过程中,只比较key,value是相同无所谓// key已经有了就不插入了dict.insert(make_pair("insert", "xxxx"));

测试:

void test_map4()
{map<string, string> dict;dict.insert(make_pair("string", "字符串"));dict.insert(make_pair("sort", "排序"));dict.insert(make_pair("insert", "插入"));cout << dict["sort"] << endl; // 查找和读dict["map"];                  // 插入,返回1dict["map"] = "映射,地图";     // 修改dict["insert"] = "xxx";       // 修改dict["set"] = "集合";         // 插入+修改
}

 运用

统计次数:例如说我们要计数如下动物:

"兔子", "大象", "兔子", "长颈鹿", "狮子", "猴子",
"大象", "兔子", "猴子", "猴子", "大象", "斑马",
"狮子", "猴子", "长颈鹿", "兔子", "斑马", "猴子"

我们可以这么写

map<string, int> count_map;for (auto& str : arr) {map<string, int>::iterator it = count_map.find(str);if (it != count_map.end()) {it->second++;}else {count_map.insert(make_pair(str, 1));}}

这里实际上可以让 insert 来优化一下

我们可以看到 insert 的返回值是一个 pair<iterator, bool> 类型。

插入成功, second 就是 true;如果插入失败, second 就是 false

既然如此,我们就可以这么去迭代:

for (auto& str : arr) {pair< map<string, int>::iterator, bool >ret= count_map.insert(make_pair(str, 1)); 
}

所以我们写一个判断,判断 second (bool) 是不是 false,如果是就让统计 次数++。

map<string, int> count_map;
for (auto& str : arr) {auto ret = count_map.insert(make_pair(str, 1)); if (ret.second == false) {ret.first->second++;}
}

实际运用中这两种方法我们几乎都不用,因为会有更好的方法,语言之父已经帮我们迭代过啦

for (auto& str : arr) {count_map[str]++;
}

根据上面我们可以知道,底层返回的实现

mapped_type& operator[] (const key_type& k) {pair<iterator, bool> ret = insert(make_pair(k, mapped_type()));// return (*(ret.first)).second;return ret.first->second;
}

如果是第一次出现,就先插入。插入成功后会返回插入的节点中的次数 0 的引用,对它 ++ 后变为 1。如果是第二次插入,插入失败,会返回它所在节点的迭代器的次数,再 ++


然后这个地方的使用不仅联想到了 hash ,讲个题外的知识点,将两者做个比较:

std::mapstd::unordered_map通常称为hash map)是C++标准库中提供的两种关联容器,它们的主要区别在于内部实现和性能特征上。

std::map

  • 内部实现std::map使用红黑树(一种自平衡的二叉搜索树)作为底层数据结构。这意味着它的键值对是有序的,按照键的自然顺序或由比较器定义的顺序排列。
  • 查找、插入和删除的时间复杂度:对于std::map,这些操作的时间复杂度为O(log n),其中n是容器中的元素数量。这是因为每次操作都需要在树中移动log n的距离
  • 迭代:由于std::map保持了键的排序,因此迭代访问元素时可以按照键的顺序进行,这对于需要排序访问的场景非常有用。

std::unordered_map

  • 内部实现std::unordered_map使用哈希表作为底层数据结构。它将键映射到哈希表中的位置,理想情况下,不同的键将映射到不同的位置,从而允许快速查找。
  • 查找、插入和删除的时间复杂度:平均而言,std::unordered_map的查找、插入和删除操作的时间复杂度为O(1),即常数时间。但在最坏的情况下,如果哈希冲突频繁发生,这些操作可能退化到线性时间复杂度O(n)
  • 迭代std::unordered_map不保证元素的顺序,因此迭代访问元素时,元素的顺序可能是任意的,这取决于哈希函数和哈希表的实现

总结

  • 当你需要键值对保持排序时,使用std::map
  • 当你需要快速查找而不关心键值对的顺序时,使用std::unordered_map
  • std::map在查找、插入和删除操作上的性能随着容器大小的增长而增长(对数时间复杂度),而std::unordered_map在平均情况下的性能通常是常数时间,但对于特定的输入数据可能会退化。

选择使用哪种容器应基于你的具体需求:是否需要排序、预期的性能、以及对最坏情况性能的考虑。


2.Multimap 类

0. 引入:不去重的 Multi
  • 背景:对于一词多义的情况,例如单词 "left" 可以表示 "左边" 和 "剩余",需要存储同一个键的多个值
  • 解决方案:使用 multimap 类,类似于 multiset,它允许存储具有相同键的多个值,但仍然保持排序。
void test_multimap() {multimap<string, string> dict;dict.insert(make_pair("left", "左边"));dict.insert(make_pair("left", "剩余"));
}
  • 特性:在 map 中,重复插入相同键的元素会导致失败,而在 multimap 中,相同键的多次插入均会成功
1. Multimap 不支持 Operator[]
  • 原因multimap 允许多个键值对拥有相同的键,因此它不支持通过 operator[] 直接访问元素,因为该操作符假定键是唯一的
int main() {multimap<int, std::string> myMultimap;myMultimap.insert(make_pair(1, "apple"));myMultimap.insert(make_pair(1, "apricot"));// myMultimap[1]; // 错误,因为无法确定返回哪个值
}
  • 替代方案:使用迭代器遍历equal_range 函数来获取相同键的值范围。

equal_range 是 C++ 标准库中的一个函数模板,主要用于在有序容器(如 set, map, vector 等)中查找一个值的范围。它返回一对迭代器,表示要查找值在容器中第一次出现的位置所在指针(迭代器)和最后一次出现的位置指针

auto range = myMultimap.equal_range(1);
for (auto it = range.first; it != range.second; ++it) {cout << "Value: " << it->second << endl;
}
2. Multimap 的删除
  • 方法:使用 erase 函数可以删除指定键的元素,例如删除所有键为 1 的元素。
myMultimap.erase(1); // 删除所有键为 1 的元素

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

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

相关文章

1 go语言环境的搭建

本专栏将从基础开始&#xff0c;循序渐进&#xff0c;由浅入深讲解Go语言&#xff0c;希望大家都能够从中有所收获&#xff0c;也请大家多多支持。 查看相关资料与知识库 专栏地址:Go专栏 如果文章知识点有错误的地方&#xff0c;请指正&#xff01;大家一起学习&#xff0c;…

软件测试---测试需求分析

课程目标 什么是软件测试需求 软件测试需求的必要性 如何对软件测试需求进行分析&#xff08;重点&#xff09; 课程补充 灰度测试&#xff08;基于功能&#xff09;&#xff1a;先发布部分功能&#xff0c;然后看用户的反馈&#xff0c;再去发布另外一部分的功能更新。 A/B测…

运筹学笔记

计算的时间问题&#xff01;计算机解决了计算量的问题&#xff01; 计算机的发展对运筹学研究起到了极大的促进作用。 运筹学的一个特征之一是它常常会考虑寻求问题模型的最佳解决方案&#xff08;称为最优解&#xff09;。 没有人能成为运筹学所有方面的专家。 分析学越来越流…

Pytorch 8

这节课是讲mini_batch数据下载的 from torch.utils.data import Dataset from torch.utils.data import DataLoader第一个类是抽象类&#xff0c;只能继承 第二个可以直接用 class DiabetesDataset(Dataset):def __init__(self, filepath):xy np.loadtxt(filepath, delimit…

redis的分片集群(仅供自己参考)

前言&#xff1a;为什么使用分片集群&#xff1a;因为redis的主从和哨兵机制主要是用来解决redis的高并发读的问题&#xff0c;还有redis的高并发的写的问题没有解决。使用分片集群就可以很好的解决redis写的问题&#xff0c;有多个master就可以实现并发的写。同时&#xff0c;…

C++学习笔记04-补充知识点(问题-解答自查版)

前言 以下问题以Q&A形式记录&#xff0c;基本上都是笔者在初学一轮后&#xff0c;掌握不牢或者频繁忘记的点 Q&A的形式有助于学习过程中时刻关注自己的输入与输出关系&#xff0c;也适合做查漏补缺和复盘。 本文对读者可以用作自查&#xff0c;答案在后面&#xff0…

Mysql-高级实战案例

文章目录 千万级用户场景下的运营系统SQL调优1. 索引优化2. 查询优化3. 分析查询执行计划4. 存储引擎配置5. 数据库架构优化6. 监控与报警7. 定期维护8. 软件升级 亿级数据量商品系统的SQL调优实战1. 索引优化2. 查询重构3. 分区策略4. 优化查询计划5. 缓存策略6. 数据库架构调…

国内微短剧系统平台抖音微信付费小程序app开发源代码交付

微短剧作为当下热门的内容&#xff0c;结合抖音平台的广泛用户基础&#xff0c;开发微短剧付费小程序APP具有显著的市场潜力&#xff0c;用户对于短剧内容的需求旺盛&#xff0c;特别是在言情、总裁、赘婿等热门题材方面&#xff0c;接下来给大家普及一下微短剧小程序系统。 顺…

rce漏洞-ctfshow(50-70)

Web51 if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\|\%|\x09|\x26/i", $c)){ system($c." >/dev/null 2>&1"); } Nl&#xff0c;绕过tac&#xff0c;cat&#xff0c;绕…

Pytest进阶之fixture的使用(超详细)

目录 Fixture定义 Fixture使用方式 作为参数使用 Fixture间相互调用(作为参数调用) 作为conftest.py文件传入 Fixture作用范围Scope function class module session Fixture中params和ids Fixture中autouse Fixture中Name 总结 pytest fixture 是一种用来管理测试…

回溯算法(相关解题):

求子集序列&#xff1a; 解题思路&#xff1a; 已知原集合的数据位数为N&#xff0c;则可以通过二进制比对原来集合&#xff0c;二进制位为1则输出集合上的该位数据&#xff0c;为0则空&#xff0c;二进制的01排序规律与子集的输出一致由集合的位数可以判断出二进制的范围 0 ~…

Direct3D 9的介绍以及Demo演示

文章目录 1、d3d9的介绍1. 概述2. 核心概念3. 初始化和渲染流程4. 常见用法5. 先进特性6. 总结 2、d3d9demo详解1.头文件和全局变量2.IGW 相关全局变量3.函数&#xff1a;CloseIGW4.函数&#xff1a;OpenIGW5.UI 控件和日志处理6.登录和登出相关函数7.登录回调函数8.DXUT 相关回…

P3-AI产品经理-九五小庞

AI产品的数据流向 美团外卖&#xff0c;实时只能调度 美团28分钟送达需求的分析 AI产品常用的算法 常用算法 常见的AI算法解析 自然语言生成NLG语音识别&#xff1a;科大讯飞&#xff0c;通义千问 虚拟现实机器学习平台 决策管理系统生物特征识别技术 RPA(机器人流程自动…

探索 GPT-4o Mini:开发者的新利器

文章目录 探索 GPT-4o Mini&#xff1a;开发者的新利器1. 引言2. GPT-4o Mini 的核心特点3. 使用 GPT-4o Mini 的实际案例3.1 客户支持自动化3.2 内容生成与创作3.3 代码生成与优化 4. 使用体验分享5. 未来展望6. 结论 探索 GPT-4o Mini&#xff1a;开发者的新利器 OpenAI 最新…

UE4-光照重建

当我们拉入新的光源和模型到我们的场景中后&#xff0c;会产生这样的情况&#xff1a; Preview:预览 表示此时由于光照物体所产生的阴影都是预览级别的并不是真正的效果。 方法一&#xff1a; 或者也可以在世界大纲中选中我们的光源&#xff0c;然后将我们的光源改变为可以…

JAVA基本概念(垃圾回收、API)- 10

一、垃圾分代回收机制 1. 垃圾回收针对的是堆内存 2. 对象在堆内存中存储,对象在使用完成之后会在不定的某个时刻被垃圾回收器(GC - Garbage Collector)解析 掉。现阶段回收过程无法手动控制。当调用构造方法的时候,创建好一个对象,因为Java中对每种数据类型都明确 给…

Jdk22新特性

JDK 22 引入了多项新特性,旨在提升 Java 语言的性能、简化开发过程以及增强代码的可读性和可维护性。以下是对 JDK 22 新特性的详细归纳: 核心Java库 外部函数和内存 API (JEP 454):提供了一个纯 Java 应用程序接口,用于替代 JNI(Java Native Interface),以支持直接调…

前端切片下载

要在Vue3前端实现文件切片下载&#xff0c;可以参考以下步骤&#xff1a; 分片函数&#xff1a;将文件分成多个小片段。 生成Blob对象&#xff1a;将片段转换为Blob对象。 创建下载链接&#xff1a;通过Blob对象创建下载链接。 合并下载的片段&#xff1a;下载完成后&#x…

判断字符串,数组方法

判断字符串方法 在JavaScript中&#xff0c;可以使用typeof操作符来判断一个变量是否为字符串。 function isString(value) {return typeof value string; } 判断数组 在JavaScript中&#xff0c;typeof操作符并不足以准确判断一个变量是否为数组&#xff0c;因为typeof会…

深入理解Python中的Pandas库

目录 Pandas简介安装PandasPandas的核心数据结构 SeriesDataFrame 数据加载与存储 从CSV文件读取数据从Excel文件读取数据从SQL数据库读取数据数据存储 数据操作 数据选择数据过滤数据排序数据分组与聚合数据透视表 数据清洗与处理 处理缺失值数据转换数据合并 数据可视化实战…