【C++】STL-map的使用

目录

1、map的简述

2、map的使用

2.1 insert 

2.2 operator*、operator->

2.3 operator[]

3、multimap


1、map的简述

map与set一样是关联式容器

map就相当于二叉搜索树中的KV模型,底层是使用红黑树实现的,仿函数默认是less,即比根小的往左走,比根大的往右走。但是,map中的Key和Value是组合在一起的,是一个新的数据结构pair。pair称为键值对。map中也是不能键值冗余的

2、map的使用

map大部分是和set相似的,这里就介绍几个与set有较大区别的成员函数

2.1 insert 

在pair中,两个成员变量称为first和second。注意:map中结点的first是const的,一旦放入,不允许修改

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)
{}
};

所以,向map中插入元素时,不能将两个值分开写,应该将两个值放入一个pair才能放入

void test_map1()
{map<string, string> dict;dict.insert("left", "左边");
}

像上面这样的写法就是错的,下面介绍4种写法

pair中有一个make_pair函数是用来创建一个pair对象的

void test_map1()
{map<string, string> dict;// 1、定义有名对象pair<string, string> kv1("left", "左边");dict.insert(kv1);// 2、定义匿名对象dict.insert(pair<string, string>("right", "右边"));// 3、用make_pair创建一个对象dict.insert(make_pair("insert", "插入"));// 4、用花括号括起来,多参数的隐式类型转换dict.insert({ "string","插入" });
}

map初始化是可以使用迭代器区间初始化的

void test_map2()
{map<string, string> dict = { {"left","右边"},{"right","右边"},{"insert","插入"} };
}

外层的{}是initializer_list,内层的{}是隐式类型转换

2.2 operator*、operator->

pair是不支持流插入和流提取的,所以想要获得map的迭代器中的值不能直接对迭代器解引用

void test_map2()
{map<string, string> dict = { {"left","右边"},{"right","右边"},{"insert","插入"} };map<string, string>::iterator it = dict.begin();while (it != dict.end()){cout << (*it) << " ";++it;}
}

此时cout处是会报错的,因为*it获得的是pair对象,而pair对象又不支持流提取和流插入

void test_map2()
{map<string, string> dict = { {"left","右边"},{"right","右边"},{"insert","插入"} };map<string, string>::iterator it = dict.begin();while (it != dict.end()){cout << (*it).first << "-" << (*it).second << endl;++it;}
}

因为pair里面存的数据类型支持流插入和流提取,结果是,遍历出的顺序与插入顺序是不同的,因为是按Key,即pair的first进行了排序

不过,最方便的其实是使用->来访问pair中的数据

void test_map2()
{map<string, string> dict = { {"left","右边"},{"right","右边"},{"insert","插入"} };map<string, string>::iterator it = dict.begin();while (it != dict.end()){cout << it->first << "-" << it->second << endl;++it;}
}

这里是省略了一个->的,前面也有讲过

迭代器的operator*是返回里面数据的引用,迭代器的operator->是返回里面数据的指针。其实当容器的数据存的是一个结构体时,若要访问迭代器里面的数据,是没办法*it的,需要(*it).xxx,此时更推荐使用opertaor->

可以使用迭代器则一定可以使用范围for

void test_map3()
{map<string, string> dict = { {"left","右边"},{"right","右边"},{"insert","插入"} };map<string, string>::iterator it = dict.begin();for (const auto& e : dict){cout << e.first << "-" << e.second << endl;}for (auto& [x, y] : dict){cout << x << "-" << y << endl;}
}

有两种写法,第一种写法一定要使用引用,防止深拷贝。第二种写法叫做结构化绑定,是C++17的内容,同样要加引用,first给x,second给y

2.3 operator[]

operator[]是map特有的,set中没有

此时,如果我们要统计出一个字符串数组中,每个字符串出现的次数,则可以使用map,insert配合find

void test_map4()
{string arr[] = { "苹果","西瓜","苹果","西瓜","苹果","梨","香蕉" };map<string, int> countTree;for (const auto& str : arr){// 先查找水果在不在搜索树中// 1、不在,说明水果第一次出现,则插入<水果,1>// 2、在,则查找到的结点中水果对应的次数++auto ret = countTree.find(str);if (ret == countTree.end())countTree.insert({ str,1 });elseret->second++;}
}

当然,也可以使用operator[]

void test_map5()
{string arr[] = { "苹果","西瓜","苹果","西瓜","苹果","梨","香蕉" };map<string, int> countTree;for (const auto& str : arr){countTree[str]++;}
}

这里是传入str后,若map中没有,则插入并++,若有,则返回这个Key对应的value,然后对value++

我们重点看最下面哪一行,operator[]底层是调用insert实现的

insert的返回值是一个pair,插入成功,pair的first是新插入元素的迭代器,second是true,插入失败,说明这个key已经存在了,pair的first指向与key相等的迭代器,second位false。所以insert也可以通过返回的pair来判断是否插入成功。插入成功时,这个结点的first用传过来的值,second调用对应类型的默认构造函数。像上面哪里,若这个水果类型还没有,则插入后,second会调用int的默认构造,也就是构造成0,然后再++,就变成1

void test_map6()
{map<string, string> dict;dict["left"] = "左边";
}

像上面这段代码,会发现key为"left"的没有,所以构造一个pair,这个pair的first是"left",second调用string的默认构造,也就是一个空字符串"",然后用这个pair去构造一个map的结点插入

所以,dict[key],若key存在是查找,若不存在是插入,同时还能修改key对应的value。但在用来查找时要注意,若这个值不存在,会变成插入

3、multimap

multimap是允许键值冗余的map

map插入时,只比较key,若插入的key已经存在,则不进行操作,不会修改对应的value

void test_map6()
{map<string, string> dict;dict.insert({ "left","左边" });dict.insert({ "left","右边" });dict.insert({ "left","插入" });
}

像这里,left的value是左边

multimap插入时,无论key是否存在,都插入,并且没有operator[]

其他与map的差别,与set和multiset类似,这里就不过多介绍了

4、例题

学习了set和map后,可以对以前学过的一些题进行更新解法

4.1 复杂链表的复制

在之前,是将每一个结点复制,然后链接到其后面,通过这样的方式建立起拷贝结点与原结点的映射关系,这样拷贝结点就可以通过与原结点的关系来复制random,复制好random后,就可以将两个链表分开,返回拷贝的链表即可

class Solution {
public:Node* copyRandomList(Node* head) {if(head == nullptr) return head;// 在原链表的每一个结点后面加一个复制的结点Node* cur = head;while(cur){Node* copy = new Node(cur->val);copy->next = cur->next;cur->next = copy;cur = copy->next;}// 复制随机指针cur = head;while(cur){Node* copy = cur->next;if(cur->random == nullptr) copy->random = nullptr;else copy->random = cur->random->next;cur = copy->next;}// 分成两个链表Node* copyHead = head->next;cur = head;while(cur){Node* copy = cur->next;Node* next = cur->next->next;if(next)copy->next = next->next;elsecopy->next = nullptr;cur->next = next;cur = next;}return copyHead;}
};

这样是比较麻烦的,我们学习了map后,就可以利用map将原结点与拷贝结点建立关系,map<ListNode*, ListNode*>,这样就可以通过原结点来找到对应的拷贝结点了

class Solution {
public:Node* copyRandomList(Node* head) {Node* copyHead = nullptr,*copyTail = nullptr;Node* cur = head;map<Node*,Node*> OriginalCopy; // 创建一个原来结点与复制节点对应的mapwhile(cur){if(copyHead == nullptr){copyHead = copyTail = new Node(cur->val);}else{copyTail->next = new Node(cur->val);copyTail = copyTail->next;}OriginalCopy[cur] = copyTail; // 这样通过operator[]传入一个原结点,就能返回这个原结点对应的拷贝结点cur = cur->next;}cur = head;copyTail = copyHead;while(cur){if(cur->random == nullptr){copyTail->random = nullptr;}else{copyTail->random = OriginalCopy[cur->random];}cur = cur->next;copyTail = copyTail->next;}return copyHead;}
};

实际上,要用一个值去找另一个值都可以用map 

4.2 环形链表

在之前,我们是定义一个快指针和一个慢指针,快指针一次走一步,慢指针一次走两步,若快慢指针在移动过程中能够相遇,则说明有环,若快指针走到nullptr了,说明没环

class Solution {
public:bool hasCycle(ListNode *head) {if(head == nullptr) return false;ListNode* slow = head,*fast = head;while(fast && fast->next){slow = slow->next;fast = fast->next->next;if(slow == fast) return true;}return false;}
};

现在我们可以使用set,用set保存每个结点,若指针走到nullptr了,则没有环,若插入失败,则说明有环,并且插入失败的这个结点,就是入环的结点

class Solution {
public:bool hasCycle(ListNode *head) {set<ListNode*> s;ListNode* cur = head;while(cur){// 也可以auto ret = s.insert(cur);pair<set<ListNode*>::iterator,bool> ret = s.insert(cur);if(ret.second == false) return true;cur = cur->next;}return false;}
};

4.3 环形链表II

在之前,我们是先找到快、慢指针相遇的那个结点,然后定义一个指针从头结点出发,一个指针从相遇结点出发,这两个指针相遇的结点就是入环结点

class Solution {
public:ListNode *detectCycle(ListNode *head) {if(head == nullptr) return nullptr;ListNode* slow = head,*fast = head;while(fast && fast->next){slow = slow->next;fast = fast->next->next;if(slow == fast) break;}if(fast == nullptr || fast->next == nullptr) return nullptr;while(head != slow){head = head->next;slow = slow->next;}return head;}
};

现在我们可以使用set,用set保存每个结点,若指针走到nullptr了,则没有环,若插入失败,则说明有环,并且插入失败的这个结点,就是入环的结点

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

4.4 两个数组的交集

首先,我们需要使用两个set对nums1和nums2分别进行排序和去重,然后比较两个set,若两个元素相等,就是交集,放如vector后,两个迭代器同时++,若其中一个迭代器指向的元素比较小,则小的那个迭代器++

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());vector<int> ret;// set排过序,依次比较,若相等则是交集auto it1 = s1.begin();auto it2 = s2.begin();while(it1 != s1.end() && it2 != s2.end()){if(*it1 < *it2) it1++;else if(*it1 > *it2) it2++;else{ret.push_back(*it1);it1++;it2++;}}return ret;}
};

4.5 两个数组的交集II

在上一题,我们只需要向vector中放入交集中的一个,这道题需要放入多个,所以需要使用map

class Solution {
public:vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {map<int,int> m1;map<int,int> m2;vector<int> ret;for(auto e : nums1)m1[e]++;for(auto e : nums2)m2[e]++;auto it1 = m1.begin();auto it2 = m2.begin();while(it1 != m1.end() && it2 != m2.end()){if(it1->first < it2->first) ++it1;else if(it1->first > it2->first) ++it2;else{int x = min(it1->second,it2->second);for(int i = 0;i < x;i++) ret.push_back(it1->first);++it1;++it2;}}return ret;}
};

在云存储中就有利用到计算交集和差集的算法,假设同一个用户有手机和iPad两个设备,并且都开启了云存储,则在手机上或iPad上删除了一个东西,此时是不会真正删除的,过一会当同步服务开启后,会启动计算差集和交集的算法,若手机或iPad中有一个与云存储的数据不一样,则将云存储的数据同步到手机或iPad上。只有去云存储上删除了才是真的删除了。同步服务会计算出一个差集和一个交集。

差集进行同步

交集进行比对:每个文件都会有一个属性:最后修改时间。若修改时间变了,则进行同步。修改是在手机或iPad上,同步至云存储 

4.6 前K个高频单词

这道题我们可以使用map<string,int>来存储字符串和这个字符串出现的频次,但是map是根据pair中的first,也就是string来进行排序的,并不是根据频次排序的。所以我们需要额外使用priority_queue或者sort来对map中的频次进行排序。因为这道题数据量不大,所以使用sort为例,数据量大时最好使用priority_queue。因为map的迭代器是随机迭代器,所以不能使用sort。所以使用vector<pair<string, int>>来存储map中的数据,这样就可以实现排序了。但是sort默认是根据vector中存储的对象进行排序,也就是根据pair来进行排序,pair是支持比较大小的,两个pair对象实现比较first,first相等再比较second

我们在这里需要的只是比较pair对象的second即可,并且是排降序,所以使用仿函数

class Solution {
public:struct Compare{bool operator()(const pair<string,int>& x,const pair<string,int>& y){return x.second > y.second;}};vector<string> topKFrequent(vector<string>& words, int k) {map<string, int> countMap;for(auto e : words)     countMap[e]++;vector<pair<string, int>> v(countMap.begin(),countMap.end());// 降序sort(v.begin(),v.end(),Compare()); // sort需要传一个仿函数的对象,这里使用匿名对象vector<string> ret;for(int i = 0;i < k;i++){ret.push_back(v[i].first);}return ret;}
};

但这个代码只能过一部分的测试用例

此时我们会发现,找出来的k个字符串是一样的,只是排列的顺序不同导致错误

题目中有:返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。

也就是说,我们找对了,只是因为没有将出现频次相同的字符串按字典序排序输出。我们可以将排序完的vector输出出来看一下

class Solution {
public:struct Compare{bool operator()(const pair<string,int>& x,const pair<string,int>& y){return x.second > y.second;}};vector<string> topKFrequent(vector<string>& words, int k) {map<string, int> countMap;for(auto e : words)     countMap[e]++;vector<pair<string, int>> v(countMap.begin(),countMap.end());// 降序sort(v.begin(),v.end(),Compare()); // sort需要传一个仿函数的对象,这里使用匿名对象for(const auto& e : v){cout<<e.first<<":"<<e.second<<endl;}vector<string> ret;for(int i = 0;i < k;i++){ret.push_back(v[i].first);}return ret;}
};

会发现确实是因为没有将出现频次相同的字符串按字典序排序输出

在将字符串和出现频次放入map中完成时,同一个频次的字符是按字典序排序的,并且vector刚被初始化好时,相同频次的字符串也是按字典序排序的。之所以sort完了之后,相同频次的字符串就不是按字典序的顺序排序是因为sort不是稳定的排序,只有稳定的排序才能保证相同的值的相对顺序不变。头文件algorithm中,有stable_sort是稳定的排序,可以使用这个替代sort

class Solution {
public:struct Compare{bool operator()(const pair<string,int>& x,const pair<string,int>& y){return x.second > y.second;}};vector<string> topKFrequent(vector<string>& words, int k) {map<string, int> countMap;for(auto e : words)     countMap[e]++;vector<pair<string, int>> v(countMap.begin(),countMap.end());// 降序stable_sort(v.begin(),v.end(),Compare()); // sort需要传一个仿函数的对象,这里使用匿名对象vector<string> ret;for(int i = 0;i < k;i++){ret.push_back(v[i].first);}return ret;}
};

这样就可以了,当然,我们也可以仍然使用sort,只是需要修改一下仿函数,当出现频次不同时,按出现频次排序,当出现频次相同时,按string的字典序升序排序。并且若说使用优先级队列只能用这种方法,因为优先级队列没有稳定不稳定一说

class Solution {
public:struct Compare{bool operator()(const pair<string,int>& x,const pair<string,int>& y){return x.second > y.second || (x.second == y.second && x.first < y.first);}};vector<string> topKFrequent(vector<string>& words, int k) {map<string, int> countMap;for(auto e : words)     countMap[e]++;vector<pair<string, int>> v(countMap.begin(),countMap.end());// 降序sort(v.begin(),v.end(),Compare()); // sort需要传一个仿函数的对象,这里使用匿名对象vector<string> ret;for(int i = 0;i < k;i++){ret.push_back(v[i].first);}return ret;}
};

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

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

相关文章

在 PostgreSQL 中如何实现数据的加密存储?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 在 PostgreSQL 中如何实现数据的加密存储&#xff1f;一、为什么要进行数据加密存储&#xff1f;二、P…

如何证明员工有泄密行为,哪款软件可以提供这样的帮助?

如果员工泄密&#xff0c;如何证明员工有泄密行为&#xff1f; 证明员工有泄密行为通常需要以下几个步骤&#xff1a; 监控与记录&#xff1a;通过DLP&#xff08;数据防泄漏&#xff09;系统实时监控员工的行为&#xff0c;包括文件操作、数据传输、邮件发送等。分析行为&am…

RESTful API设计指南:构建高效、可扩展和易用的API

文章目录 引言一、RESTful API概述1.1 什么是RESTful API1.2 RESTful API的重要性 二、RESTful API的基本原则2.1 资源导向设计2.2 HTTP方法的正确使用 三、URL设计3.1 使用名词而非动词3.2 使用复数形式表示资源集合 四、请求和响应设计4.1 HTTP状态码4.2 响应格式4.2.1 响应实…

Linux中进程的控制

一、进程的创建 1、知识储备 进程的创建要调用系统接口&#xff0c;头文件 #include<unistd.h> 函数fork() 由于之前的铺垫我们现在可以更新一个概念 进程 内核数据结构&#xff08;task_struct, mm_struct, 页表....&#xff09; 代码 数据 所以如何理解进程的独…

C++进阶 之 【C++11】部分简单语法详细讲解(带你先入门学习C++11)

目录 一、C11简介 二、列表初始化 1.{} 初始化 2.std::initializer_list 三、变量类型推导 1.auto 2.decltype 3.nullptr 四、新增加容器---静态数组array、forward_list以及unordered系列 1.静态数组 array 2.单链表 forward_list 3.unordered_map 4.unordered_s…

前端面试题(JS篇七)

一、SQL 注入攻击&#xff1f; SQL 注入攻击指的是攻击者在 HTTP 请求中注入恶意的 SQL 代码&#xff0c;服务器使用参数构建数据库 SQL 命令时&#xff0c;恶意 SQL 被一起构 造&#xff0c;破坏原有 SQL 结构&#xff0c;并在数据库中执行&#xff0c;达到编写程序时意料之外…

shell详细介绍(清晰明了)

一、shell的介绍 Shell ⼀个命令解释器&#xff0c;它接收应⽤程序/⽤户命令&#xff0c;然后调⽤操作系统内核。 Shell还是⼀个功能强⼤的编程语⾔&#xff0c;易编写、易调试、灵活性强。 (1) Linux提供的shell解释器有 (2) bash 和 sh的关系 (3) Centos默认的Shell解析器…

2850. 将石头分散到网格图的最少移动次数 Medium

给你一个大小为 3 * 3 &#xff0c;下标从 0 开始的二维整数矩阵 grid &#xff0c;分别表示每一个格子里石头的数目。网格图中总共恰好有 9 个石头&#xff0c;一个格子里可能会有 多个 石头。 每一次操作中&#xff0c;你可以将一个石头从它当前所在格子移动到一个至少有一条…

C++ | Leetcode C++题解之第240题搜索二维矩阵II

题目&#xff1a; 题解&#xff1a; class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int m matrix.size(), n matrix[0].size();int x 0, y n - 1;while (x < m && y > 0) {if (matrix[x][y] targ…

nodejs安装+踩坑报错解决

下载Node.js安装包 官网下载地址&#xff1a;http://nodejs.cn/download/&#xff0c;根据自己电脑选择32位还是64位&#xff0c; 下载地址 选择合适的版本下载 X86是32位的&#xff0c;X64是64位的&#xff0c;我们一般是下载win版X64的msi文件的是点击可以直接启动安装程序的…

Vue 对接海康威视,实现摄像头画面展示

文章目录 需求分析1. 下载2. 安装3. new 一个WebControl 插件相关实例 需求 项目中集成海康威视&#xff0c;实现摄像头画面展示 分析 1. 下载 传送门&#xff1a;官方插件包和文档下载 2. 安装 &#xff08;1&#xff09;下载完成后打开 &#xff08;2&#xff09;在项…

30_Swin-Transformer网络结构详解

1.1 简介 Swin Transformer 是一种用于计算机视觉任务的新型深度学习架构&#xff0c;由微软亚洲研究院于2021年提出。它结合了Transformer模型在序列数据处理上的强大能力与卷积神经网络&#xff08;CNN&#xff09;在图像识别中的高效局部特征提取优势&#xff0c;特别适用于…

《数据结构》预备

在学习数据结构之前&#xff0c;需要预先准备学习的C语言知识是&#xff1a;自定义类型--结构体类型。 本节主要讲的内容有&#xff1a; 1.结构体类型的声明 2.结构体变量的创建和初始化 3.结构成员的访问操作符 4.结构体传参 5.结构体内存对齐 6.结构体实现位段(位域) 正文开…

verilog实现ram16*8 (vivado)

module ram_16x2 (input clk, // 时钟信号input we, // 写使能input en, // 使能信号input [3:0] addr, // 地址线input [1:0] datain, // 输入数据线output reg [1:0] dataout // 输出数据线 );// 定义存储器数组reg [1:0] mem [15:0];always (posedge…

影响转化率的多元因素分析及定制开发AI智能名片S2B2C商城系统小程序的应用案例

摘要&#xff1a;在互联网时代&#xff0c;转化率是衡量营销活动成功与否的关键指标。本文首先分析了影响转化率的多种因素&#xff0c;包括活动页面的设计、活动的限时性、主题文案的吸引力、从众心理的运用&#xff0c;以及最核心的产品质量与优惠力度。接着&#xff0c;本文…

Linux 13:网络编程1

1. 预备知识 1-1. 理解源IP地址和目的IP地址 在IP数据包头部中&#xff0c;有两个IP地址&#xff0c;分别叫做源IP地址&#xff0c;和目的IP地址。 我们光有IP地址就可以完成通信了嘛&#xff1f;想象一下发qq消息的例子&#xff0c;有了IP地址能够把消息发送到对方的…

【周记】2024暑期集训第一周

例题记录 Together 题目解析 输入n个数&#xff0c;你可以将这些数分别1&#xff0c;-1或者保持不变&#xff0c;尽可能多的将这些数变成同一个数x&#xff0c;输出x的个数。 算法思路 每个数都有3种情况&#xff0c;那么只需要将所有情况得到的数&#xff0c;每一个的个数…

【Qt】常用控件 Q widget的enabled属性,geometry属性

Qt是一个实现图形化程序的程序。为了便于我们开发&#xff0c;Qt为我们提供了许多“控件”。我们需要熟悉并掌握这些控件的使用。 一.什么是控件 控件是构成⼀个图形化界⾯的基本要素. 示例一&#xff1a; 像上述⽰例一中的,按钮,列表视图,树形视图,单⾏输⼊框,多⾏输⼊框,滚动…

Web开发:图片九宫格与非九宫格动态切换效果(HTML、CSS、JavaScript)

目录 一、业务需求 二、实现思路 三、实现过程 1、基础页面 2、图片大小调整 3、图片位置调整 4、鼠标控制切换 5、添加过渡 四、完整代码 一、业务需求 默认显示基础图片&#xff1b; 当鼠标移入&#xff0c;使用九宫格效果展示图片&#xff1b; 当鼠标离开&#…

SpringCloud—08—高级之SpringCloud Alibaba中—Sentinel

文章目录 提前预知18、Sentinel是什么&#xff1f;18.1、sentinel是什么&#xff1f;18.2、Sentinel下载安装运行18.3、Sentinel初始化监控18.4、Sentinel流控规则1、流控规则基本介绍2、流控规则之-QPS-直接-快速失败3、流控规则之-线程数-直接失败4、流控规则之-QPS-关联-快速…