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

news2024/10/6 6:41:54

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会依次指向新链表的每一个元素,新链表的random
            m[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.coloradmin.cn/o/1566580.html

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

相关文章

面试题:RabbitMQ 消息队列中间件

1. 确保消息不丢失 生产者确认机制 确保生产者的消息能到达队列&#xff0c;如果报错可以先记录到日志中&#xff0c;再去修复数据持久化功能 确保消息未消费前在队列中不会丢失&#xff0c;其中的交换机、队列、和消息都要做持久化消费者确认机制 由spring确认消息处理成功后…

Java基础之流程控制语句(循环)

文章目录 Java基础之流程控制语句(循环)1.顺序结构2.分支结构if语句的第一种格式if语句的第二种格式if语句的第三种格式Switch语句格式Switch的其他知识点default的位置和省略case穿透Switch的新特性 3.循环结构循环的分类for 循环while 循环for循环 与 while循环 的对比 4.do.…

RAG原理、综述与论文应用全解析

1. 背景 1.1 定义 检索增强生成 (Retrieval-Augmented Generation, RAG) 是指在利用大语言模型回答问题之前&#xff0c;先从外部知识库检索相关信息。 早在2020年就已经有人提及RAG的概念&#xff08;paper&#xff1a;Retrieval-augmented generation for knowledge-inten…

IDEA 解决 java: 找不到符号 符号: 类 __ (使用了lombok的注解)

原因IDEA版本太高&#xff0c;在 ProcessingEnvironement 预编译的时候是以代理的方式来执行的&#xff0c;不再是直接 javac方式, lombok依赖的 javac方式的 annotation processors 不再生效了 解决办法&#xff1a;下面这一句&#xff0c;加在下图中 -Djps.track.ap.depen…

八口快速以太网交换机芯片方案分享/JL5110

以太网交换机(switch)是一种网络设备&#xff0c;用于在局域网中连接多个计算机和其他网络设备。它可以实现多个端口之间的同时传输&#xff0c;并根据MAC地址进行帧过滤和转发。交换机通过自学习的方式&#xff0c;将MAC地址与相应的接口关联起来&#xff0c;以便将数据帧准确…

C语言中的数组与函数指针:深入解析与应用

文章目录 一、引言二、数组的定义1、数组的定义与初始化2、char*与char[]的区别1. 存储与表示2. 修改内容3. 作为函数参数 三、字符串指针数组1. 定义与概念2. 使用示例3. 内存管理 四、从字符串指针数组到函数指针的过渡1、字符串指针数组的应用场景2、函数指针的基本概念3、如…

【RedHat9.0】Timer定时器——创建单调定时器实例

一个timer&#xff08;定时器&#xff09;的单元类型&#xff0c;用来定时触发用户定义的操作。要使用timer的定时器&#xff0c;关键是要创建一个定时器单元文件和一个配套的服务单元文件&#xff0c;然后启动这些单元文件。 定时器类型&#xff1a; 单调定时器&#xff1a;即…

解析html内容的h标签成目录树(markdown解析出来的html)

一.本人用的markdown插件是cherry-markdown&#xff0c;个人觉得比较好用&#xff0c;画图和数学公式都整合的很好 https://github.com/Tencent/cherry-markdown 二.背景 经过markdown解析的html&#xff0c;要取里面的h标签转换成目录树&#xff0c;发现这里面都要人工计算&…

EXCEL-VB编程实现自动抓取多工作簿多工作表中的单元格数据

一、VB编程基础 1、 EXCEL文件启动宏设置 文件-选项-信任中心-信任中心设置-宏设置-启用所有宏 汇总文件保存必须以宏启动工作簿格式类型进行保存 2、 VB编程界面与入门 参考收藏 https://blog.csdn.net/O_MMMM_O/article/details/107260402?spm1001.2014.3001.5506 二、…

用Vue仿了一个类似抖音的App

大家好&#xff0c;我是 Java陈序员。 今天&#xff0c;给大家介绍一个基于 Vue3 实现的高仿抖音开源项目。 关注微信公众号&#xff1a;【Java陈序员】&#xff0c;获取开源项目分享、AI副业分享、超200本经典计算机电子书籍等。 项目介绍 douyin —— 一个基于 Vue、Vite 实…

解决uCharts图表在微信小程序层级过高问题

uniapp微信小程图表层级过高问题&#xff1f; 项目中涉及 uCharts图表&#xff0c;在 App/H5端均正常使用&#xff0c;微信小程序 存在层级问题&#xff01; 文章目录 uniapp微信小程图表层级过高问题&#xff1f;效果图遇到问题解决方案 啰嗦一下~&#xff0c;自己的粗心 在实…

Firefox 关键词高亮插件的简单实现

目录 1、配置 manifest.json 文件 2、编写侧边栏结构 3、查找关键词并高亮的方法 3-1&#xff09; 如果直接使用 innerHTML 进行替换 4、清除关键词高亮 5、页面脚本代码 6、参考 1、配置 manifest.json 文件 {"manifest_version": 2,"name": &quo…

HDLbits 刷题 --Always nolatches

学习: Your circuit has one 16-bit input, and four outputs. Build this circuit that recognizes these four scancodes and asserts the correct output. To avoid creating latches, all outputs must be assigned a value in all possible conditions (See also always…

内存管理是如何影响系统的性能的

大家好&#xff0c;今天给大家介绍内存管理是如何影响系统的性能的&#xff0c;文章末尾附有分享大家一个资料包&#xff0c;差不多150多G。里面学习内容、面经、项目都比较新也比较全&#xff01;可进群免费领取。 内存管理对系统性能的影响至关重要&#xff0c;主要体现在以下…

课程设计项目3.2:基于振动信号分析的电机轴承故障检测

01.课程设计内容 02.代码效果图 获取代码请关注MATLAB科研小白的个人公众号&#xff08;即文章下方二维码&#xff09;&#xff0c;并回复&#xff1a;MATLAB课程设计本公众号致力于解决找代码难&#xff0c;写代码怵。各位有什么急需的代码&#xff0c;欢迎后台留言~不定时更新…

Js之运算符与表达式——②

运算符&#xff1a;也叫操作符&#xff0c;是一种符号。通过运算符可以对一个或多个值进行运算&#xff0c;并获取运算结果。 表达式&#xff1a;由数字、运算符、变量的组合&#xff08;组成的式子&#xff09;。 表达式最终都会有一个运算结果&#xff0c;我们将这个结果称…

【cache】卡常

来源于《国家集训队2024论文集》中的《论现代硬件上的常数优化》 个人总结&#xff1a; 不要开二的次幂作为维度的数组&#xff0c;否则常数会变大【存疑】。总是保证内存访问连续性更高。比如 st表&#xff0c;把log维放在第二维&#xff0c;会导致内存访问距离最大为N*LOG。…

权限提升技术:攻防实战与技巧

本次活动赠书1本&#xff0c;包邮到家。参与方式&#xff1a;点赞收藏文章即可。获奖者将以私信方式告知。 网络安全已经成为当今社会非常重要的话题&#xff0c;尤其是近几年来&#xff0c;我们目睹了越来越多的网络攻击事件&#xff0c;例如公民个人信息泄露&#xff0c;企业…

Vulnhub:MY FILE SERVER: 1

目录 信息收集 1、arp 2、nmap 3、whatweb WEB web信息收集 dirmap FTP匿名登录 enum4linux smbclient showmount FTP登录 ssh-kegen ssh登录 提权 系统信息收集 脏牛提权 get root 信息收集 1、arp ┌──(root㉿ru)-[~/kali/vulnhub] └─# arp-scan -l I…

信息咨询模块

信息资讯 资讯文章专题、互动评论、自定义多级分类&#xff0c;打造本地新闻圈 简介 本项目旨在打造一个本地新闻资讯平台&#xff0c;为用户提供以下功能&#xff1a; 资讯文章&#xff1a;提供本地新闻、生活、娱乐等各类资讯文章。专题&#xff1a;针对重要事件或热门话…