【C++】STL map和set用法基本介绍

map、set用法简介

  • 前言
  • 正式开始
    • set
      • 构造
      • erase
      • find
      • swap
      • count
      • lower_bound 和 upper_bound
        • lower_bound
        • upper_bound
      • equal_range
        • pair
      • multiuset
        • find
        • erase
        • count
    • map
      • 构造
      • insert
      • [ ]
        • [ ]底层原理
    • multimap
    • 两道题目
      • 前K个高频单词
      • 两个数组的交集

在这里插入图片描述

前言

首先,使用map和set最少要了解二叉搜索树,如果点进来的同学对于二叉搜索树还不熟悉的话,可以先看看我这篇博客:【C++】二叉搜索树。

当然,如果想要更深入的理解map和set的话,肯定是还要学更高级一点的树的,就比如说AVL树、红黑树,但是这里只想要简单用用的话,二叉搜索树先搞清楚就差不多够用了。

本篇主要讲讲map和set的基本用法,最后会有两道题来巩固一下。

后面的两篇博客来说一说关于AVL树的实现和红黑树的实现来更好的理解map和set。

正式开始

STL将容器分为两种。
一种是序列式容器,也就是数据结构中的线性表,包括有vector、list、deque等等。
还有一种是关联式容器,比如我们这里要讲的map和set。关联就是指存放的元素之间是相互关联的,可以通过某一元素来找到其他元素,不像vector那样的,各存各的元素。

那么set和map对应到前一篇的二叉搜索树中分别就是key模型和key/value模型。

先来说set。

还是cplusplus的网站:set,里面的接口我挑着讲。

set

在这里插入图片描述
如上所示,三个模版参数。

  1. T就相当于是key。
  2. compare是仿函数,默认情况下的是less,就是左<根<右。如果传的是greater就是左>根>右。
  3. alloc就是给内存池,这个参数和之前那些容器一样,还没有到该讲的时候,先不讲。

构造

在这里插入图片描述

这三个最常用的就是无参构造。
无参构造初始化了之后就不断插入:
在这里插入图片描述

insert接口如下:
在这里插入图片描述
还是插值、按位置插、迭代器区间。后两个用的都很少。
第一个插值的返回值这里先不细讲等会讲map的时候会说。

如果我们想要遍历的话可以用迭代器:
在这里插入图片描述
这里set的迭代器只支持访问,不支持修改,如果直接*it修改的话会报错。

当然有了迭代器就支持范围for:
在这里插入图片描述

C++11里面还有个用大括号初始化的:
在这里插入图片描述
这个演示一下:
在这里插入图片描述
这里插入的时候会直接去重,然后遍历的时候打印出来是有序的,这也和二叉搜索树的特性是相符的。

迭代器区间构造用到的场景比较少。

在这里插入图片描述

我们可以将compare改为greater的,这样遍历的时候用正向迭代器就变成了降序:

在这里插入图片描述

如果用less但是还想排降序的话,可以用反向迭代器:
在这里插入图片描述

拷贝构造用的也比较少,因为拷贝构造的开销还是比较大的,用的时候要慎重。

还可以用数组来初始化:

在这里插入图片描述

erase

在这里插入图片描述

删除,值、位置、迭代器区间。后两个不常用,演示第一个:
在这里插入图片描述

在这里插入图片描述

find

在这里插入图片描述

find的值如果存在,就返回那个值对应的迭代器。如果不存在,就返回end()。

这里可以用find来查找并删除某值。

在这里插入图片描述

因为可能有值不存在的情况,所以上述的写法是不严谨的,如果删除一个不存在的数就会出问题:
在这里插入图片描述
上面是debug的,如果是release的话就会直接出问题,因为release下会省略assert:
在这里插入图片描述

所以应该这样写:
在这里插入图片描述
判断一下是不是end,是了再删除,不是的话就别删。

这里里比直接erase值更好的一点是,如果某个值不存在,那么就可以打印出该值不存在的信息,如果是直接用erase删除的话,就没法打印了。

在这里插入图片描述
如果不需要打印信息的话可以直接用erase删值。

swap

在这里插入图片描述

两个set的对象交换的时候就用set的swap,因为set的swap就是简单的将根节点的指针交换就行了。不要用算法库中的swap,赋值和拷贝构造的开销会很大。

count

在这里插入图片描述

这个函数就是统计某个值出现的次数,可以说对于set来说没有什么用,因为set中的值永远出现的是一次。

set中有这个函数单纯是为了使得库提供的函数接口的一致性而给的,因为multiset中就有这个count,而multiset就和set的底层一样,但是multiset允许键值冗余,所以可以有多个值同时存在,我们就可以在multiset中使用count。

不过我们可以用count来判断某个值是否存在,如果存在返回1,如果不存在返回0。
在这里插入图片描述

lower_bound 和 upper_bound

lower_bound

在这里插入图片描述
这个函数返回>=val的值的迭代器,如果这个>=的值存在的话,就返回这个值的迭代器,如果不存在的话就返回end。
在这里插入图片描述

在这里插入图片描述

upper_bound

在这里插入图片描述
这个是返回>的值的迭代器。

直接给个和lower_bound一块的示例:
在这里插入图片描述

equal_range

在这里插入图片描述

这里的返回值为pair,这里讲一讲。

pair

在这里插入图片描述
就是一个存放一对值的结构体。

两个值库中定义的名字是first和second。
大概代码如下:

template <class T1, class T2>
struct pair
{typedef T1 first_type;typedef T2 second_type;T1 first;T2 second;pair(): first(T1()), second(T2()){}pair(const T1& a, const T2& b): first(a), second(b){}
};

所以就好说了。

在这里插入图片描述
上面的返回值为pair,而且pair中两个参数的类型是迭代器。也就是返回两个值的迭代器。
返回的是一个左闭右开的区间:[x, y)。(x,y均为迭代器)
如果val的值存在于树中,x=val,y为++x。
如果val不存在,但还在值的范围内,x>val,y=x;不在范围内,x,y都是end()。

在这里插入图片描述

在这里插入图片描述

multiuset

上面也提到了,这个类型支持键值冗余,也就是可以同时存放多个值。

演示一下:
在这里插入图片描述
这里的multiset就是单纯的排序了。没有了去重的功能。

find

这里find的话,如果值没有重复,就返回那个值的迭代器,如果重复了,就返回中序遍历顺序下该值第一次出现的值的迭代器。

在这里插入图片描述

如果想要访问第二次出现的值的话++一下返回的迭代器就好了。

erase

这里如果是删除重复的值的话,比如说x,会把x全部删除。

在这里插入图片描述

和上面给迭代器的删除不一样。

count

这个上面也提过了,这里可以用。

在这里插入图片描述

其他功能就跟set类似了,这里就不继续讲,multiset了。

map

map也分map和multimap,和上面一样,multimap允许键值冗余,而map不允许。

二者都是二叉搜索树中的 key/value 模型。

先来说map。

在这里插入图片描述
这里有四个模版参数。

第一个是键值key,这个值是用来进行关键字的比较的。

第二个是附加项T,这就是一个与key相关的一个值,插入的时候是按照key比较的,而不是T。也就是二叉搜索树中key/value模型中的value。

第三个是仿函数Compare,默认值也是less,不过less只有一个key,也就是比较的时候按照key比较,T不会参与。

第四个是内存池,暂时不讲。里面的那个pair前面也讲过了。

map中有几个内嵌类型(map中typedef的类型)要说一下。
在这里插入图片描述

  1. key_type 模版参数中的第一个参数key
  2. mapped_type 模版参数中的第二个参数T
  3. value_type pair<const key_type,mapped_type>
  4. key_compare 模版参数中的第三个参数Compare

构造

在这里插入图片描述

一般没有直接初始化的时候就给值的,直接调用一下默认构造然后再插入或者用[ ]就行。

所以这里就不讲了。注意一下拷贝构造慎用,消耗较大。

insert

在这里插入图片描述

这里insert,看第一个,返回值先不说,先说参数。
参数为value_type,也就是pair<const key_type,mapped_type>。
那么我们用的时候要这样(下面的例子为写一个字典):
在这里插入图片描述
可以看到,排序的时候是按照ASCII排的。

但是这样有点麻烦了。
可以用匿名对象:
在这里插入图片描述

但是也没有方便到哪去。
我们可以typedef一下pair:
在这里插入图片描述

还有一个接口,专门用来搞pair的。
就是make_pair。

在这里插入图片描述

这个函数的大概实现为:
在这里插入图片描述
其实就是搞了个pair的对象。

用用:
在这里插入图片描述
用的时候可以不像pair那样显示写类型,make_pair是一个函数模版。

上面的都是用范围for来遍历的,下面来说说用迭代器遍历。
在这里插入图片描述
上面可以看到,it解引用得到的是pair类型的对象,然后一个 . 访问其first和second。

如果我们想用指针呢?
在这里插入图片描述
前面list模拟实现的博客中也讲过了,it->返回的是pair类型的指针,如果想要再访问pair中的成员时,就要再加一个->,但是这里为了可读性,编译器做了优化,直接省略掉了一个->。

注意到我上面用范围for的时候里面的赋值为const auto& e的,重在引用,因为map中的存放的为pair类型的数据,如果pair中又存放了string或者其他自定义类型的数据的话,用引用效率就会高不少。

其实上面的insert用的也不多,我只是为了给大家演示演示。
用的最多的还是[ ]。

[ ]

这个才是最需要学的。

如果我们想要用map统计……出现的次数,怎么搞呢?

比如:统计这里面各个水果出现的次数。

string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };

两种方法:

  1. 用insert

代码如下:
在这里插入图片描述

  1. 用[ ]

在这里插入图片描述

可以看到,用[ ]非常的方便。

那么[ ]底层不了解的话,上面是很难看懂的,下面就来说说。

[ ]底层原理

在这里插入图片描述
返回值是mapped_type就是第二个模版参数T。也就是对应到pair中的second的类型。

实际的返回值是这样的:
在这里插入图片描述
新手可能一眼就被吓到了。我们来逐个分析分析。

首先里面有个insert,用到了这个函数的返回值。
那么insert返回值刚刚没讲,就是要在这里讲。
在这里插入图片描述
对比一下上面的调用的话,就是第一个插值的insert,返回值为pair<iterator, bool>类型的。

看下库中的解释:
在这里插入图片描述
意思就是上面提供的第一个insert函数返回了一个pair类型的对象(假如说是x),不管是插入了新元素(假如说新插入的元素是y)或是原来map中就有这个元素y(原来有的话就不会插入),x的first都会指向insert之后的map中的元素y,也就是那个元素的迭代器;如果原来map中没有这个新插入的元素y,x的second就是true,代表插入成功了,如果原来map中有这个新插入的元素y,x的second就是false,代表插入失败了。

再看一下函数调用:
在这里插入图片描述
我把insert对应的括号的匹配给画出来了,去掉之后是:
在这里插入图片描述
其返回值是pair对象,然后访问了这个对象的first,first为迭代器,指向新插入的pair,解引用之后又访问了其指向的pair的second,也就是第二个模版参数T。所以最终返回值类型就是第二个模版参数T,对应的就是insert之后的pair的second。

对应到我们上面的countMap[str],其最终的返回值就是striing对象所对应的int。

所以当我们第一次用[ ]的时候,是没有str的,先插入了,然后再让对应的int++了一下。

再看一下上面调用的insert,调用时传参为make_pair(k,mapped_type()),也就是说second会调用其默认构造函数,而上面的int默认值就是0。所以插入之后为0,返回之后再++正好为1,所以插入一次就加一。就达到了统计次数的目的。

如果感觉上面的[ ]重载实现的太难以理解了,我们也可以自己来实现一下:

在这里插入图片描述

再总结一下[ ]的返回值:

  1. map中有这个key,返回的是T的引用。即查找+修改T的功能。
  2. map中没有这个key,插入一个pair(key, T())并返回T的引用。 即插入+修改T的功能。

现在我们再来写刚刚写过的词典的话就非常简单了。
在这里插入图片描述
在这里插入图片描述

map中还有个at,也可以返回T的引用,和[ ]不一样的是,at不在的话是抛异常:
在这里插入图片描述

可以看到将map的时候有一大堆的pair,可以说pair就是专门为[ ]准备的。

再来说一点关于multimap的

multimap

就说一点,允许键值冗余。
如果允许键值冗余的话,就不能用[ ]和at了,因为如果有重复的元素,编译器不知道该匹配哪个,直接报错:
在这里插入图片描述

但是好处就是我们可以插入相同的K值了。
在这里插入图片描述
left有左边,也有剩余的意思。可以看到,插入了两次左边,一次剩余。

剩下的就不说了。

下面来两道题来练练手:

两道题目

两道题目链接:
前K个高频单词
两个数组的交集

前K个高频单词

在这里插入图片描述

这道题乍一看是topK问题,有的同学就想到了用堆来写。确实可以。

法一:优先级队列

我们先用map来统计一下各个单词出现的次数。然后再根据出现的次数来将map中的各个pair放到优先级队列(大堆)中,然后就可以不断获取堆顶元素来获取topK个单词。

但是题目中要求,各个单词要按照出现频率来排序,当两个单词出现的频率相同时,按照字典序排序,也就是按照ASCII来排。

最终实现的代码如下:
在这里插入图片描述
用到仿函数的时候,里面的逻辑有点绕。

法二:数组排序

我们先用map统计次数,此时map中的各个pair的顺序是按照string来排的。然后我们将map中的各个pair放入到数组中,用数组按照pair的second来进行排序。同样也是要写仿函数来控制频率相同的情况。

实现代码如下:
在这里插入图片描述

方法三:两个map

两个map,一个用来统计出现次数,一个用来根据次数排序。

在这里插入图片描述

两个数组的交集

这道题其实可以直接用双指针,但用双指针的话还要先排序再去重,太麻烦了,我们可以直接用set。

求交集有一个思路。在两个数组有序且不重复的前提下,比如说:
在这里插入图片描述
分为上下两个数组。

每个都从头开始遍历:
在这里插入图片描述

因为是有序的,所以当某个值 it1 小比另一个值 it2 小的时候,那么 it1 一定比另一个数组中的所有数都小。it2同理。所以小的时候就让其++,然后再对比,等的时候就同时++。当一个走到尽头时,就说明没有交集可找了,此时停下来就行。

因为需要排序 + 去重,而set正好能够满足这个条件,代码如下:
在这里插入图片描述

同样的思路,我们可以用来求差集。
在这里插入图片描述

就讲到这。

到此结束。。。

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

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

相关文章

Jmeter(六) - 从入门到精通 - 建立数据库测试计划(详解教程)

1.简介 在实际工作中&#xff0c;我们经常会听到数据库的性能和稳定性等等&#xff0c;这些有时候也需要测试工程师去评估和测试&#xff0c;因此这篇文章主要介绍了jmeter连接和创建数据库测试计划的过程,在文中通过示例和代码非常详细地介绍给大家&#xff0c;希望对各位小伙…

使用idea如何生成webservice客户端

需求阐述 在和外围系统对接的时候&#xff0c;对方只给了wsdl地址&#xff0c;记得之前了解到的webservice&#xff0c;可以用idea生成客户端代码。先记录生成的步骤 使用idea如何生成webservice客户端 1.创建一个Java项目 2.第二步生成代码 我的idea再右键要生成文件目录里…

【目标检测系列】YOLOV2解读

为更好理解YOLOv2模型&#xff0c;请先移步&#xff0c;了解YOLOv1后才能更好的理解YOLOv2所做的改进。 前情回顾&#xff1a;【目标检测系列】YOLOV1解读_怀逸%的博客-CSDN博客 背景 通用的目标检测应该具备快速、准确且能过识别各种各样的目标的特点。自从引入神经网络以来&a…

【go语言学习笔记】04 Go 语言工程管理

文章目录 一、质量保证1. 单元测试1.1 定义1.2 Go 语言的单元测试1.3 单元测试覆盖率 2. 基准测试2.1 定义2.2 Go 语言的基准测试2.3 计时方法2.4 内存统计2.5 并发基准测试2.6 基准测试实战 3. 特别注意 二、性能优化1. 代码规范检查1.1 定义1.2 golangci-lint1.2.1 安装1.2.2…

【C# 基础精讲】循环语句:for、while、do-while

循环语句是C#编程中用于重复执行一段代码块的关键结构。C#支持for、while和do-while三种常见的循环语句&#xff0c;它们允许根据条件来控制代码块的重复执行。在本文中&#xff0c;我们将详细介绍这三种循环语句的语法和使用方法。 for循环 for循环是一种常见的循环结构&…

MySQL8是什么-MySQL8知识详解

从今天起&#xff0c;开始更新MySQL8的教程&#xff0c;今天更新MySQL8的第一篇文章&#xff0c;主要讲了MySQL8是什么、MySQL数据库的概念、MySQL的优势和MySQL的发展历史。 1、MySQL8是什么 MySQL 8是一个开源的关系型数据库管理系统。它是MySQL数据库的最新版本&#xff0c…

【RTT驱动框架分析06】-pwn驱动框架分析+pwm驱动实现

pwm pwm应用程序开发 访问 PWM 设备API 应用程序通过 RT-Thread 提供的 PWM 设备管理接口来访问 PWM 设备硬件&#xff0c;相关接口如下所示&#xff1a; 函数描述rt_device_find()根据 PWM 设备名称查找设备获取设备句柄rt_pwm_set()设置 PWM 周期和脉冲宽度rt_pwm_enable…

Tomcat 部署及优化

Tomcat概述 Tomcat 是 Java 语言开发的&#xff0c;Tomcat 服务器是一个免费的开放源代码的 Web 应用服务器&#xff0c;是 Apache 软件基金会的 Jakarta 项目中的一个核心项目&#xff0c;由 Apache、Sun 和其他一些公司及个人共同开发而成。在中小型系统和并发访问用户不是很…

【腾讯云 Cloud Studio 实战训练营】基于Cloud Studio构建React完成点餐H5页面

前言 【腾讯云 Cloud Studio 实战训练营】基于Cloud Studio 构建React完成点餐H5页面一、Cloud Studio介绍1.1 Cloud Studio 是什么1.2 相关链接1.3 登录注册 二、实战练习2.1 初始化工作空间2.2 开发一个简版的点餐系统页面1. 安装 antd-mobile2. 安装 less 和 less-loader3. …

opencv基础-38 形态学操作-闭运算(先膨胀,后腐蚀)cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

闭运算是先膨胀、后腐蚀的运算&#xff0c;它有助于关闭前景物体内部的小孔&#xff0c;或去除物体上的小黑点&#xff0c;还可以将不同的前景图像进行连接。 例如&#xff0c;在图 8-17 中&#xff0c;通过先膨胀后腐蚀的闭运算去除了原始图像内部的小孔&#xff08;内部闭合的…

剑指offer39.数组中出现次数超过一半的数字

这个题非常简单&#xff0c;解法有很多种&#xff0c;我用的是HashMap记录每个元素出现的次数&#xff0c;只要次数大于数组长度的一半就返回。下面是我的代码&#xff1a; class Solution {public int majorityElement(int[] nums) {int len nums.length/2;HashMap<Integ…

为生成式AI提速,亚马逊云科技Amazon EC2 P5满足GPU需求

生成式AI&#xff08;Generative AI&#xff09;已经成为全球范围内的一个重要趋势&#xff0c;得到越来越多企业和研究机构的关注和应用。纽约时间7月26日&#xff0c;亚马逊云科技数据库、数据分析和机器学习全球副总裁Swami Sivasubramanian在亚马逊云科技举办的纽约峰会上更…

剑指 Offer 15. 二进制中1的个数

题目描述 编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 ‘1’ 的个数&#xff08;也被称为 汉明重量).&#xff09;。 提示&#xff1a; 请注意&#xff0c;在某些语言&#xff08;如…

电动汽车设计、制造、研发的学科、技术和前沿科技综述

引言&#xff1a;电动汽车作为替代传统燃油汽车的一种先进交通工具&#xff0c;不仅具有环保、低噪音等优势&#xff0c;而且对于能源消耗和气候变化等全球性问题也具有重要意义。本文将综述与电动汽车设计、制造、研发相关的学科、技术和前沿科技&#xff0c;以期对电动汽车领…

PHP8的程序结构-PHP8知识详解

在做任何事情之前&#xff0c;都需要遵循一定的规则。在PHP8中&#xff0c;程序能够安照人们的意愿执行程序&#xff0c;主要依靠程序的流程控制语句。 不管多复杂的程序&#xff0c;都是由这些基本的语句组成的。语句是构造程序的基本单位。程序执行的过程就是执行程序语句的…

你不了解的Dictionary和ConcurrentDictionary

最近在做项目时&#xff0c;多线程中使用Dictionary的全局变量时&#xff0c;发现数据并没有存入到Dictionary中&#xff0c;但是程序也没有报错&#xff0c;经过自己的一番排查&#xff0c;发现Dictionary为非线程安全类型&#xff0c;因此我感觉数据没有写进去的原因是多线程…

基于Python 简易实现接口测试自动化

目录 实现思路 统筹脚本 请求封装 日志封装 结果比对 结果邮件 用例获取及数据格式化 请求url转换 测试用例excel结构 测试报告 邮件接收结果 资料获取方法 实现思路 使用excel管理用例用例信息&#xff0c;requests模块发送http请求&#xff0c;实现了记录日志&…

使用vue模拟通讯录列表,对中文名拼音首字母提取并排序

一个功能需求,做一个类似联系人列表的功能,点击名称获取对应的id,样式简陋,只是一个模板,原来是uniapp项目,根据需要改成了vue,需要的自行设计css&#xff08;万是有一个mo的音&#xff09; 流程 获取数据提取首个字的拼音的首个字母排序并分组 上代码&#xff1a; <temp…

SpringBoot自动装配及run方法原理探究

自动装配 1、pom.xml spring-boot-dependencies&#xff1a;核心依赖在父工程中&#xff01;我们在写或者引入一些SpringBoot依赖的时候&#xff0c;不需要指定版本&#xff0c;就因为有这些版本仓库 1.1 其中它主要是依赖一个父工程&#xff0c;作用是管理项目的资源过滤及…

实验室如何选择适合的LIMS实验室管理系统

实验室信息管理系统(LIMS)是从20世纪70年代末开始发展起来的&#xff0c;距今在国外已发展40多年。国内发展历史约20多年&#xff0c;且前十几年国内市场上主要是国外进口的LIMS产品&#xff0c;存在价格高、产品重&#xff0c;实施周期长等水土不服的情况。近十年开始&#xf…