C++ STL初阶(14): map和set

1.关联式容器与键值对

 前导文章:C++ 二叉树进阶-CSDN博客

之前我们学习的线性的容器,如:vector deque list等都叫作序列式容器

与之对立的概念是关联式容器

关联式容器 也是用来存储数据的,与序列式容器不同的是,其 里面存储的是 <key, value> 结构的 键值对,在数据检索时比序列式容器效率更高

key就是键值,可以在容器中通过键值直接找到value.

比如:现在要建立一个英汉互译的字典,那该字典中必然 有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应 该单词,在词典中就可以找到与其对应的中文含义

我们在 前导文章中的代码的基础上继续往下写:

                             

在搜索二叉树中,有两种典型的模型:key模型和key_value模型

key模型对应set,key_value模型对应map


2. set

1. set 是按照一定次序存储元素的容器
2. set 中,元素的 value 也标识它 (value 就是 key ,类型为 T) ,并且每个 value 必须是唯一的。
set 中的元素不能在容器中修改 ( 元素总是 const) ,但是可以从容器中插入或删除它们。
3. 在内部, set 中的元素总是按照其内部比较对象 ( 类型比较 ) 所指示的特定严格弱排序准则进行排序。
4. set 容器通过 key 访问单个元素的速度通常比 unordered_set 容器慢,但它们允许根据顺序对
子集进行直接迭代。
5. set 在底层是用二叉搜索树 ( 红黑树 ) 实现的。

set有三个参数,元素类型、用于比较的仿函数(默认是less)、内存池。

set的操作:

插入操作只有insert(先不管emplace), 线性的数据结构才能push。

set的底层是二叉搜索树中的key模式,并且迭代器在底层默认走的是中序

二叉搜索树的中序遍历一定是有序的,而且还有去重的功能:

  set能够去重(insert了两个三最后还是只出了一个3): 

                       


set的构造:

在c++98下,set只有范围构造和拷贝构造两种使用方法。

可以用迭代器range构造:

因为节点也是存的指针,所以拷贝构造也是走树的深拷贝:

                                              

关于销毁:

在实现析构函数时:

因为析构函数不能有参数,所以我们需要通过在析构函数里套一个Destroy函数来完成析构:

~BSTree()
{Destroy(_root);_root = nullptr;
}

采用后序遍历一个一个销毁节点

为什么要套用?

因为树的销毁是采用递归销毁的办法,而析构函数不能传参,也就无法递归

同理,拷贝构造也是需要递归遍历,也需要封装。

不能直接用BST(const BSTNode<K,V>& Node)去递归拷贝

增删查改

删除最小值就是删除begin()对应的数据就行。

因为iterator走的是中序遍历,所以begin()对应的一定是最左值,而最左值就是最小的数据。

                                   

可以通过erase的返回值来判断是否erase成功:


关于erase删除的报错:

erase删除数值

比如erase(80) , 有80就删除成功,没有80就删除失败。但是删除失败不会报错。

erase删除迭代器:

但是如果使用find()返回的迭代器来删除,find又没有找到,就会报错。

因为find在没有找到的时候,返回值是end(),直接erase(end())就会报错。


查找:

库中的find是根据iterator全部遍历一遍,暴力查找

set中的find是按照搜索二叉树走的;

因此效率有很大差距。

还有个count函数用于计数:

但是由于搜索二叉树是一种不允许冗余数据的容器

所以在目前的使用情境下,该数据存在就返回1,不存在就返回0

                       

因此在set中,count函数一般直接用于判断元素是否存在。 


lower_bound  和 upper_bound(返回的都是>或>=val的第一个数据)

louwer_bound返回的就是set中>=val的第一个数据的iterator

如果没有合适的元素,返回的就是end()

upper_bound同理:

返回的是set中第一个>val的数据的iterator

直接看官网的测试用例:

但是根据cpp左闭右开的性质,lower_bound(30)返回的就是30

upper_bound(60)返回的就是稍大于60的值(此处应该是70),最终能把[30,70)区间里的所有数都删掉,所以删掉的还是[30,60]的数据


3. multiset

multiset是set的变种容器:允许有冗余的元素,允许修改容器里的元素。

multiset和set的区别:

因为multiset是可以插入相同元素的,所以在find时,multiset的find返回的是中序遇到的第一个值:

multiset更能解释count函数存在的意义了,count在multiset中就可以返回相同数值的节点的个数。

还有刚才的lower_bound和upper_bound两个功能下面的被我们忽视的功能:equal_range

equal_range :找到一个数据所有相同数值所在的左闭右开的区间(multiset中相同数据一定是紧挨着的)。

因为相等的数据一定是在相邻的位置。

equal_range返回的是一个pair

pair 中有两个数据,表示的是整个满足条件的区间。

pair是一种结构体。

equal_range返回的pair就是这个区间。


4. map 

map结构

map就是在本文开始处讲的基于搜索二叉树的<key,value>类型

pair是一个结构体模版,在map中统一规定所有的value_type都是pair类型的。

来观察下pair:

成员有first和second

                       

map的结构和set很像

最大的区别map支持方括号访问。(将在后文解释)


map使用(make_pair)

现在就不需要手搓平衡二叉树了,可以直接使用map来创建一个简易字典。

               

但是这样insert是不对的,在map内部,key和value不是分开存放的,而是一起被放在pair中的

正确的四种写法:

第一种,构造有名对象。

第二种,构造匿名对象。

第三种,是库中函数make_pair()对pair<T1,T2>的一种封装

                     

第四种,多参数构造函数可以用{}来进行隐式类型转换。

其实是用{"string","字符串"}构造了一个pair,然后被insert进去。

最后还有一种初始化的方法:

map<string, string> dict = { {"left", "左边"}, {"right", "右边"},{"insert", "插入"},{ "string", "字符串" } };

使用的是所有的容器都支持的initializer_list

外围花括号就是针对一个一个pair的initializer_list, 内层花括号就是隐式类型转换构造的pair

map的遍历: 

想要访问map中的元素,需要pair.去访问内部元素(如上图)。

还可以用->去访问。因为it是像指针一样的东西,之前在链表部分也讲过

库中是通过重载operator->()来使用:

这里的it是为了可读性而省略了一个箭头。

map访问数据多是使用operator->

查询时同理:

    


★★★map的方括号访问(重难点):

(mapped_type就是value_type , 也就是我们常说的value)

不同于传统的方括号访问,

参数是key,返回的是value的引用。换句话说,方括号是通过key去找value的

首先要知道map中insert的返回值:

insert返回的是一个pair
只要插入成功,bool的值就是true, iterator指向的是新插入成功的节点。

在 C++ 中,std::map 是一个基于红黑树实现的关联容器,它不允许插入具有相同键(key)的元素std::map 的每个键值对都是唯一的,键用于组织数据,因此它会自动根据键的顺序对元素进行排序。

如果你尝试插入一个已经存在的键,std::map 会拒绝这个插入操作,并且不会覆盖原有的键值对。如果你需要检查插入是否成功,可以使用 insert 方法,它会返回一个 pair,其中 pair.second 表示插入是否成功(如果键已存在,则为 false),而 pair.first 会指向插入的元素或者已存在的元素。

这就是下图为什么第二次insert"你好"这个键失败的原因

了解了insert的原理,我们再回过头来观察方括号访问:

将operator[]的逻辑写成函数:

             

所以,如果operator[k]的时候,k是一个没有出现过的key , map就会自动创建一个k为key,v为mapped_value()的匿名构造的pair,存在map中。

并且方括号返回的是mapped_value的引用,可以再对这个value的值进行修改。

所以这个operator[]是一个插入+修改的功能。

如果我们再调用一次operator[]:

              

第二次调用["left"],先查找有没有left,因为第一次插入已经创建了left了,所以第二次就找到了<"left","左边">,然后返回了"左边"的引用,通过赋值符号将"左边"改成了 "左边、剩余"

如果只调用一次这个:

                                             

相对于插入了一个"insert",V()的pair

小结:

                   

综上,map的方括号引用是一个复合功能。

所以使用方括号访问的时候要小心,否则一不小心就可能变成插入一个不希望插入的内容。

方括号访问了一个本来不存在的键也会导致插入一个不想插入的pair

使用这个特性,之前计数的代码就可以:

如果水果名字是第一次出现,那么就会在map中插入一个<新的水果名,0>,然后对返回值0进行一次++;如果水果名字不是第一次出现,就是直接对返回的(*iterator).second的value进行++


multimap

multimap map 的唯一不同就是: map 中的 key 是唯一的,而 multimap key 是可以
重复的 。并且multimap不支持方括号访问,inser返回的也只是iterator,而不是一个pair

 甚至还可以在<key,value>都相同的情况下insert一个一模一样的pair


5. 大试牛刀

138. 随机链表的复制 - 力扣(LeetCode) 

我们曾经使用C语言解决过这个问题,解决方法是在原链表的基础上,每一个节点后面都拷贝一份一样的,这样做的本质就是建立相对映射关系。

但是现在有了map可以自主建立映射关系了,就不需要上述麻烦了。

先遍历深拷贝一下节点:

                 

每深拷贝一个节点,就赛一个进入map,可以insert进去也可以直接通过方括号访问调用insert

                 

然后通过映射关系处理深拷贝的所有节点的random

class Solution {
public:Node* copyRandomList(Node* head) {Node* cur = head;map<Node*,Node*> NodeMap;Node* phead = nullptr;Node* ptail = nullptr;while(cur){if(ptail==nullptr){phead = ptail = new Node(cur->val);}else{ptail->next = new Node(cur->val);ptail = ptail->next;}  //每拷贝一个,就让被拷贝的和其对应的两个节点入mapNodeMap.insert(make_pair(cur,ptail));//也可以这样进入map//NodeMap[cur] = ptail;cur = cur->next;}//最后来处理randomcur = head;ptail = phead;while(cur){if(cur->random == nullptr){ptail->random = nullptr;}else{ptail->random = NodeMap[cur->random];}cur = cur->next;ptail = ptail->next;}return phead;}
};

判断是否有环

142. 环形链表 II - 力扣(LeetCode)

放在以前,我们需要利用追击问题的数学知识,推出:

在fast和slow相遇在meet点时,从头出发一个cur,cur和slow同时遍历,一定在环的入口相遇。

现在利用set插入时的特点,第一个插入失败的元素就是环的入口点。

class Solution {
public:ListNode *detectCycle(ListNode *head) {set<ListNode*> s;ListNode* cur = head;while(cur && (s.insert(cur)).second){cur = cur->next;}return cur;}
};

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

求交集或者差集,第一步建议:排序+去重

先使用set的迭代器区间构造:

                              

然后使用双指针:

class Solution {
public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {set<int> s1(nums1.begin(),nums1.end());set<int> s2(nums2.begin(),nums2.end());auto it1 = s1.begin();auto it2 = s2.begin();vector<int> ans;while(it1 != s1.end() && it2 != s2.end()){if(*it1 == *it2){ans.push_back(*it1);++it1,++it2;}else if(*it1 < *it2){++it1;}else{++it2;}}return ans;}
};

用set可以完美实现排序加去重。然后再利用一个双指针依次遍历

实践中,云平台的同步服务就需要以上的通过交集和并集的同步操作


692. 前K个高频单词 - 力扣(LeetCode) 

        这是一个映射+topk的结合问题,思路如下:

        先把words中的词全部加入到map<单词,出现次数>中去(同遍历水果名字并且计数)

为了让map中的数据根据出现次数来排序,又需要把map中的一个一个pair给放入vector中,然后自己传仿函数(根据出现次数和字典序来排序),比较完之后再将排在前面的k个数据给push到要返回的vector<string>中去。

但是要通过countMap.second来排序以便获得topk

但是排序(数据量小直接用sort,数据量大的时候必须用优先级队列)必须用支持随机迭代器的容器。

所以先将map中的数据入到一个vector中去

 不过对于pair自带的比大小:

不同于我们的期望,我们现在只希望按照次数去比较,所以需要我们自己去实现一个仿函数

然后再创建一个vector来储存答案。

但是发很多测试用例不能通过。

原因是忽略了一个题目的要求:

也不能直接在最后的vector中排序,因为只要求对出现次数相同的单词进行字典序排序

明明在加入到map中的时候已经经过了一次字典序的排序,为怎么现在又乱了?

因为sort的底层是快排,快排是不稳定排序。

可以使用stable_sort:

                

或者改写我们的仿函数:

注意,关于sort要传的用于比较的仿函数:

        返回值需要是bool并且要传两个参数 ,希望是降序就要保证第一个参数大于第二个参数为真。

但是此题的字典序是针对string的升序,所以后面的条件是小于号。

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

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

相关文章

【C++】检测TCP链接超时——时间轮组件设计

目录 引言 时间轮思想 设计的核心思路 完整代码 组件接口 个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 引言 对于高并发的服务器来说&#xff0c;链接是一种比较珍贵的资源&#xff0c;对不活跃的链接应该及时释放。判断连接是否活跃的策略是——在给定的时间内&#…

Redis中BigKey与MoreKey优化笔记

1.MoreKey 在Redis中&#xff0c;MoreKey问题通常指的是当数据库中的key数量非常多时&#xff0c;使用如KEYS *这样的命令去检索所有的key&#xff0c;这会导致Redis服务阻塞&#xff0c;影响正常业务。因为Redis是单线程操作的&#xff0c;执行这类命令时会占用大量时间&…

Arthas redefine(加载外部的.class文件,redefine到JVM里 )

文章目录 二、命令列表2.2 class/classloader相关命令2.2.3 redefine&#xff08;加载外部的.class文件&#xff0c;redefine到JVM里 &#xff09;举例1&#xff1a;加载新的代码&#xff0c;jad/mc 命令使用举例2&#xff1a;上传 .class 文件到服务器的技巧 二、命令列表 2.…

柯桥韩语学校|韩语每日一词打卡:회갑연[회가변]【名词】花甲宴

今日一词:회갑연 韩语每日一词打卡&#xff1a;회갑연[회가변]【名词】花甲宴 原文:인구 노령화에 따라서 요즘 회갑연보다는 고희연을 더 많이 지냅니다. 意思&#xff1a;随着人口老龄化&#xff0c;最近比起花甲宴&#xff0c;更多人办古稀宴。 【原文分解】 1、인구[인구]…

【BurpSuite】访问控制漏洞和权限提升 | Access control vulnerabilities (3-6)

&#x1f3d8;️个人主页&#xff1a; 点燃银河尽头的篝火(●’◡’●) 如果文章有帮到你的话记得点赞&#x1f44d;收藏&#x1f497;支持一下哦 【BurpSuite】访问控制漏洞和权限提升 | Access control vulnerabilities (3-6&#xff09; 实验三 Lab: User role controlled b…

【IoT-NTN】系统消息SIB31信令分析

3GPP卫星通信发展迅速&#xff0c; TS36.331 R17中新增SIB31携带星历信息&#xff0c;本文对SIB31的信令内容进行了分析。 SystemInformationBlockType31 分析报告 一、概述 本文档详细描述了SystemInformationBlockType31&#xff08;简称SIB31&#xff09;的结构和内容&am…

[Redis][集群][上]详细讲解

目录 0.前言1.基本概念2.数据分片算法0.前言1.哈希求余2.一致性哈希算法3.哈希槽分区算法(Redis使用) 0.前言 说明&#xff1a;该章节相关操作不需要记忆&#xff0c;理解流程和原理即可&#xff0c;用的时候能自主查到即可 1.基本概念 哨兵模式提高了系统的可用性&#xff0…

试用Debian12.7和Ubuntu24.4小札

Debian GNU/Linux 12 (bookworm)和Ubuntu 24.04.1 LTS是现阶段&#xff08;2024年9月26日&#xff09;两个发行版的最新版本。Ubuntu Server版本默认就不带桌面&#xff08;ubuntu-24.04-live-server-amd64.iso&#xff09;&#xff0c;这个默认就是最小化安装&#xff08;安装…

Moshi: a speech-text foundation model for real time dialogue

视频号 挺神奇的东西 整下来 kyutai-labs/moshi (github.com) git clone https://github.com/kyutai-labs/moshi.git 在线体验 moshi.chat 结束后 点击Download audio Download video 可以下载音频与视频 &#xff08;不过是webm格式&#xff09; 发行版 已上传至资源 小…

springboot+大数据基于数据挖掘的招聘信息可视化大屏系统【内含源码+文档+部署教程】

博主介绍&#xff1a;✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久&#xff0c;选择我们就是选择放心、选择安心毕业✌ &#x1f345;由于篇幅限制&#xff0c;想要获取完整文章或者源码&#xff0c;或者代做&am…

【C++】 vector 迭代器失效问题

【C】 vector 迭代器失效问题 一. 迭代器失效问题分析二. 对于vector可能会导致其迭代器失效的操作有&#xff1a;1. 会引起其底层空间改变的操作&#xff0c;都有可能是迭代器失效2. 指定位置元素的删除操作--erase3. Linux下&#xff0c;g编译器对迭代器失效的检测并不是非常…

数论——数数(找质因数个数),三位出题人(组合数学,快速幂)

数数&#xff08;找质因数个数&#xff09; 题目描述 登录—专业IT笔试面试备考平台_牛客网 运行代码&#xff08;通过率一半&#xff09; #include <iostream> #include <vector> using namespace std; const int N5e66; int n; vector<bool>vis; vo…

vmware-toolbox安装,VMware虚拟机访问win10共享目录

问题&#xff1a;VMware界面无法安装vmware-toolbox&#xff0c;共享目录设置失败 解决方法&#xff1a; VMware设置 共享文件夹 ubuntu24 vm中运行vmware-toolbox-cmd -v 检查版本 vm运行sudo apt install open-vm-tools // vm可能需要重启 vm的 /mnt 目录下如果没有 hgfs…

骨传导耳机哪个牌子好?年度五大热门骨传导耳机推荐清单来了!

近年来&#xff0c;骨传导耳机以其独特的传音方式和开放耳道的设计&#xff0c;逐渐成为运动爱好者和追求健康生活方式人群的新宠。与传统耳机相比&#xff0c;骨传导耳机不仅能够保护听力&#xff0c;还能在享受音乐的同时保持对周围环境的警觉。 随着骨传导耳机市场的不断壮…

1.MySQL的安装

目录 下载安装包 安装前环境的准备 正式安装 下载安装包 MySQL安装网址:https://www.mysql.com/cn/ 进去之后就是上面这个页面&#xff0c;进行汉化的时候将这个网页拉至最下&#xff0c;右下角点成中文就可以&#xff0c;如下这个页面。 回到页面顶端&#xff0c;点击下载&a…

并发编程---线程与进程

业务场景&#xff1a;小明去理发店理发。 小明去理发店理发&#xff0c;完成理发需要吹&#xff0c;剪&#xff0c;洗、理的过程。由这个场景我们引用进程和线程这两个 概念。 一.进程 1.什么是进程 进程是具有独立功能的程序关于某个数据集合上的一次运行活动&#xff0c;是…

基于Hadoop的NBA球员大数据分析及可视化系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

MySQL InnoDB MVCC数据结构分析

1、概述 MVCC&#xff08;Multiversion Concurrency Control&#xff09;多版本并发控制&#xff0c;通过维护不同的版本号&#xff0c;提供一种很好的并发控制技术&#xff0c;这种技术能够使读写操作不冲突&#xff0c;提升并发性能。 MySQL InnoDB存储引擎&#xff0c;在更…

Colorful/七彩虹将星X17 XS 22 Win11原厂OEM系统 带COLORFUL一键还原

安装完毕自带原厂驱动和预装软件以及一键恢复功能&#xff0c;自动重建COLORFUL RECOVERY功能&#xff0c;恢复到新机开箱状态。 【格式】&#xff1a;iso 【系统类型】&#xff1a;Windows11 原厂系统下载网址&#xff1a;http://www.bioxt.cn 注意&#xff1a;安装系统会…

设计模式、系统设计 record part02

软件设计模式&#xff1a; 1.应对重复发生的问题 2.解决方案 3.可以反复使用 1.本质是面向对象 2.优点很多 1.创建型-创建和使用分离 2.结构型-组合 3.行为型-协作 571123种模式 UML-统一建模语言-Unified Modeling Language 1.可视化&#xff0c;图形化 2.各种图&#xff08;9…