当前位置: 首页 > article >正文

【C++】map和set的介绍+使用

前言:

    我们前面一起学习了二叉搜索树,这便是为了引入本章我们所学的map和set容器。map和set的底层实现就和二叉搜索树有关...

目录

(一)键值对的引入

(1)关联式容器

(2)键值对

(二)set

(1)set的介绍

(2)set的使用

set的插入:

 set的查找:

set的删除:

 (3)multiset的介绍+使用

(三)map

(1)map的介绍

(2)map的使用

map的插入:

std::map::operator[]的使用(*重点):


(一)键值对的引入

(1)关联式容器

在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面
存储的是元素本身。
那什么是关联式容器?它与序列式容器有什么区别?
关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的 键值对,在数据检索时比序列式容器效率更高

通常我们学习的都是树状结构的关联式容器

根据应用场景的不桶,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结
构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使
用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一
个容器。

总结:

序列式容器:

  • 在前期我们所学过的STL容器中,例如:string,vector,list,queue…,这些序列式容器中我们不难发现,其存储的都是 C++ 基本数据类型(诸如 int、double、float、string等)或使用自定义类型(结构体)的元素。

树状结构的关联式容器:

  • 关联式容器则和序列式容器有很大区别,关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。除此之外,序列式容器中存储的元素默认都是未经过排序的,而使用关联式容器存储的元素,默认会根据各元素的键值的大小做升序排序。

 

(2)键值对

关联式容器存放的是键值对,那么键值对是什么呢?

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息
比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该单词,在词典中就可以找到与其对应的中文含义。
我们通过C++网站查询,介绍如下:

 它实际上就是一个类模板,用来表示具有一 一对应关系的一种结构。

STL源码库给出的源代码如下:

 

(二)set

(1)set的介绍

使用 set 容器,必须引入该头文件#include < set >

set的使用文档

T: set中存放元素的类型,实际在底层存储<value, value>的键值对。
Compareset中元素默认按照小于来比较
Allocset中元素空间的管理方式,使用STL提供的空间配置器管理

 介绍:

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

注意:
  • 1. 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放 value,但在底层实际存放的是由<value, value>构成的键值对
  • 2. set中插入元素时,只需要插入value即可,不需要构造键值对。
  • 3. set中的元素不可以重复(因此可以使用set进行去重)。
  • 4. 使用set的迭代器遍历set中的元素,可以得到有序序列
  • 5. set中的元素默认按照小于来比较
  • 6. set中查找某个元素,时间复杂度为:$log_2 n$
  • 7. set中的元素不允许修改

(2)set的使用

set的插入:

和很多容器一样,set设置了插入的接口,但是set的插入是有额外的功能性的! 

我们试用下面的代码:

void test_set1()
{
	set<int> s1;
	s1.insert(2);
	s1.insert(6);
	s1.insert(2);
	s1.insert(3);
	s1.insert(3);
	s1.insert(5);
	s1.insert(8);
	s1.insert(8);
	s1.insert(6);
	s1.insert(7);

//两种迭代打印的方法::
	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		// 搜索树不允许修改key,可能会破坏搜索的规则
		//*it1 += 1;
		cout << *it << " ";
		++it;
	}
	cout << endl;


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

}

输出结果:

 set实现了去重和排序

  • 有重复的不插入
  • 默认按照升序排列

同样的迭代器的使用方式也一样,只是这里的const迭代器和普通迭代器都是一样的,都不支持修改,原因是如果对二叉搜索树进行修改的话,很有可能会导致整棵树的结构被打乱,所以不支持修改。


 set的查找:

 应用代码:
 

void test_set2()
{
	// 排序 + 去重
	set<int> s1;
	s1.insert(2);
	s1.insert(6);
	s1.insert(2);
	s1.insert(3);
	s1.insert(3);
	s1.insert(5);
	s1.insert(8);
	s1.insert(8);
	s1.insert(6);
	s1.insert(7);
	int x;
	while (cin >> x)
	{
		/*auto ret = s1.find(x);
		if (ret != s1.end())
			cout << "在" << endl;
		else
			cout << "不在" << endl;*/

		if (s1.count(x))
			cout << "在" << endl;
		else
			cout << "不在" << endl;
	}
}

这里find的用法和算法库里的一样,但是为什么要再在set中创立一个呢?

set自带的查找和算法库中的查找有什么区别

  • set自带的查找是利用了搜索树的特点,查找时间复杂度为〇(logN)
  • 如果用算法库中的查找则是通过暴力查找的方式进行的,时间复杂度为〇(N)

set的删除:



		set<int> s;
		s.insert(4);
		s.insert(5);
		s.insert(2);
		s.insert(1);
		s.insert(1);
		s.insert(3);
		s.insert(2);
		s.insert(1);

		s.erase(3);//直接给值删除

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

		int x;
		while (cin >> x)
		{
			set<int>::iterator pos = s.find(x);
			if (pos != s.end())
			{
				s.erase(pos);//迭代器删除
				cout << "删除" << x << "成功" << endl;
			}
			else
			{
				cout << x << "不在set中" << endl;
			}

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

 

删除接口也没什么可以过多介绍的了,还有size、empty、clear等接口和我们之前学的容器接口用法一样,大家可以自己实践一下!

 (3)multiset的介绍+使用

set会自动实现去重的操作,那我们只想拥有排序等相关的操作,而不想去重,这里STL也为我们提供了multiset容器。

介绍:

  • 1. multiset是按照特定顺序存储元素的容器,其中元素是可以重复的
  • 2. 在multiset中,元素的value也会识别它(因为multiset中本身存储的就是<value, value>组成 的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器中进行修改(因为元素总是const的),但可以从容器中插入或删除
  • 3. 在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则进行排序。
  • 4. multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭代器遍历时会得到一个有序序列。
  • 5. multiset底层结构为二叉搜索树(红黑树)。


用法:


void test_set3()
{
	multiset<int> s1;
	s1.insert(1);
	s1.insert(1);
	s1.insert(2);
	s1.insert(3);
	s1.insert(3);
	s1.insert(4);
	s1.insert(4);
	s1.insert(4);
	s1.insert(5);

	multiset<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}

	cout << endl;

	auto ret = s1.find(1);
	cout << *ret << endl;
	while (ret != s1.end() && *ret == 1)
	{
		cout << *ret << " ";
		++ret;
	}
	cout << endl;
	cout << s1.count(1) << " ";
	cout << s1.count(5) << " ";

}

输出结果:

 

 这里我们看到,可以打印出重复的数据了,此时count的作用可以计数了,不需要局限于和find同等的用处。

(三)map

(1)map的介绍

使用 map 容器,必须引入该头文件#include < map >

map的使用文档

key: 键值对中key的类型
T: 键值对中value的类型
Compare: 比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比
较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户
自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)
Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的
空间配置器
介绍:
  • 1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
  • 2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的 内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型 value_type绑定在一起,为其取别名称为pair: typedef pair<const key, T> value_type;
  • 3. 在内部,map中的元素总是按照键值key进行比较排序的。
  • 4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序
  • 对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
  • 5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
  • 6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。

(2)map的使用

map的插入:

其实map的操作也和其他容器的操作是一样的,但由于map中存放的是键值对,所以我们以插入为例来介绍如何使用:

void test_map1()
{
	map<string, string> dict;
	//匿名对象的好处
	dict.insert(pair<string, string>("sort", "排序"));

	//不用匿名对象
	pair<string, string> kv("insert", "插入");
	dict.insert(kv);

	//make_pair是函数模板
	dict.insert(make_pair("left", "左边"));

	//可以这么写但是别这么用(隐式类型的转换) -- C++11再讲
	dict.insert({ "right", "右边" });

	//map的遍历
	map<string, string>::iterator it = dict.begin();
	while (it != dict.end())
	{
		//cout << *it << endl;//it->operator*() -- C++不支持返回两个值
		//cout << (*it).first << ":" << (*it).second << endl;

		cout << it->first << ":" << it->second << endl;

		it++;
	}
	cout << endl;

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

我们这里使用匿名对象配合insert使用非常方便,库中也给了make_pair函数模板来表示键值对的创建。

 因为C++不支持返回两个值,所以我们这里用到了pair,通过pair的first和second,即可访问到两个值。

 

同时,map迭代器的使用方法和其他容器迭代器的使用方法一样,这是STL设计时为了方便使用,所采用的高维度泛型设计。

Key_value模型中,修改不能修改key,但是可以修改value。


std::map::operator[]的使用(*重点):

引入:

我们利用map来统计各个水果出现的次数:

通过查找来挨个遍历查找统计个数

void test_map2()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果",
					 "西瓜", "苹果", "香蕉", "苹果", "香蕉" };

	map<string, int> countMap;
	for (auto& str : arr)
	{
		map<string, int>::iterator it = countMap.find(str);
		if (it != countMap.end())
		{
			it->second++;
		}
		else
		{
			countMap.insert(make_pair(str, 1));
		}
	}
}

注意:

  • 只要是一对一对的值都可以用pair
  • first和second是不允许被修改的

insert的返回值是一个pair,pair的first是一个迭代器(如果插入成功了指向新插入的位置,如果插入失败了,则返回已经存在的结点的位置),pair的second的一个bool值,插入成功是返回true,失败是返回false。

所以我们还可以通过返回值来统计个数

但是map库中给出 operator[ ] ,其实他的应用会让上述郭晨改变的更简单易懂!

  • 在之前学习的容器中,operator[]都有随机访问的意思,这里的方括号已经没有了随机访问的意思
  • 这里operator[]的参数是key,返回值是value的引用

 

其中可以对文档中的最后一句对此函数的调用等效于:

我么能深入探讨一下,理解如下:

所以operator[ ]的参数是key,返回值是value的引用!!!

我们可以用operator[ ]来统计次数:

 

因为返回的是value的引用所以,可以修改,这样一来,operator[]兼顾了两个功能:插入 + 修改

同样当map需要插入的时候,也就有了如下的写法:


void test_map2()
{
	map<string, string> dict;
	//dict.insert(pair<string, string>("sort", "排序"));
	dict.insert(make_pair("sort", "排序"));
	dict.insert(make_pair("string", "字符串"));
	dict.insert(make_pair("count", "计数"));
	dict.insert(make_pair("count", "(计数)"));//插入失败


	dict["left"] = "左面";//插入+修改
	dict["right"];//插入
	dict["count"] = "(计数)";//修改
	cout << dict["count"] << endl; // 查找

	//map<string, string>::iterator dit = dict.begin();
	auto dit = dict.begin();
	while (dit != dict.end())
	{
		//cout << (*dit).first << ":" << (*dit).second << endl;
		cout << dit->first << ":" << dit->second << endl;

		++dit;
	}
	cout << endl;
}

operator[ ]的使用不仅可以简化计数等,还可以兼顾插入、修改、查找等作用!大家重点理解!

感谢您的阅读!


http://www.kler.cn/news/18265.html

相关文章:

  • EC6110-Hi3798MV310-当贝纯净桌面-卡刷强刷固件包
  • 在 node.js 里面写 MySQL 增删改查语句
  • helm部署nacos
  • 线性结构-数组
  • nestjs笔记
  • 打动人心的故事 | 如何利用文案在Facebook上塑造品牌形象
  • 面试整理 - 二叉排序树 c语言 及java 例子
  • 【在homeassistant的ONVIF中配置TP-Link】
  • SpringBoot启用web模拟测试(一)
  • 固态继电器的优点
  • 增强型PID-自适应-前馈-神经网络控制研究(Matlab代码实现)
  • 网页端操作提示「msg.js」库简介
  • linux实现CP指令
  • LeetCode 2432. 处理用时最长的那个任务的员工
  • 从爆火的“哇呀挖”,思考我软件开发的人生意义何在?
  • JDK17新特性之--JDK9到JDK17 String 新增的新方法
  • 53.MDL、NCNN和 TFLite比较
  • C++Primer 第一章
  • 将数据从 Oracle 加载到 Azure 的框架
  • 68元工业级双核A7,全新T113核心板震撼上市!
  • CSA发布|《洞察2022 云上数据安全与重要事项 》
  • spring-web HandlerAdapter 源码分析
  • 记录每日LeetCode 2432.处理用时最长的那个任务的员工 Java实现
  • Feign组件的使用及开发中使用方式
  • ZC706P试验PL_DDR3内存条的步骤方法
  • 使用SaleSmartly自动化流程的 5 个原因
  • 网络基础学习:什么是网络与网络发展史
  • 接口自动化测试之HTTP协议详解(敢称全网最全)
  • AP360X 可充电多功能LED手电筒与移动照明控制ic和应用方案
  • 【SpringBoot】SpringBoot集成ElasticSearch