【C++】map、set,multiset和multimap的使用及底层原理【完整版】

news2024/11/27 3:14:49

目录

一、map和set的使用

1、序列式容器和关联式容器

2、set的使用讲解

 3、map的使用讲解

二、multiset和multimap 

1、multiset和multimap的使用

2、OJ题:前k个高频单词


一、map和set的使用

1、序列式容器和关联式容器

序列式容器:vector/list/string/deque

序列式容器才支持push等操作,关联式容器不支持

关联式容器:map/set/unordered_map/unordered_set

set和map底层实现平衡搜索二叉树


2、set的使用讲解

  • set就是搜索树中的key模型
  • set的特性:①、会对插入的数据自动排序 ②、set是不允许修改值的 ③、set中不允许出现重复的数值,即使存在,也只会留一个
  • set的遍历:①、迭代器遍历 ②、范围for遍历(因为支持迭代器遍历就一定支持范围for)
  • set的拷贝构造
  • set的插入只有insert,其没有push、pop等,因为它是关联式容器
  • set的find,find找到了会返回被查找元素的迭代器,没找到返回end(),故应检查找没找到
  • 那set的find和库里面提供的find有什么区别呢?
  • 都可实现查找,区别在于效率
  • set是搜索二叉树的:时间复杂度:O(logN),而算法中的是O(N)
  • 算法中的find是个模板,其实现是为了所有容器可以通用它,故set尽量用自己的find 
  • set的删除
  • ①、erase(待删除位置的迭代器)  ②、erase(待删除数据) ③、erase(s.begin(), s.end())【即迭代器头和尾,其效果等价于clear   】

因为setkey模型,是看在不在,如果把中国所有人的信息存入到set中,最多搜索次数才31次,因为搜索二叉树的效率:O(logN)2^31就=20多亿了,这个效率是非常好的

代码如下:

void test_set()
{
	set<int> s;
	s.insert(3);
	s.insert(1);
	s.insert(4);
	s.insert(3);
	s.insert(7);

	//set : 排序+去重
	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//支持迭代器,就支持范围for
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	set<int> copy(s);//set的深拷贝
	for (auto& e : copy)
	{
		cout << e << " ";
	}
	cout << endl;

	//auto pos = s.find(3);//可用auto推导类型
	//set<int>::iterator pos = s.find(3);//find查找返回迭代器 
	find找到了会返回元素的迭代器,没找到返回end()
	//if (pos != s.end())
	//{//找到了才能删除
	//	s.erase(pos);//erase会删除迭代器位置的数据
	//}
	//若erase直接给值,若值不存在,也不会报错,但迭代器必须存在那个位置

	set<int>::iterator pos = find(s.begin(), s.end(), 3);//使用算法中的find
	if (pos != s.end())
	{
		s.erase(pos);
	}

	for (auto& e : s)
	{
		cout << e << " ";
	}
	cout << endl;
}

运行结果: 

  


 3、map的使用讲解

  • map就是搜索树中的key/value模型
  • map的遍历①、迭代器遍历 ②、范围for遍历
  • map类型pairpair存的一个是key的,一个是value的类型
  • map的构造函数:①、pair构造函数 ②、make_pair函数模板构造一个pair对象
void test_map1()
{
	map<int, int> m;
	//m.insert(1, 1);//编译不通过
	m.insert(pair<int, int>(1, 1));//pair构造函数,构造一个匿名对象
	m.insert(pair<int, int>(3, 3));
	m.insert(pair<int, int>(2, 2));
	m.insert(make_pair(4, 4));	   //函数模板构造一个pair对象

	map<int, int>::iterator it = m.begin();
	while (it != m.end())
	{	//*it等价于pair,而要访问它的成员
		cout << it->first << ":" << it->second << " " << endl;
		//也可以用(*it).first    (*it).second
		//operator* 返回值是节点中值的引用
		//operator->返回值是节点中值的指针,即pair<k,v>指针
		//本质上为了可读性,这里省略了一个->
		++it; 
	}
	cout << endl;

	for (auto& e : m)
	{//first就是key值,即pair中的第一个值,second就是value值,即pair中的第二个值
		cout << e.first << ":" << e.second << endl;
	}

}

 


  • map构造函数两种方法区别


void test_map2()
{
	//一般写项目不会把std库中的全引进来,而是如下代码,make_pair明显更加简洁
	std::map<std::string, std::string> dict;
	dict.insert(pair<std::string, std::string>("metric", "米制的"));
	dict.insert(make_pair("potent", "强大的"));
	dict.insert(make_pair("deplete", "大量减少"));


	std::map<std::string, std::string>::iterator it = dict.begin();
	while (it != dict.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;
}

可见使用make_pair会使代码更简洁


以下是map的应用统计水果出现的次数【本质是key/value模型的应用

法一:利用map的find(key值查找,不是value值)

void test_map3()
{
	//用STL中的map怎么统计水果出现的次数呢?
	string strs[] = { "西瓜","樱桃","苹果","西瓜","西瓜","西瓜","西瓜","苹果" };
	map<string, int> countMap;
	for (auto & str : strs)
	{
		map<string, int>::iterator ret = countMap.find(str);
		if (ret != countMap.end())
		{
			ret->second++;//相当于value++
		}
		else
		{
			//第一次出现,直接插入value为1
			countMap.insert(make_pair(str, 1));
		}
	}

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


法二、map的operator[ ]求解

我们之前学的容器只有string,vector和deque才有operator[ ],而这里map的operator[ ]还有所不同

下面是operator[ ]底层

可见给operator[ ]一个key值,它返回对应的value值的引用

那就可以把求水果出现的次数代码用operator[ ]实现进一步优化

void test_map3()
{
	//用STL中的map怎么统计水果出现的次数呢?
	string strs[] = { "西瓜","樱桃","苹果","西瓜","西瓜","西瓜","西瓜","苹果" };
	map<string, int> countMap;
	for (auto& str : strs)
	{
		//法二、operator[]实现
		countMap[str]++;//给key值:字符串,返回对应value的引用:次数
	}

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

法三、map的insert求解

operator[ ]的底层是调用insert实现的,故想了解operator[ ]要先了解insert

insert的其中一个版本是

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

返回值的意思:

单元素版本:(1)返回pair,其成员pair::first设置为一个迭代器,该迭代器指向新插入的元素或映射中具有等效键的元素。如果插入了新元素,则pair::第二个元素设为true,如果已经存在等效键,则设为false。

理解:

insert对于插入不存在的数据充当插入作用pairfirst指向新插入元素,second设为true,但若插入一个已经存在的数据,insert充当查找作用pairfirst指向之前存在的那个元素,second设为false

利用insert这个版本的特点,我们可以把水果出现的次数再写一个insert的版本

void test_map3()
{
	//用STL中的map怎么统计水果出现的次数呢?
	string strs[] = { "西瓜","樱桃","苹果","西瓜","西瓜","西瓜","西瓜","苹果" };
	map<string, int> countMap;
	for (auto & str : strs)
	{
		//法三、insert实现
		pair<map<string, int>::iterator, bool> ret = countMap.insert(make_pair(str, 1));
	    //也可写为auto ret = countMap.insert(make_pair(str, 1));
		//如果插入成功,那就说明之前在map中没出现过,value为1即可
		if (ret.second == false)
		{//插入失败,说明之前存在这个数据,迭代器指向之前出现的那个元素
			ret.first->second++;//用迭代器访问到这个元素的value值
		}
	}

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

那insert是如何实现map的operator[]的?

  • 如果水果不在map中,则[ ]会insert插入pair<str, int()> 等价于 pair<str, 0>,那么返回映射对象(次数)的引用就进行了++1
  • 如果水果在map中,则operator[ ]返回水果对应的映射对象(次数)的引用,对它++

下面讲解下map的operator[ ]多种功能

void test_map3()
{
	//用STL中的map怎么统计水果出现的次数呢?
	string strs[] = { "西瓜","樱桃","苹果","西瓜","西瓜","西瓜","西瓜","苹果" };
	map<string, int> countMap;
	for (auto & str : strs)
	{
		//法二、operator[]实现
		countMap[str]++;//给key值:字符串,返回对应value的引用:次数
	}

	countMap["香蕉"];       //插入,因为第一次出现
	countMap["香蕉"] = 1;   //修改,因为operator[]返回value的引用,故可修改
	cout << countMap["香蕉"] << endl;//查找,因为香蕉已经存在了
	countMap["哈密瓜"] = 5; //插入+修改,哈密瓜第一次出现,并对他的value进行了修改

	map<string, string> dict;
	dict.insert(make_pair("sort", "排序"));
	dict["string"];//key为string,value是string类型的构造函数【因为其是缺省值】,即空串  //插入(一般不会这样用)
	dict["string"] = "字符串";//返回value的引用,可以对其进行修改,能修改是因为返回value的引用 //修改,不算插入因为已存在
	dict["left"] = "左边";//插入+修改,因为"左边"第一次出现,故插入,插入后又对其value进行了修改
	
	for (auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
}

注:传参只能传key,不能只传value不传key,因为底层是搜索树,搜索树要用key去比较大小,key只要进去了就不能修改了

一般使用operator[]

  • 插入+修改
  • 修改

一般不会用它去查找,因为如果key不在会插入数据

总结:


二、multiset和multimap 

1、multiset和multimap的使用

 multiset和multimap除了在set和map的基础上支持数据重复出现外,根本没什么区别

void test_multi()
{
	//与set的区别是允许键值key冗余(重复)
	multiset<int> ms;
	ms.insert(3);
	ms.insert(2);
	ms.insert(3);
	ms.insert(1);
	ms.insert(4);
	ms.insert(5);

	for (auto e : ms)
	{
		cout << e << " ";
	}
	cout << endl;

	auto pos = ms.find(3);
	cout << *pos << endl;
	++pos;
	cout << *pos << endl;
	++pos;

	//multi_map和map的区别和set与multi_set的区别一样
	//额外区别是muti_map没有operator[],因为当有多个相同的可以时,不知道返回哪个key对应的value
	multimap<string, int> mm;
	mm.insert(make_pair("苹果", 1));
	mm.insert(make_pair("苹果", 1));
	mm.insert(make_pair("苹果", 3));
	mm.insert(make_pair("西瓜", 2));
	mm.insert(make_pair("西瓜", 1));

 }

 


2、OJ题:前k个高频单词

思路:

①、先创建个map对象,利用operator[ ]对其中的字符串排序(会按ASCII码排序),那么key值应该是string,因为map是按照key值从低到高排序的 

②、因为出现频率高的在前,且还有重复数据的出现,故使用multimap和仿函数

countMap中的数据插入到multimap中,multimapkey值是int类型的,那相当于multimap按出现频率排序,那出现频率高的就会在前,而出现频率相同的,之前operator[ ]已排好序了,按字典顺序排的,小的ASCII码在前

③、因为返回vector<string>,故只把multimap中的string存入到结果中即可,访问他的string即迭代器位置->second

class Solution {
public:
	vector<string> topKFrequent(vector<string>& words, int k) {
		map<string, int> countMap;
		//统计每个字符串出现了多少次
		for (auto& e : words)
		{
			countMap[e]++;//map会自动对key值排序,即对string排序,并修改对应的value值
		}
		//但我们现在需对value值排序,即对int排序,因为要找出现频率高的
		
		//法一、将pair<string, int>键值对放到vector中,用sort排序,还要写一个
		//按int比较的仿函数,因为sort是快排实现的,不稳定,排完了,还需对次数相同的按字母排,要存入vector是因为
		//sort只供支持随机访问的容器使用,如vector、deque

		//法二、用multimap按次数排序,利用仿函数控制从大到小排
		multimap<int, string,greater<int>> sortMap;//multimap可以保证数据的重复出现
		for (auto& kv : countMap)
		{
			sortMap.insert(make_pair(kv.second, kv.first));//排完序后插入到multimap,其会按int从大到小排
			//排完后
			//出现次数高的在前面,而出现次数相同的,之前已用operator[]按string排序了
		}

		vector<string> v;
		auto it = sortMap.begin();
		while (it != sortMap.end())
		{
			if (k == 0)
				break;
			v.push_back(it->second);//插入字符串
			++it;
			--k;//插入完一个就--
		}

		return v;
	}
};

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

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

相关文章

java遇到的问题

java遇到的问题 Tomcat与JDK版本问题 当使用Tomcat10的版本用于springmvc借用浏览器调试时&#xff0c;使用JDK8浏览器会报异常。 需要JDK17&#xff08;可以配置多个JDK环境&#xff0c;切换使用&#xff09;才可以使用&#xff0c;配置为JAVA_HOME路径&#xff0c;否则&a…

Linux系统编程系列之进程间通信-消息队列

一、什么是消息队列 消息队列是system-V三种IPC对象之一&#xff0c;是进程间通信的一种方式。 二、消息队列的特性 允许发送的数据携带类型&#xff08;指定发送给谁&#xff09;&#xff0c;具有相同类型的数据在消息队列内部排队&#xff0c;读取的时候也要指定类型&#x…

STM32三种开发方式及标准库和HAL库的编程差异

三种开发方式 STM32基于标准库函数和HAL库编程差异_stm32库函数和hal库-CSDN博客本文目的是以串口通信来简要分析STM32使用标准库函数和HAL库函数编程的差异。目录&#xff08;一&#xff09;开发方式1.配置寄存器2.库函数3.HAL库&#xff08;二&#xff09;库函数与HAL库对比…

格点数据可视化(美国站点的日降雨数据)

获取美国站点的日降雨量的格点数据&#xff0c;并且可视化 导入模块 from datetime import datetime, timedelta from urllib.request import urlopenimport cartopy.crs as ccrs import cartopy.feature as cfeature import matplotlib.colors as mcolors import matplotli…

3D孪生场景搭建:模型区域摆放

前面介绍完了NSDT场景编辑器的线性绘制和阵列绘制&#xff0c;本章将讲述下编辑器的另一种绘制方式&#xff1a;区域绘制。 1、区域绘制功能简介 在场景中绘制资产时&#xff0c;除使用上述两个的方式外&#xff0c;NSDT 编辑器还支持使用区域绘制的方式进行绘制。先选取需要…

【C/C++笔试练习】——数组名和数组名、switch循环语句、数据在计算机中的存储顺序、字符串中找出连续最长的数字串、数组中出现次数超过一半的数字

文章目录 C/C笔试练习1.数组名和&数组名&#xff08;1&#xff09;数组名和&数组名的差异&#xff08;2&#xff09;理解数组名和指针偏移&#xff08;3&#xff09;理解数组名代表的含义&#xff08;4&#xff09;理解数组名代表的含义 2.switch循环语句&#xff08;6…

FFmpeg 命令:从入门到精通 | ffplay 简单过滤器

FFmpeg 命令&#xff1a;从入门到精通 | ffplay 简单过滤器 FFmpeg 命令&#xff1a;从入门到精通 | ffplay 简单过滤器视频旋转视频反转视频旋转和反转音频变速播放视频变速播放音视频同时变速更多参考 FFmpeg 命令&#xff1a;从入门到精通 | ffplay 简单过滤器 本节介绍了简…

【CFD小工坊】浅水方程的离散及求解方法

【CFD小工坊】浅水方程的离散及求解方法 前言基于有限体积法的方程离散界面通量与源项计算干-湿网格的处理数值离散的稳定性条件参考文献 前言 我们模型的控制方程&#xff0c;即浅水方程组的表达式如下&#xff1a; ∂ U ∂ t ∂ E ( U ) ∂ x ∂ G ( U ) ∂ y S ( U ) U…

十五、异常(4)

本章概要 Java 标志异常 特例&#xff1a;RuntimeException 使用 finally 进行清理 finally 用来做什么&#xff1f;在 return 中使用 finally缺憾&#xff1a;异常丢失 Java 标准异常 Throwable 这个 Java 类被用来表示任何可以作为异常被抛出的类。Throwable 对象可分为两…

JUC——并发编程—第二部分

集合类不安全 list不安全 //报错 java.util.ConcurrentModificationException public class ListTest {public static void main(String[] args) {List<String> list new CopyOnWriteArrayList<>();//并发下Arrayist边读边写会不安全的/*** 解决方案&#xff1a…

国庆作业 9月30 消息队列实现进程间通信

01_write.c&#xff1a; #include <myhead.h> #define MAX 1024//x消息结构体 typedef struct {long msgtype; //消息类型char msg[MAX]; //消息正文 }Msg;#define SIZE sizeof(Msg)-sizeof(long) //正文大小int main(int argc, const char *argv[]) {//1:创建一个key…

机器学习之单层神经网络的训练:增量规则(Delta Rule)

文章目录 权重的调整单层神经网络使用delta规则的训练过程 神经网络以权值的形式存储信息,根据给定的信息来修改权值的系统方法称为学习规则。由于训练是神经网络系统地存储信息的唯一途径&#xff0c;因此学习规则是神经网络研究中的一个重要组成部分 权重的调整 &#xff08…

企业级磁盘阵列存储系统由硬到软全析

企业级磁盘阵列是由一组设备构成的存储系统,主要包括两种类型的设备,分别是控制器和扩展柜,其中控制器只有一台,扩展柜可以没有,也可以有多台。在EMC的Unity中分别称为DPE(Disk Processor Enclosure)和DAE(Disk Array Enclosure),在华为的OceanStor里面称为控制框和硬…

【网络模型】OSI七层网络模型、TCP/IP网络模型、键入网址到页面显示的过程、DNS是什么等重点知识汇总

目录 OSI 的七层模型 TCP/IP 网络模型 键入网址到网页显示发生了什么 你知道DNS是什么&#xff1f; OSI 的七层模型 简要概括 应用层&#xff1a;为用户的应用进程提供网络通信服务表示层&#xff1a;处理用户信息的表示问题&#xff0c;数据的编码&#xff0c;压缩和解压…

在枚举类中“优雅地”使用枚举处理器

使用枚举类的一大好处就是&#xff0c;代码易懂&#xff0c;方便自己或他人维护。如&#xff0c;枚举状态、异常等。 下面有两个类&#xff08;枚举类和实体类&#xff09;&#xff1a; package com.zhang.enums;import lombok.Getter;/*** Author lgz* Description* Date 202…

【生物信息学】基因差异分析Deg(数据读取、数据处理、差异分析、结果可视化)

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 3. IDE 三、实验内容 0. 导入必要的工具包 1. 定义一些阈值和参数 2. 读取数据 normal_data.csv部分展示 tumor_data.csv部分展示 3. 绘制箱型图 4. 删除表达量低于阈值的基因 5. 计算差异显著的基…

Git多账号管理通过ssh 公钥的方式,git,gitlab,gitee

按照目前国内访问git&#xff0c;如果不科学上网&#xff0c;我们很大可能访问会超时。基于这个&#xff0c;所以我现在的git 配置已经增加到了3个了 一个公司gitlab&#xff0c;一个git&#xff0c;一个gitee. 以下基于这个环境&#xff0c;我们来说明下如何创建配置ssh公钥。…

VBA技术资料MF63:遍历形状并改变颜色

【分享成果&#xff0c;随喜正能量】人生&#xff0c;一站有一站的风景&#xff0c;一岁有一岁的味道&#xff0c;你的年龄应该成为你生命的勋章而不是你伤感的理由。生活嘛&#xff0c;慢慢来&#xff0c;你又不差&#xff01;。 我给VBA的定义&#xff1a;VBA是个人小型自动…

【数据结构与算法】栈与队列相关算法的实现

目录 检查括号是否成对出现 反转字符串 循环队列的实现 使用队列实现栈 使用栈实现队列 检查括号是否成对出现 算法要求 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串&#xff0c;判断该字符串是否有效。 有效字符串需满…