C++(set和map详解,包含常用函数的分析)

set

set是关联性容器

set的底层是在极端情况下都不会退化成单只的红黑树,也就是平衡树,本质是二叉搜索树.

set的性质:set的key是不允许被修改的

使用set需要包含头文件

set<int> s;s.insert(1);s.insert(1);s.insert(1);s.insert(1);s.insert(2);s.insert(56);s.insert(7);s.insert(7);s.insert(7);s.insert(3);for (auto e : s){cout << e << " ";}cout << endl;set<int>::iterator it = s.begin();while (it != s.end()){cout << *it << " ";it++;}cout << endl;

set中是没有重复的,重复的元素是无法插入set的,有时候去重可以选择使用set.可以避免使用去重算法.

set的遍历方式:

  • 迭代器
  • 范围for(范围for的底层就就是迭代器.)

Find成员函数:

iterator find (const value_type& val) const;
  • Find函数找不到就返回set::end,指向最终的end迭代器.

  • set的Erase方法的参数是迭代器的时候:参数必须是有效的迭代器位置,否则会报错,当参数是一个key的时候,不管该值在不在都不会报错.

  • set的count函数:用于计算指定的元素的个数

    size_type count (const value_type& val) const;
    
  • lower_bound函数和upper_bound函数:

    iterator lower_bound (const value_type& val) const;
    iterator upper_bound (const value_type& val) const;
    

    例如:要删除set中[3,5]的值,如何删除?
    首先想到使用的是先一个一个找,接着使用erase进行删除,但是这个区间的断点值不一定存在.而且erase在删除不存在的迭代器的时候会报错,并且erase的删除区间是左闭右开的.而我们找不到这个的端点值.

    所以在想要删除某个闭区间的值的时候,我们使用erase,但是要给给erase提供左闭右开区间的迭代器的位置.左边闭区间的位置使用的是lower_bound提供,右边开区间的参数是upper_bound提供.接着将这个两个函数的返回值传递给erase即可.

    注意:如果想要删除[3,8]的数,那么lower_bound和upper_bound的参数就要分别提供3和8

    void test_set4()
    {set<int> s;s.insert(1);s.insert(2);s.insert(3);s.insert(4);s.insert(5);s.insert(6);s.insert(7);s.insert(8);// 删除[3,8]set<int>::iterator start = s.lower_bound(3);  // >=val的最小值set<int>::iterator finish = s.upper_bound(8); // >val最小值s.erase(start, finish);for (auto e : s) {cout << e << " ";}
    }
    

    lower_bound和upper_bound用于查找某一段区间的元素.通过获取到左闭右开的区间,可以更好的遍历.

    // 查找3-8之间的元素
    void test_set5()
    {set<int> s;s.insert(1);s.insert(2);s.insert(3);s.insert(4);s.insert(5);s.insert(6);s.insert(7);s.insert(8);// 删除[3,8]set<int>::iterator start = s.lower_bound(3);  // >=val的最小值set<int>::iterator finish = s.upper_bound(8); // >val最小值while (start != finish) {cout << *start << " ";start++;}
    }
    

multiset

multiset和set的用法类似.但是multiset只能排序,不能去重.multiset的底层遇见相同的值的节点的时候,会插入在已有的值的节点的左边或者是右边均可.最后经过旋转了之后结果都是一样的.

multiset的erase方法返回size_t时,此时就有意义了.

multiset的lower_bound和upper_bound函数的参数假如有多个的时候,一般都是返回指向在中序遍历中第一次出现的那个数的迭代器.Find也是同理.

map

map的结构上和set是完全相同的,但是map中插入数据要构造pair类.

map的特点:key是不能修改的,value能修改.

value_type就是pair,pair的key不能被修改.但是mapped_type是可以被修改的
在这里插入图片描述

pair<iterator,bool> insert (const value_type& val)

map结构数据的插入

map的节点中的数据都是pair,pair对象有两个成员,分别是first和second,first就是key_type,不可被修改,second是mapped_type,可被修改,插入数据的时候,也需要构造成pair再进行插入即可.

那么pair的构造函数长啥样呢?

default (1)pair();
copy (2)template<class U, class V> pair (const pair<U,V>& pr);
initialization (3)pair (const first_type& a, const second_type& b);

所以pair的构造方法:

pair<string,string> v("sort","排序");   //调用构造函数
pair<string,string> = {"right","右边"}; // C++11中新引入的多参数默认构造.
pair<string,string>("left","左边");     // pair的匿名对象

另一种构造pair的方法:

make_pair("integer","整形");
	map<string, string> m;m.insert(make_pair("sort","分类"));m.insert(pair<string, string>("right", "右边"));pair<string, string> p("left", "右边");m.insert(p);

map结构数据的遍历

  • 迭代器:
    map中的迭代器也是指向的是pair对象,我们需要访问的值是pair对象的成员.

    	map<string, string>::iterator begin = m.begin();while (begin != m.end()) {cout << (*begin).first << " ";cout << (*begin).second << endl;// 也可以:// cout<<begin->first<<"";// cout<<begin->second<<endl;begin++;}
    
  • 范围for(这里最好是加上引用,否则拷贝代价很大)

    	for (auto& kv : m) {cout << kv.first << " ";cout << kv.second << endl;}
    

map的方括号

在这里插入图片描述

在这里插入图片描述

这里调用[]之后,相当于自动根据传入的参数和mapped_type的默认构造函数创建了一个pair,并调用insert将这个pair插入到map中,紧接着再获取到insert返回值的pair的第一个参数(通过后文可知,insert的返回值也是一个pair,并且这个pair的第一个参数的类型是map中的迭代器,第二个参数是一个bool类型的值),这个参数是一个迭代器,并且这个迭代器指向key的值为参数k的节点,这个节点中的存储的结构也是pair,最后将这个迭代器解引用,获取这个节点中的second值,也就是这个map中存储的pair中,key值为参数k的节点的第二个参数,

由上可见:[]重载的返回值是依赖于insert的返回值的,那么我们再去看看insert函数的返回值:
在这里插入图片描述

当使用向map插入一个pair时,这里会返回一个pair,这个返回的pair的对象的两个元素分别是:iterator和bool类型的.来看看对他们的说明吧:
在这里插入图片描述
在这里插入图片描述

可见,当插入的值成功之后,iterator会指向这个新插入的pair,并且bool类型的参数的值为false,而当要被插入的值A在map中已经存在的时候,那么iterator会指向这个map中与A相同的key的pair对象.

insert函数用于查找元素

通过观察insert的返回值可见,由于返回值是一个pair类型,所以可以用insert函数来寻找特定的值是否存在,可将insert用于查找功能.假如要插入的值不存在,那么返回值pair的second的值就为true,假如要插入的值存在,那么返回值pair的second的值就是false.

来看一个案例:

使用map计算每种水果出现的次数:

**方法一:**利用普通的find函数和insert函数结合操作

int main()
{map<string, int> m;string arr[] = { "西瓜","草莓","西瓜" };for(auto& e:arr){map<string,int>::iterator it = m.find(e);if(it!=m.end()){// 不是第一次出现it->second++;}else{// 是第一次出现m.insert(make_pair(e,1));}}for(auto& e:m){cout<<e.first<<" ";cout<<e.second<<endl;}return 0;
}

**方法二:**利用[]重载完成

int main()
{map<string, int> m;string arr[] = { "西瓜","草莓","西瓜" };for(auto& e:arr){m[e]++;}for(auto& e:m){cout<<e.first<<" ";cout<<e.second<<endl;}return 0;
}

方法三:利用insert的返回值完成:

int main()
{string arr[] = { "西瓜","草莓","西瓜" };map<string, int> m;pair<map<string, int>::iterator, bool> ret;for (auto& e : arr) {ret = m.insert(make_pair(e, 1));if (ret.second) {// 插入成功,因为是第一次出现}else {// 插入失败,因为已经出现过了ret.first->second++;}}for (auto& e : m) {cout << e.first << " ";cout << e.second << endl;}return 0;
}

map的[]能实现的4个功能

以后在map的函数中,使用的都是[]居多.因为[]的功能实在是太强大了.

  • 插入
  • 查找
  • 修改
  • 插入+修改
int main()
{map<string, string> m;m["sort"]; // 插入元素m["sort"] = "分类"; // 修改元素cout << m["sort"] << endl;// 查找元素m["right"] = "右边";// 插入+修改元素return 0;
}

map解决比较复杂的问题

138. 随机链表的复制 - 力扣(LeetCode)
在这里插入图片描述

分析:

要做到深拷贝,我们可以首先使用一个map来保存原链表和新链表的节点之间的对应关系,在map中原链表和新链表的节点一一对应.这样在获取到了原链表的random的时候,这个原random节点也一定能够找到与之对应的random节点.

class Solution {
public:Node* copyRandomList(Node* head) {Node* newHead = nullptr;Node* newTail = nullptr;map<Node*,Node*> m;Node* cur = head; // 从第一个链表的头开始遍历while(cur){// 假如是第一次连接if(newHead == nullptr){newHead = newTail = new Node(cur->val);}else{// 不是第一次连接newTail->next = new Node(cur->val);newTail = newTail->next;}// newTail会依次指向新链表的每一个元素,新链表的randomm[cur] = newTail;cur = cur->next;}cur = head;Node* tail = newHead;while(cur){if(cur->random == nullptr){tail->random = nullptr;}else{tail->random = m[cur->random];}cur = cur->next;tail = tail->next;}return newHead;}
};

349. 两个数组的交集 - 力扣(LeetCode)
在这里插入图片描述

寻找交集和差集比较中要的的算法思想:
定义两个迭代器,分别指向各自的set集合,迭代器从头开始遍历,并且比较两个迭代器指向的数据的大小,其中,小的数据,就是差集,接着让小的迭代器++,假如比较的数据相同,那么该值就是两个set的并集,此时两个迭代器都向后++,直到有一个迭代器走到了end位置.此时就找到了两个set的差集和交集.

方法一:

将两个数组存在在set中,去除重复元素之后,再看s1中的元素在s2中是否出现过.

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;for(auto& e:s1){// 在s2中是否出现过if(s2.count(e)){ret.push_back(e);}}return ret;            }
};

方法二:

由于装入set集合中的数据都是有序的,所以使用两个迭代器分别指向两个set集合的开始,查看这两个迭代器指向的数据的大小,小的那个数据就是这两个set的差集,接着,小的那个迭代器++,大的迭代器不变,当两个相等时,此时所指向的数据就是两个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());set<int>::iterator it1 = s1.begin();set<int>::iterator it2 = s2.begin();vector<int> ret;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;}
};

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

在这里插入图片描述

分析:

首先将数组存入 map<string,int> m的结构中,这里会默认按着key排序,也就是按着字符串的ascall码进行排序,但是我们想要的是按着出现的次数从大到小进行排序,所以我们需要将map中的元素存储在vector中,再对vector进行排序即可.

注意sort函数的第三个参数,第三个这个参数是一个对象,就是一个比较器,这比较器是一个对象,并且对象要重载()方法,而且重载的这个()方法的返回值必须是bool类型的.前两个参数是比较的数据的类型,这里由于vector中存储的数据是pair类型.所以参数就是pair类型,返回的时候,假如前两个被比较的参数,符号是<,那么就是按着升序排列的,反之就是按着降序排列的.

class Cmp{public:bool operator()(const pair<string,int>& kv1,const pair<string,int>& kv2){return kv1.second > kv2.second;}
};
class Solution {
public:vector<string> topKFrequent(vector<string>& words, int k) {map<string,int> m;for(auto& e: words){m[e]++;}vector<pair<string,int>> v(m.begin(),m.end());stable_sort(v.begin(),v.end(),Cmp());vector<string> ret;auto it = v.begin();while(k--){ret.push_back(it->first);it++;}   return ret;}
};

multimap

multimap和map的区别是:允许key的重复.

	void test_map6()
{multimap<string, string> m;  m.insert(make_pair("sort", "分类"));  m.insert(pair<string, string>("right", "右边"));  pair<string, string> p("left", "右边");  m.insert(p);  m.insert(p);  for (auto& e : m) {  cout << e.first << " ";  cout << e.second << endl;  }
}

本篇文章就到这里啦,若有不足,请在评论区指正.下期见!

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

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

相关文章

制造业工厂怎么通过MES系统来升级改造车间管理

在当今高度竞争的市场环境下&#xff0c;制造业企业需要不断提高生产效率&#xff0c;以在激烈的竞争中立于不败之地。而一种被广泛应用的方法就是利用MES控制系统&#xff0c;通过数字化管理和自动化控制来改造生产车间提升生产效率。 1、MES管理系统能够实现对生产过程的全面…

Navicat Premium 16 Mac/win---数据库设计、管理与维护轻松掌握数据库管理精髓

Navicat Premium是一款功能强大的数据库开发工具&#xff0c;支持多种数据库系统&#xff0c;如MySQL、Redis、MariaDB、Oracle等&#xff0c;并可与云数据库兼容&#xff0c;如Amazon RDS、Microsoft Azure等。它提供了直观易用的用户界面&#xff0c;使得开发者能够轻松上手并…

k8s calico由IPIP模式切换为BGP模式

按照官网calico.yaml部署后&#xff0c;默认是IPIP模式 查看route -n &#xff0c; 看到是tunl0口进行转发 怎么切换到BGP模式呢&#xff1f; kubectl edit ippool 将ipipMode由Always修改为Never &#xff0c;修改后保存文件即可。无需做任何操作&#xff0c;自动就切换为BG…

MySql实战--普通索引和唯一索引,应该怎么选择

在前面的基础篇文章中&#xff0c;我给你介绍过索引的基本概念&#xff0c;相信你已经了解了唯一索引和普通索引的区别。今天我们就继续来谈谈&#xff0c;在不同的业务场景下&#xff0c;应该选择普通索引&#xff0c;还是唯一索引&#xff1f; 假设你在维护一个市民系统&…

stm32cubeMX_io输入输出讲解

1创建项目&#xff08;可在专栏里找到&#xff09; 2进入当前页面点击引脚将弹出下图选项选择输入输出 带点击GPIO 点击引脚弹出如下选项根据需求选择 如有需要可以使用外部时钟&#xff1b;设置如图使用外部时钟 生成代码 将会弹出一个提示点击中间项//打开项目

HarmonyOS NEXT应用开发之MVVM模式

应用通过状态去渲染更新UI是程序设计中相对复杂&#xff0c;但又十分重要的&#xff0c;往往决定了应用程序的性能。程序的状态数据通常包含了数组、对象&#xff0c;或者是嵌套对象组合而成。在这些情况下&#xff0c;ArkUI采取MVVM Model View ViewModel模式&#xff0c;其…

clickhouse 源码编译部署

clickhouse 源码编译部署 版本 21.7.9.7 点击build project&#xff0c;编译工程&#xff0c;经过一定时间&#xff08;第一次编译可能几个小时&#xff0c;后续再编译&#xff0c;只编译有改动的文件&#xff09;生成release目录 在cmake-build-release → programs目录下…

vivado eFUSE 寄存器访问和编程

eFUSE 寄存器访问和编程 注释 &#xff1a; 在 MPSoC 和 Versal 器件上不支持以下 eFUSE 访问和编程方法。 7 系列、 UltraScale 和 UltraScale 器件具有一次性可编程位用于执行特定功能 &#xff0c; 称为 eFUSE 位。不同 eFUSE 位类型如 下所述&#xff1a; • …

单例(Singleton)设计模式

2.1 设计模式概述 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱&#xff0c;不同的棋局&#xff0c;我们用不同的棋谱。"套路" 经典的设计模式共有23种。每个…

语音识别:基于HMM

HMM语音识别的解码过程 从麦克风采集的输入音频波形被转换为固定尺寸的一组声学向量&#xff1a; 其中是维的语音特征向量&#xff08;例如MFCC&#xff09;。 解码器尝试去找到上述特征向量序列对应的单词&#xff08;word&#xff09;的序列&#xff1a; 单词序列的长度是。…

【大数据存储】实验4 NoSQL数据库

实验4 NoSQL数据库 NoSQL数据库的安装和使用实验环境&#xff1a; Ubuntu 22.04.3 Jdk 1.8.0_341 Hadoop 3.2.3 Hbase 2.4.17 Redis 6.0.6 mongdb 6.0.12 mogosh 2.1.0 Redis 安装redis完成 新建终端启动redisredis-server新建一个终端redis-cli 建表操作 尝…

超越传统时序!多模态+时间序列8个创新方案,刷新SOTA

传统时间序列无法有效捕捉数据中复杂的非线性关系&#xff0c;导致在处理具有复杂动力学特性的系统时效果不佳。为解决此问题&#xff0c;研究者提出了多模态时间序列。 在预测任务中&#xff0c;多模态时间序列能够整合来自不同类型数据源的信息&#xff0c;从而提供更全面的洞…

笔记: JavaSE day15 笔记

第十五天课堂笔记 数组 可变长参数★★★ 方法 : 返回值类型 方法名(参数类型 参数名 , 参数类型 … 可变长参数名){}方法体 : 变长参数 相当于一个数组一个数组最多只能有一个可变长参数, 并放到列表的最后parameter : 方法参数 数组相关算法★★ 冒泡排序 由小到大: 从前…

JavaScript(六)---【回调、异步、promise、Async】

零.前言 JavaScript(一)---【js的两种导入方式、全局作用域、函数作用域、块作用域】-CSDN博客 JavaScript(二)---【js数组、js对象、this指针】-CSDN博客 JavaScript(三)---【this指针&#xff0c;函数定义、Call、Apply、函数绑定、闭包】-CSDN博客 JavaScript(四)---【执…

并发编程之线程池的应用以及一些小细节的详细解析

线程池在实际中的使用 实际开发中&#xff0c;最常用主要还是利用ThreadPoolExecutor自定义线程池&#xff0c;可以给出一些关键的参数来自定义。 在下面的代码中可以看到&#xff0c;该线程池的最大并行线程数是5&#xff0c;线程等候区&#xff08;阻塞队列)是3&#xff0c;即…

数据挖掘|关联分析与Apriori算法详解

数据挖掘|关联分析与Apriori算法 1. 关联分析2. 关联规则相关概念2.1 项目2.2 事务2.3 项目集2.4 频繁项目集2.5 支持度2.6 置信度2.7 提升度2.8 强关联规则2.9 关联规则的分类 3. Apriori算法3.1 Apriori算法的Python实现3.2 基于mlxtend库的Apriori算法的Python实现 1. 关联分…

window中如何在Anaconda虚拟环境中安装compressai

1, 进入CompressAI的Github代码页下载压缩包并解压到自己的项目路径 2&#xff0c;打开anaconda的prompt命令行&#xff0c;激活需要安装的虚拟环境&#xff0c;然后进入compressai文件夹&#xff0c;比如下操作&#xff1a; 3&#xff0c;输出安装命令行 pip install -e . -…

企业必备! 防员工偷懒神器,工作状况一目了然

在当前企业管理中&#xff0c;员工的工作状态和工作效率一直是管理者们关注的焦点。为了更加有效地监管员工的工作微信使用情况&#xff0c;微信管理系统成为了企业必备的神器。 这款系统不仅可以实时监控员工的工作微信&#xff0c;还具有多种实用功能&#xff0c;帮助企业管…

查看图片某点亮度

一背景 光强度的评价通常涉及对光源发出的光的量进行测量和描述。这种评价可以通过多种方式进行&#xff0c;但最常见的是使用光强单位“坎德拉”&#xff08;candela&#xff0c;cd&#xff09;来表示。坎德拉是国际单位制&#xff08;SI&#xff09;中光强度的单位&#xff…

2024 蓝桥打卡Day31

递归与辗转相除法 递归&#xff08;Recursion&#xff09;辗转相除法&#xff08;Euclidean Algorithm&#xff09;总结 递归&#xff08;Recursion&#xff09; 递归是指一个函数在执行过程中调用自身的过程。在编程中&#xff0c;递归函数在遇到满足某个条件时会停止调用自身…