STL映射

1. 如何理解映射。

我们过去学过 数组 , 例如 int a[100000]; 是定义了一个整数类型的数组,下标可以从0到99999,这里其实是一共有 100000 个下标,换句话说,就是有 100000 个数组元素。下标就是一个整数类型的数字,例如 a[5] 或者 a[71] 分别代表两个不同的数组元素,下标分别为 5 和 71 。

int a[1000000]; // 定义数组。程序运行到这里,会申请分配一片连续的内存,记住,是连续的。这片内存有 4*1000000 byte,分别用于存放从 a[0] 到 a[999999] 的值

//给数组的元素赋值
for(int i=0;i<1000000;i++)
    scanf("%d",&a[i]); 

//访问和输出数组元素
for(int i=0;i<1000000;i++)
    printf("a[%d]=%d\n",i,a[i]);

 

大家有没有想过,我们的下标能否换成别的东西,最常见的,我们能不能换成字符串?我们曾经做过大沥镇的一条竞赛题,叫 小偷,一共有 6 个人,我们能否用一个数组记录着各个人在哪一个房间(假设这个数组叫 a ),我们能否执行这样的命令: a["Tom"] = "Room 1"; 去记录 Tom 去了 "Room 1",如果行,那个程序就可以大大的简化。而今天我们学的 STL 映射 模板就是可以解决这个问题的。

通过小偷 这条题,我们可以初步的去理解 映射(STL 的 map), 它就是一种技术,解决 key-value 的快速对应关系的,map 的英文就是有相互对照的关系。如果有一个类似数组的东西,我通过人名 Tom 能对应出他在哪个房子 ("Room 1"),这就是一种对应关系,在这里人名就是 key,对应的房子就是value。

回过头看我们过去学的数组,int a[100];,就是 int 到 int 的映射,int b=a[6];,我们用 key=6,去找出对应的value; bool c[100];,就是从 int 到 bool 的映射。今天,如果你喜欢,你可以把过去很多基于数组的题目改成基于映射的代码,大多数都没问题的。

2. 为什么要用映射

映射能提供很灵活的 key-value 对应关系的存储和查找,例如,我设置一个映射变量,专门记录各个单词在字典中的第几页,"country" 在第 17 页,"door" 在第 3 页。如果没有这个映射,你要写很多代码才能做同样的事情。

有一些题目,还是 int:int 的映射,表面上可以用数组,但是数据范围很大,如果你要定义一个很大很大的数组,你就爆内存的,用映射可以解决这样的问题。因为,映射其实不是通过静态内存来存放数据的,这个机制相当复杂,我就不展开介绍了。简单来说,就是你存的数据少,映射占的内存就少,你存的数据多,映射占的内存就多。如果数据很稀疏的时候,用 map 特别升内存。 - 数组的下标只能是非负数,而基于映射,那就没有这个限制了

3. 映射和数组相比,有什么坏处。

  • 映射里面的数据,是经过了排序的(和二分法很有关系,但是更复杂),所以,你要操作数据的时候,STL 并不是按顺序的去查找的,而是类似于用二分查找的方法去定位你要访问的那个元素在什么地方。这个过程也挺快,但是再快也没有基于数组的方法快(数组的访问是基于位置便宜的算法,你填下标,直接算出你要访问的元素在内存的什么位置,一步到位,非常快)。所以,能用数组的时候还是继续用数组。

4. 迭代器

基于传统的数组技术,我们可以很容易访问数组中所有元素的值,弄一个 for 循环就行了。

//访问和输出数组元素,下标的范围是明确的,我列举了所有的下标就可以访问所有的数组元素
for(int i=0;i<1000000;i++)
    printf("a[%d]=%d\n",i,a[i]);

 

针对映射,key 的值往往是不太明确的,例如如果 key 是字符串,你们你很难用一个 for 语句来枚举。这时候,就要依靠迭代器。反正,迭代器及时给你枚举用的。

5. 代码样例

5.1 映射的定义

//映射的关键字是 map
// 尖括号内一定是有 2 个数据类型,不能多,也不能少。两个类型用逗号分隔,一个是 key 的类型,一个是 value 的类型。
map < int, int > m1;  // 定义 m1 映射,是从 int 映射到 int
map <string ,int > m2; //定义 m2 映射,是从 string 映射到 int
map <long long ,int > m3; //定义 m3 映射,是从 long long 映射到 int

 

5.2 映射的访问

映射的访问很像数组

例子 1

有 n 个学生,他们的名字不重复,我们输入他们的姓名和分数。

map < string, int > m; // 定义一个 从 string 到 int 的映射

int n;
cin>>n;

string name;
int score;
for (int i=0;i<n;i++){
    cin>>name>>score; // 输入到临时变量
    m[name] = score; // 把成绩信息记录到 映射 m 。这里 name 是字符串,是用来做下标
}

例子 2

有 1000 学生,他们每个学生有学号,学号是 15 位的数字,学号不重复。现在输入这 100 个学生的学号和成绩。

map <long long, int > m; // 定义一个 从 long long 到 int 的映射,学号是 15 位 数字,所以需要用 long long

long long id;
int score;
for (int i=0;i<1000;i++){
    cin>>id>>score; // 输入到临时变量
    m[id] = score; // 把成绩信息记录到 映射 m 。这里 id 是 long long 类型。如果定义一个数组 int x[1000000000000000],内存一定爆了
}

5.3 迭代器

接着上面的例子 2 做延伸

完成学生的成绩信息输入之后,假设我们现在要输出这些学生的数据

// 定义迭代器,迭代器的前半段内容和定义 STL 模板(映射)的格式一样,尖括号后面发生变化
// :: iterator 是固定格式, 再后面是空格,然后是的迭代器的名字
// 名字是自己起的,只要符合变量描述符的要求就可以了
// :: 前面的内容要和映射的 key-value 特征完全一致
map < long long , int > :: iterator it; // 定义一个迭代器,叫 it,它可以用于迭代 <long long , int > 类型

// m 已经在例子 2 前面的代码里定义过了
for(it=m.begin();it!=m.end();it++){
    printf("学号是:%lld, ",it->first);
    printf("成绩是: %d\n", it->second);
    // 也可以用这个方法输出成绩 printf("成绩是: %d", m[it->second])
    // 下面的这种写法要做一次二分查找,所以慢一些。
}

上面的输出,是从 m.begin() 开始, begin 是映射的一个函数,返回的是映射的第一个成员的指针,end() 是返回一个空指针,如果迭代器不断加加,最后变成空指针,那么就是枚举完毕。

映射的迭代器有两个成员,first 指向 key,second 指向 value。所以 it->first 就是得到那一个元素的 key,it->second 得到的是那一个元素的 value。

迭代的过程中,是按照 key 从小到大的顺序。

接着上面的例子 1 做延伸

完成了学生姓名和成绩的输入之后,加入我们要输出这些数据

map <string, int> :: iterator it;
for(it=m.begin();it!=m.end();it++)
    cout<< it->first <<" "<< it->second << endl; 

//上面的输出是按照名字字典序从小到大

begin() 和 end( )

begin() 返回的是正向的第一个键值对的指针

end() 返回的是正向的最后一个键值对后面的一个空值的指针。

查找函数 find()

基于上面的例子 1 做延伸

假设我们想知道某个名字是否存在

string t;
cin>>t;
if( m.find(t)!=m.end() )
    cout<<"这位同学的成绩是:"<< m[t] <<endl;
else
    cout<<"不存在名字为 " << t << "的同学\n";

 

又或者是这么写

map <string , int> :: iterator it;
cin>>t;
it = m.find(t);
if(it!=m.end())
    cout<<"这位同学的成绩是:"<< it->second <<endl;
else
    cout<<"不存在名字为 " << t << "的同学\n";

// 这个代码比上面的代码更好,因为少了一次映射查找

 

简单来说,find 函数返回的是某一个 key 值对应的指针。如果找不到,这个指针就是空指针。

lower_bound() 和 upper_bound( )

STL 模板其实是按照顺序去整理各个 key-value 键值对的。上面的 find 函数是精确查找。除此以外,还有 大于等于查找 和 大于查找。

lower_bound() 是查找大于等于某个一个 key 的键值对(返回的是指针)

upper_bound() 是查找大于某个一个 key 的键值对(返回的是指针)

找不到的时候,就是返回空指针 ( end() )

erase( )

erase 是删除一个 key-value 键值对。这个函数是多态函数,参数可以是一个 key ,也可以是一个 指针。

延续上面的例子 1

string tmp;
cin>>tmp; // 输入要删除的人的名字
m.erase(tmp); // erase 的参数是字符串 (key)


map <string , int> :: iterator it;
cin>>tmp;
it = m.find(tmp); // 先找到 tmp 对应的指针

if(it!=m.end()) erase(it); // erase 的参数是指针

 

但是,这里有一个很隐秘的错误。如果通过迭代器来删键值对,而你又是在通过迭代器来遍历每一个键值对,那么删完之后,就会有问题。

例如,延续 例子 1 ,假设我们要删掉成绩小于 60 分的键值对,错误程序如下

map <string, int> :: iterator it;
// 通过迭代器访问每一个键值对 ()
for(it=m.begin();it!=m.end();it++){
    if(it->score < 60){
        // 这位同学的分数少于 60 分,删除这个同学
        m.erase(it);
    }
}

 

假如有多个同学的成绩低于 60 分,上面的代码执行会有问题。原因是 erase 了 it 指向的那个键值对之后,it 就变成是空指针,基于这个空指针做 it++ 是有问题,也就是说没办法继续迭代下去了。

erase 函数返回的是删除一个键值对之后,后面跟着的那个键值对的指针。所以,要删除所有低于 60 分同学的成绩,代码是:

map <string, int> :: iterator it;
// 通过迭代器访问每一个键值对 ()
for(it=m.begin();it!=m.end();){
    if(it->score < 60){
        // 这位同学的分数少于 60 分,删除这个同学
        it = m.erase(it); // erase 返回下一个键值对的指针,所以就不用做 it++ 了
    }else{
        it++;
    }
}

 

size( )

size 函数是获得映射的键值对的个数。

rbegin() 和 rend()

我们学过 begin() 和 end() ,前面多了个 r ,意思是反向的 (reverse 的单词)

rend() 返回的是最后一个键值对的指针 (就是 end( ) 前面的那一个 )

rbegin() 返回的是第一个键值对更前面的位置的指针 (就是 begin( ) 前面的那一个,所以其实也是空指针 )

接着上面的例子 1 ,如果想倒着来输出成绩信息,代码是

map <string, int> :: reverse_iterator it; // 反向迭代器
for(it=m.rbegin();it!=m.rend();it++)
    cout<< it->name <<" "<< it->second << endl; 

//上面的输出是按照名字字典序从大到小

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python 制作 发货单 (生成 html, pdf)

起因&#xff0c; 目的: 某个小店&#xff0c;想做个发货单。 过程: 先写一个 html 模板。准备数据&#xff0c; 一般是从数据库读取&#xff0c;也可以是 json 格式&#xff0c;或是 python 字典。总之&#xff0c;是数据内容。使用 jinja2 来渲染模板。最终的结果可以是 h…

多线程进阶——线程池的实现

什么是池化技术 池化技术是一种资源管理策略&#xff0c;它通过重复利用已存在的资源来减少资源的消耗&#xff0c;从而提高系统的性能和效率。在计算机编程中&#xff0c;池化技术通常用于管理线程、连接、数据库连接等资源。 我们会将可能使用的资源预先创建好&#xff0c;…

WPF+MVVM案例实战(七)- 系统初始化界面字体描边效果实现

文章目录 1、案例效果展示2、项目准备3、功能实现1、资源获取2、界面代码3、后台代码 4 源代码获取 1、案例效果展示 2、项目准备 打开项目 Wpf_Examples&#xff0c;新建系统初始化界面 WelcomeWindow.xmal,如下所示&#xff1a; 3、功能实现 1、资源获取 案例中使用的CSD…

Java | Leetcode Java题解之第516题最长回文子序列

题目&#xff1a; 题解&#xff1a; class Solution {public int longestPalindromeSubseq(String s) {int n s.length();int[][] dp new int[n][n];for (int i n - 1; i > 0; i--) {dp[i][i] 1;char c1 s.charAt(i);for (int j i 1; j < n; j) {char c2 s.char…

【Java并发编程】信号量Semaphore详解

一、简介 Semaphore&#xff08;信号量&#xff09;&#xff1a;是用来控制同时访问特定资源的线程数量&#xff0c;它通过协调各个线程&#xff0c;以保证合理的使用公共资源。 Semaphore 一般用于流量的控制&#xff0c;特别是公共资源有限的应用场景。例如数据库的连接&am…

Python | Leetcode Python题解之第516题最长回文子序列

题目&#xff1a; 题解&#xff1a; class Solution:def longestPalindromeSubseq(self, s: str) -> int:n len(s)dp [[0] * n for _ in range(n)]for i in range(n - 1, -1, -1):dp[i][i] 1for j in range(i 1, n):if s[i] s[j]:dp[i][j] dp[i 1][j - 1] 2else:dp…

从病理AI的基础模型发展历程,看未来的医学AI发展趋势|个人观点·24-10-23

小罗碎碎念 在临床相关的人工智能&#xff08;AI&#xff09;模型发展方面&#xff0c;传统上需要大量标注数据集&#xff0c;这使得AI的进步主要围绕大型中心和私营企业展开。所以&#xff0c;在这期推文中&#xff0c;我会介绍一些已经商用的模型&#xff0c;并且为计划进军…

逻辑推理学习笔记

目的 立场辩护整理思绪 基本框架 论题 &#xff08;变化&#xff09; 我要证明&#xff08;讨论对象 变化&#xff09; 论据 &#xff08;变化&#xff09; 拿什么证明&#xff1f;也就是证据呈现。 论证 &#xff08;不变&#xff09; 要如何证明&#xff1f;逻辑框架…

通过conda install -c nvidia cuda=“11.3.0“ 安装低版本的cuda,但是却安装了高版本的12.4.0

问题 直接通过 conda install -c nvidia cuda"11.3.0"安装得到的却是高版本的 不清楚原理 解决方法 不过我们可以分个安装 runtime toolkit 和 nvcc 安装指定版本的 cudatoolkit 和 nvcc conda install -c nvidia cuda-cudart"11.3.58" conda instal…

【Linux系统编程】——Linux入门指南:从零开始掌握操作系统的核心(指令篇)

文章目录 查看 Linux 主机 ip以及登录主机Linux基础文件操作指令man&#xff1a;查看命令的手册页&#xff0c;了解命令的详细用法。pwd&#xff1a;显示当前目录路径。cd&#xff1a;切换目录。ls&#xff1a;列出当前目录下的文件和文件夹。mkdir&#xff1a;创建新目录。 文…

第三讲、C的运算符和表达式

一、运算符分类&#xff1a; &#xff08;1&#xff09;按运算对象的数目&#xff1a; 单目运算符 双目运算符 三目运算符 &#xff08;2&#xff09;按运算对象的数目&#xff1a; 算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符、自增自减运算符、…

菜叶子芯酸笔记3:GPU、GPGPU、CUDA之间的关系;CUDA之外;Tensor Core

我今天看到B站一个up主很好的资料【云计算科普研究所的个人空间-云计算科普研究所个人主页-哔哩哔哩视频】&#xff0c;结合我这周的积累整理了这份我觉得相比之前逻辑更加完善的笔记。 先是GPU到GPGPU 到CUDA之间进化关系部分&#xff0c;然后CUDA之外的友商竞品部分&#xf…

orbslam安装

1.linux操作命令 pwd&#xff1a;查看终端所在路径 cd&#xff1a;切换路径 cd ..&#xff1a;跳回到上级目录 ls: 列出当前路径下的所有文件夹 touch&#xff1a;创建新的文件 mv &#xff1a;移动文件(在该文件所在目录的路径下执行此操作) 例如&#xff1a;mv test_file /ho…

vue3中mitt和pinia的区别和主要用途,是否有可重合的部分?

在 Vue 中&#xff0c;Mitt 和 Pinia 是两个不同的工具&#xff0c;它们的主要用途和功能有所不同&#xff0c;但在某些方面也存在重合的部分。 区别 Mitt&#xff1a; Mitt 是一个简单而强大的事件总线库&#xff0c;用于在组件之间进行事件的发布和订阅。 它提供了一种简洁…

讲一讲 kafka 的 ack 的三种机制?

大家好&#xff0c;我是锋哥。今天分享关于【K讲一讲 kafka 的 ack 的三种机制&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; 讲一讲 kafka 的 ack 的三种机制&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Kafka的消息确认机制&…

python实战项目46:selenium爬取百度新闻

python实战项目46:selenium爬取百度新闻 一、项目简介二、完整代码一、项目简介 思路是首先使用selenium打开百度新闻页面,然后实现翻页操作,获取每条新闻的标题和链接。接下来的问题是,在遍历标题和链接,对每一个链接发送请求时,发现会弹出百度安全验证,本文的思路是使…

远程root用户访问服务器中的MySQL8

一、Ubuntu下的MySQL8安装 在Ubuntu系统中安装MySQL 8.0可以通过以下步骤进行1. 更新包管理工具的仓库列表&#xff1a; sudo apt update 2. 安装MySQL 8.0&#xff0c;root用户默认没有密码&#xff1a; sudo apt install mysql-server sudo apt install mysql-client 【…

动态规划 - 背包问题 - 01背包

01背包问题 二维数组 1. 确定dp数组&#xff08;dp table&#xff09;以及下标的含义&#xff1a;dp[i][j]-下标为[0,i]的物品&#xff0c;任取放容量为j的背包中的最大价值 2. 确定递推公式&#xff1a;dp[i][j] max(dp[i-1][j]&#xff08;不放物品i), dp[i-1][j-weight[i]]…

研发效能DevOps: Vite 使用 Vue Router

目录 一、实验 1.环境 2.初始化前端项目 3.安装vue-router 4.Vite 使用 Vue Router 二、问题 1.运行出现空页面 2.Vue Router如何禁止页面回退 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 系统 软件版本备注Windows11VS Code1.94.2Node.jsv18.20.4(LT…

Redis 篇-深入了解在 Linux 的 Redis 网络模型结构及其流程(阻塞 IO、非阻塞 IO、IO 多路复用、异步 IO、信号驱动 IO)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 用户空间与内核空间概述 2.0 Redis 网络模型 2.1 Redis 网络模型 - 阻塞 IO 2.2 Redis 网络模型 - 非阻塞 IO 2.3 Redis 网络模型 - IO 多路复用 2.3.1 IO 多路复…