【C++笔记】map和set的使用

news2025/1/6 20:07:09

前言

各位读者朋友们大家好!上期我们讲完了二叉搜索树这一数据结构,这一期我们来讲STL中的map和set这两大容器。这两个容器的底层是红黑树,红黑树的底层是平衡二叉搜索树。

目录

  • 前言
  • 一. 序列式容器和关联式容器
  • 二. set系列的使用
    • 2.1 set类的介绍
    • 2.2 set的构造和迭代器
    • 2.3 set的增删查
      • 2.3.1 set的增删查
      • 2.3.2 [lower_bound](https://legacy.cplusplus.com/reference/set/set/lower_bound/)和[upper_bound](https://legacy.cplusplus.com/reference/set/set/upper_bound/)
      • 2.3.3 迭代器失效问题
    • 2.4 multiset和set的差异
    • 2.5 set的OJ题
  • 三. map系列的使用
    • 3.1 map类的介绍
    • 3.2 pair类型介绍
    • 3.3 map的构造
    • 3.4 map的增删查
    • 3.5 map数据的修改
    • 3.6 map和multimap的区别
    • 3.7 map的OJ题
  • 结语

一. 序列式容器和关联式容器

前面我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,比如交换一下,它依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。
关联式容器也是用来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是非线性结构,两个位置有紧密的关联关系,交换一下,它的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。
map和set底层是红黑树,红黑树是平衡二叉搜索树。set是key搜索场景的结构,map是key/value搜索场景的结构。

二. set系列的使用

2.1 set类的介绍

set的文档
set的声明如下,T就是set的底层关键字的类型
在这里插入图片描述

  • set默认要求T支持小于比较,如果不支持或者想按自己的需求实现可以自己实现仿函数传给第二个模板参数。
  • set的底层存储数据是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数。
  • 一般情况下我们不需要传第二个和第三个模板参数。
  • set的底层是用红黑树实现,增删查的时间复杂度是O(logN),迭代器遍历走的是二叉搜索树的中序,所以是有序的。

2.2 set的构造和迭代器

  • set的构造
  1. 无参构造
    在这里插入图片描述

  2. 迭代器区间构造
    在这里插入图片描述
    这里的迭代器区间是任意类型容器的迭代器

  3. 拷贝构造
    在这里插入图片描述

  4. initializer_list initializer 列表构造
    在这里插入图片描述

  • set的迭代器

在这里插入图片描述
set的迭代器是单向迭代器,因此只支持++操作
在这里插入图片描述

2.3 set的增删查

2.3.1 set的增删查

  • set插入数据
  1. pair<iterator,bool> insert (const value_type& val)

在这里插入图片描述
插入单个数据,可以看出set是不允许插入相同的数据的

  1. void insert (initializer_list<value_type> il)
    在这里插入图片描述

  2. template void insert (InputIterator first, InputIterator last);
    迭代器区间插入
    在这里插入图片描述
    这里的迭代器也是任意容器的迭代器

  • set查找数据
  1. 查找val,返回val所在的迭代器,没有找到返回end()
    在这里插入图片描述
    在这里插入图片描述

  2. count在这里插入图片描述
    因为set中不允许有重复的值,所以count的返回值是1,就说明set中存有val数据,反之没有
    在这里插入图片描述
    set的find和算法库中的find,set中的时间复杂度是O(logN),因为走的是二叉搜索树的搜索逻辑;算法库中的时间复杂度是O(N),是遍历查找

  • set删除数据

在这里插入图片描述
返回值是删除位置下一元素的迭代器

  1. 删除⼀个迭代器位置的值
    iterator erase (const_iterator position);
    在这里插入图片描述
  2. 删除val,val不存在返回0,存在返回1
    size_type erase (const value_type& val);
    在这里插入图片描述
  3. 删除⼀段迭代器区间的值
    iterator erase (const_iterator first, const_iterator last);
    在这里插入图片描述

2.3.2 lower_bound和upper_bound

在这里插入图片描述

在这里插入图片描述
这里的it1返回的是5位置的迭代器,it2返回的是6位置的迭代器,运用这两个成员函数,我们也可以删除一段数据区间,因为erase删除的区间是左闭右开的区间,所以左迭代器我们使用lower_bound函数来找,右迭代器使用upper_bound函数中找。

void test_set02()
{
	set<int> st({ 1,2,3,4,5,60,20,70,100,98});
	// 删除20~98的数据
	//auto it1 = st.lower_bound(20);
	//auto it2 = st.upper_bound(98);// 因为这个函数返回的是大于98的迭代器,所以可以将98删除

	// 删除15~65的数据
	auto it1 = st.lower_bound(15);
	auto it2 = st.upper_bound(65);
	st.erase(it1, it2);
	for (auto a : st)
	{
		cout << a << " ";
	}
	cout << endl;
}

不用担心数据在set中不存在的问题,删哪段数据就相应的传哪些数据即可
在这里插入图片描述

2.3.3 迭代器失效问题

在这里插入图片描述
使用库里的erase之后更新迭代器是可以使用的
在这里插入图片描述

迭代器失效分为两种情况:
在这里插入图片描述
所以删除后的迭代器不要使用了,如果要使用,更新后再使用

2.4 multiset和set的差异

multiset和set的使用基本完全相似,主要区别在于multiset支持存储相同的值,那么insert/find/count/erase都围绕着支持存储相同的值而有所差异

  • insert
    在这里插入图片描述
    和set相比,multiset支持存储相等的值,也会排序,不会去重

  • find
    在这里插入图片描述
    find返回的是中序遍历的第一个x的迭代器,因为这样可以通过迭代器自增找到后续的值为x节点。
    在这里插入图片描述

  • count
    在这里插入图片描述
    count返回的是在set中值为x的实际节点个数

  • erase
    在这里插入图片描述
    会删除所有值为val的节点。

2.5 set的OJ题

  • 两个数组的交集 - 力扣(LeetCode)

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
  vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
     vector<int> ret;
     // 将数组中的数据放入set中
     set<int> s1(nums1.begin(),nums1.end());
     set<int> s2(nums2.begin(),nums2.end());
     // 指向起始位置的迭代器
     auto it1 = s1.begin();
     auto it2 = s2.begin();
     while(it1!=s1.end() && it2!= s2.end())
     {
         if(*it1 ==  *it2)// 相等放进新数组
         {
             ret.push_back(*it1);
             ++it1;
             ++it2;
         }
         else if(*it1<*it2)
             ++it1;
         else
             ++it2;
     }
     return ret;
 }
};
  • 环形链表II
    在这里插入图片描述
    在这里插入图片描述
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        set<ListNode*> s;
        ListNode* cur = head;
        while(cur)
        {
            if(s.count(cur) == 1)
            return cur;
            s.insert(cur);
            cur = cur->next;
        }
        return nullptr;
    }
};

三. map系列的使用

3.1 map类的介绍

map的参考文档
map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key支持小于比较,如果不支持或者需要的话可以自己实现仿函数传给第二个模板参数,map的底层存储数据的内存是在空间配置器申请的。一般情况下,我们不需要传后两个模板参数。map的底层是用红黑树实现,增删查改的时间复杂度是O(logN),迭代器遍历走的是中序,所以是按Key有序顺序遍历的。
在这里插入图片描述

3.2 pair类型介绍

map底层的红黑树节点中的数据,使用pair<Key,T>存储键值对数据,也就是说map存的是pair,pair中又存了key和value
pair的文档
pair的底层:

typedef pair<const Key, T> value_type;
template <class T1, class T2>
struct pair
{
	typedef T1 first_type;
	typedef T2 second_type;
	T1 first;
	T2 second;

	pair() // pair的构造调用了类型的默认构造,自定义类型自己的构造函数,内置类型同样
		:first(T1())
		,second(T2())
	{}

	pair(const T1& a, const T2& b) 
		:first(a)
		,second(b)
	{}

	template<class U, class V>
	pair(const pair<U, V>& pr) 
		:first(pr.first)
		,second(pr.second)
	{}
};
template <class T1, class T2>
inline pair<T1, T2> make_pair(T1 x, T2 y)
{
	return (pair<T1, T2>(x, y));
}

3.3 map的构造

map支持正向和反向迭代遍历,遍历默认按key的升序顺序,因为底层是二叉搜索树,迭代器遍历走的是中序,支持迭代器就意味着支持范围for,map支持修改value数据,不支持修改key数据,修改关键字数据就会破坏底层搜索树的结构。

  • 无参构造
    在这里插入图片描述
  • 迭代器区间构造
    在这里插入图片描述
  • 拷贝构造
    在这里插入图片描述

3.4 map的增删查

map的增加数据的接口,插入pair键值对数据,和set不同,但是查和删的接口只用关键字key和set是相同的,不过find返回iterator,不仅仅确认key在不在,还可以找到key映射的value,同时通过迭代器还可以修改value

  1. 增加数据
  • 单个数据插⼊,如果已经key存在则插入失败,key存在相等value不相等也会插入失败
    pair<iterator,bool> insert (const value_type& val);
    在这里插入图片描述

  • 列表插入,已经在容器中存在的值不会插入
    void insert (initializer_list<value_type> il);
    在这里插入图片描述

  • 迭代器区间插入
    在这里插入图片描述

  1. 查找
  • 查找key,返回key所在的迭代器,没有就返回end()
    在这里插入图片描述
  • 查找key,返回k的个数
    size_type count (const key_type& k) const;
    在这里插入图片描述
  1. 删除
    在这里插入图片描述
  • 删除⼀个迭代器位置的值
    iterator erase (const_iterator position);
    在这里插入图片描述
    删除第二个位置的值
  • 删除key,key存在返回0,存在返回1
    size_type erase (const key_type& k);
    在这里插入图片描述
  • 删除⼀段迭代器区间的值
    iterator erase (const_iterator first, const_iterator last);
    在这里插入图片描述

3.5 map数据的修改

前面提到map支持修改mapped_type数据,不支持修改key数据,修改关键字数据,破坏了底层搜索树的结构。map第一个支持修改的方式是通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map还有一个非常重要的修改接口operator[],但是operator[]不仅仅支持修改,还支持插入数据和查找数据,所以它是⼀个多功能复合接口需要注意从内部实现角度,map这里把我们传统说的value值,给的是T类型typedef为mapped_type。而value_type是红黑树结点中存储的pair键值对值。日常使用我们还是习惯将这里的T映射值叫做value。
在这里插入图片描述
operator[]
在这里插入图片描述

// operator的内部实现 
mapped_type& operator[] (const key_type& k)
{
	 pair<iterator, bool> ret = insert({ k, mapped_type() });
	 iterator it = ret.first;
	 return it->second;
}

在这里插入图片描述
正常插入是插入+修改的功能
插入失败是查找+修改的功能
返回值是节点value的引用
在这里插入图片描述

3.6 map和multimap的区别

multimap和map的使用基本完全类似,主要区别点在于multimap支持关键值key冗余,那么insert/find/count/erase都围绕着支持关键值key冗余有所差异,这里跟set和multiset完全⼀样,比如find时,有多个key,返回中序第一个。其次就是multimap不支持[],因为支持key冗余,[]就只能支持插入了,不能支持修改。
equal_range
返回key相等值的迭代器区间
在这里插入图片描述
在这里插入图片描述
把所有字符对应的value打印出来。

3.7 map的OJ题

  • 随机链表的复制
    在这里插入图片描述
class Solution {
public:
    Node* copyRandomList(Node* head) {
        Node* copyhead = nullptr,*copytail = nullptr;
        Node* cur = head;
        map<Node*,Node*> mp;
        // 拷贝节点,建立映射关系
        while(cur)
        {
            if(copyhead == nullptr)
            {
                copyhead = copytail = new Node(cur->val);
            }
            else{
                copytail->next = new Node(cur->val);
                copytail = copytail->next;
            }
            mp[cur] = copytail;
            cur = cur->next;
        }
        // 链接random节点
        cur = head;
        Node* copyNode = copyhead;
        while(cur)
        {
            if(cur->random == nullptr)
            {
                copyNode->random = nullptr;
            }
            else{
                copyNode->random = mp[cur->random];
                // cur->random是一个节点,在map中找这个节点的映射
                // 赋给拷贝节点的random指针	
            }
            cur = cur->next;
            copyNode = copyNode->next;
        }
        return copyhead;
    }
};

结语

以上我们就讲完了map和set的使用,希望对大家有所帮助,感谢大家的阅读,欢迎大家批评指正!

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

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

相关文章

DevOps持续集成

DevOps流程 第一步安装git 关闭防火墙 systemctl stop firewalld cd /usr/loacl vim docker-compose.yml docker search gitlab 拉取gitlab镜像 2.33GB docker pull gitlab/gitlab-ce:latestvim docker-compose.yml修改docker-compose.yml version: 3.1 services:gitlab:i…

TCP的“可靠性”(上)

目录 TCP的“可靠性”&#xff08;上&#xff09;确认应答&#xff08;可靠性传输的基础&#xff09;超时重传连接管理&#xff08;三次握手&#xff0c;四次挥手&#xff09; TCP的“可靠性”&#xff08;上&#xff09; 想必大家都或多或少的听说过TCP的特性&#xff1a;有连…

【C++】求第二大的数详细解析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述&#x1f4af;输入描述&#x1f4af;解题思路分析1. 题目核心要求2. 代码实现与解析3. 核心逻辑逐步解析定义并初始化变量遍历并处理输入数据更新最大值与次大值输…

Linux图形化工具推荐

1、MobaXterm MobaXterm Xserver with SSH, telnet, RDP, VNC and X11 - DownloadFree X server for Windows with tabbed SSH terminal, telnet, RDP, VNC and X11-forwarding - Downloadhttps://mobaxterm.mobatek.net/download.html 2、FinalShell FinalShell SSH工具,服…

SQL计算字段:拼接字段

为了说明如何使用计算字段&#xff0c;本文将通过一个简单的示例来展示如何将两列组合成一个标题。假设Vendors表包含供应商的名称和国家信息&#xff0c;我们希望生成一个报表&#xff0c;其中列出每个供应商的名称和所在国家&#xff0c;并且需要格式化名称显示&#xff0c;国…

【Spring项目】表白墙,留言板项目的实现

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;项目实现准备 1&#xff1a;需求 2&#xff1a;准备工作 &#xff08;1&#xff09;…

MySQL | 尚硅谷 | 第12章_MySQL数据类型精讲

MySQL笔记&#xff1a;第12章_MySQL数据类型精讲 文章目录 MySQL笔记&#xff1a;第12章_MySQL数据类型精讲第12章_MySQL数据类型精讲 1. MySQL中的数据类型2. 整数类型2.1 类型介绍2.2 可选属性2.2.1 M2.2.2 UNSIGNED2.2.3 ZEROFILL 2.3 适用场景2.4 如何选择&#xff1f;演示…

ElasticSearch常见面试题汇总

一、ElasticSearch基础&#xff1a; 1、什么是Elasticsearch&#xff1a; Elasticsearch 是基于 Lucene 的 Restful 的分布式实时全文搜索引擎&#xff0c;每个字段都被索引并可被搜索&#xff0c;可以快速存储、搜索、分析海量的数据。 全文检索是指对每一个词建立一个索引…

40分钟学 Go 语言高并发:负载均衡与服务治理

负载均衡与服务治理 一、知识要点总览 模块核心内容技术实现难度负载策略轮询、权重、最小连接数自定义负载均衡器中服务降级服务降级、熔断降级、限流降级Hystrix模式高熔断机制熔断器状态机、失败计数、自动恢复Circuit Breaker高限流设计令牌桶、滑动窗口、计数器Rate Lim…

克服大规模语言模型限制,构建新的应用方法——LangChain

大模型 大模型的出现和落地开启了人工智能(AI)新一轮的信息技术革命&#xff0c;改变了人们的生 活方式、工作方式和思维方式。大模型的落地需要数据、算力和算法三大要素。经过几 年发展&#xff0c;大模型的数据集(包括多模态数据集)制作已经形成了规约&#xff0c;Meta、Go…

LLM - 多模态大模型的开源评估工具 VLMEvalKit 部署与测试 教程

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/144353087 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 VLMEva…

jenkins邮件的配置详解

Jenkins邮件的配置涉及多个步骤和细节,以下是详细的配置指南: 一、前期准备 确定邮件服务:明确Jenkins将要使用的邮件服务,如QQ邮箱、163邮箱、公司邮箱(基于Microsoft 365或Exchange Server)等。获取SMTP配置信息:根据邮件服务类型,获取相应的SMTP服务器地址、端口号…

DCL语句和函数

1.DCL语句 DCL&#xff1a;数据控制语言&#xff0c;用来管理数据库用户&#xff0c;控制数据库的访问权限。 1.控制数据库有哪些用户可以访问。 2.控制每一个用户的访问权限。 1.1 DCL-管理用户 查询用户 USE mysql SELECT * FROM user; 创建用户 CREATE USER 用户名主…

[go-redis]客户端的创建与配置说明

创建redis client 使用go-redis库进行创建redis客户端比较简单&#xff0c;只需要调用redis.NewClient接口创建一个客户端 redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379",Password: "",DB: 0, })NewClient接口只接收一个参数red…

【NLP高频面题 - 分词篇】WordPiece 分词器是如何训练的?

【NLP高频面题 - 分词篇】WordPiece 分词器是如何训练的&#xff1f; 重要性&#xff1a;★★ &#x1f4af; NLP Github 项目&#xff1a; NLP 项目实践&#xff1a;fasterai/nlp-project-practice 介绍&#xff1a;该仓库围绕着 NLP 任务模型的设计、训练、优化、部署和应用…

机器学习决策树原理详解

一、引言 在当今蓬勃发展的人工智能与大数据领域&#xff0c;大模型正以前所未有的影响力改变着众多行业的格局。而决策树作为机器学习算法家族中的经典成员&#xff0c;以其简洁直观的特点和广泛的适用性&#xff0c;不仅能独立解决诸多实际问题&#xff0c;更是诸多先进大模…

[小白系列]Ubuntu安装教程-安装prometheus和Grafana

Docker安装prometheus 拉取镜像 docker pull prom/prometheus 配置文件prometheus.yml 在/data/prometheus/建立prometheus.yml配置文件。&#xff08;/data/prometheus/可根据自己需要调整&#xff09; global:scrape_interval: 15s # By default, scrape targets ev…

【Qt之·类QSettings·参数保存】

系列文章目录 文章目录 前言一、概述1.1 QSetting是什么1.2 为什么学习QSetting是重要的 二、不同存储位置的优缺点三、 QSetting的高级用法四、实例演示总结 前言 在当今的应用程序开发中&#xff0c;设置管理是一个至关重要的方面。应用程序的设置包括用户偏好、配置选项和其…

HCIP——VRRP的实验配置

一、VRRP的理论知识 1.1VRRP&#xff08;虚拟路由冗余协议&#xff09;的概述&#xff1a; 通过把几台路由设别联合组成一台虚拟的路由设备&#xff0c;既能够实现网关的备份&#xff0c;又能解决多个网关之间互相冲突的问题。 1.2VRRP状态机&#xff1a; VRRP协议状态机有…

从爱尔兰歌曲到莎士比亚:LSTM文本生成模型的优化之旅

上一篇&#xff1a;《再用RNN神经网络架构设计生成式语言模型》 序言&#xff1a;本文探讨了如何通过多种方法改进模型的输出&#xff0c;包括扩展数据集、调整模型架构、优化训练数据的窗口设置&#xff0c;以及采用字符级编码。这些方法旨在提高生成文本的准确性和合理性&am…