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

【C++】set和map

目录

引言:

1.set 

1.1键值对:

1.2set介绍

1.21set的构造

1.22函数接口

2.multiset 

2.1特性

2.2multiset的修改 

3.map 

3.1介绍

3.2插入

3.3map的遍历

3.5查找(find)和删除(erase)

 4.multimap

4.1介绍


引言:

C++zhongdeSTL库有六大配件

 容器是其中一大重要的组成部分。我们前面学了string,vector,list 以及deque,一会我们还要学剩下的四个,那容器有没有分类呢?

我们只看顺序性容器和关联性容器 

两者区别:

  1. 顺序容器只有实值val。
  2. 关联容器的一个元素包含两个部分:键值对(key-value) 即<k值(键值)|实值>
  3. 顺序容器不涉及排序,关联容器内部自动排序
  4. 本质区别:顺序容器通过元素在容器中的位置顺序存储和访问元素,而关联容器则是通过键(key)存储和读取元素的。

标准容器类

特点

顺序性容器

vector

从后面快速的插入与删除,直接访问任何元素

deque

从前面或后面快速的插入与删除,直接访问任何元素

list

双链表,从任何地方快速插入与删除

关联容器

set

快速查找,不允许重复值

multiset

快速查找,允许重复值

map

一对多映射,基于关键字快速查找,不允许重复值

multimap

一对多映射,基于关键字快速查找,允许重复值

1.set 

1.1键值对:

用来表示具有一一对应关系的结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。

比如:英汉互译字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义一一对应,即通过该英文单词,在词典中就可以找到与其对应的中文含义。
 

1.2set介绍

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中查找某个元素,时间复杂度为:logN
  • 7、set中的元素不允许修改,因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。
  • 8、set中的底层使用二叉搜索树(红黑树)来实现。

1.21set的构造

既然set是容器,那他也可以像前几个一样被构造。头文件#include<set>

  • 1.空构造和拷贝构造
set<int> s1;
set<int> s2(s1);
  • 2.迭代器构造
string s3("I love C++");
set<char> s4(s.begin(), s.end());
  • 3.大于比较

set的底层是搜素二叉树,默认是小于排序,也可是调成大于排序。

set<int, greater<int>> s5;

1.22函数接口

在打印数据是时我们要用到遍历打印,所以讲一下迭代器

迭代器接口

Iterators:  

迭代器
begin                              

将迭代器返回到开头(公共成员函数)

end返回迭代器到结束(公共成员函数)
rbegin返回反向迭代器以反向开始(公共成员函数)
rend返回反向迭代器到反向结束(公共成员函数)
cbegin将const_iterator返回到开头(公共成员函数)
cend返回const_iterator到结束公共成员函数)
crbegin返回const_reverse_iterator反转开始(公共成员函数)
crend返回const_reverse_iterator反转端(公共成员函数)

这里也就是正反向迭代器用的多其他1知道就行。

容量接口:

capacity容量大小
empty                        反回容器是否为空公共成员功能)
size返回容器大小(公共成员功能)
max_size返回最大大小(公共成员函数)

修改接口:

我直接把一些代码放一起了

#define  _CRT_SECURE_NO_WARNINGS  1
#include<iostream>
#include<set>
using namespace std;

void test1()
{
	set<int> s1;
	s1.insert(1);   //插入操作
	s1.insert(3);
	s1.insert(5);
	s1.insert(7);
	s1.insert(4);
	set<int> s2(s1);
	set<int>::iterator it = s1.begin();
	for (auto e : s1)
	{
		cout << e << " ";   //  1 3 4 5 7,是升序排列,别搞错了
	}
	cout << endl;
	set<int>::iterator pos1 = find(s1.begin(), s1.end(), 5);
	set<int>::iterator pos2 = s1.find(7);   //set自带的查找函数
	if (it != s1.end())
	{
		s1.erase(pos1);   //删除5
		s1.erase(pos2);
		s1.insert(10);
		s1.insert(20);
	}
	while (it != s1.end())
	{
		cout << *it << " ";    //1 3 4 10 20
		*it++;
	}
	cout << endl;
	//删除指定区间
	set<int>::iterator ret = s2.find(5);
	s2.erase(s2.begin(), ret);
	for (auto e : s2)
	{
		cout << e << " ";   //5 7
	}
	cout << endl;
  //寻找是否有val,找到返回1
	if (s2.count(5)) // s2中有5就返回1,进入循环
	{
		cout << "s2中有5:" << endl;  //s2中有5
	}
	cout << s2.empty() << endl;  // 判空
	cout << s1.size() << endl;   //s1的个数是5
	cout << s2.size() << endl;   //s2的个数是2
}

int main()
{
	test1();
}

我就简单的举几个例子来写一下代码,其他就不在写了,交给各位大神了。

2.multiset 

2.1特性

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

注意:

1、multiset中在底层中存储的是<value, value>的键值对
2、mtltiset的插入接口中只需要插入即可
3、与set的区别是,multiset中的元素可以重复,set是中value是唯一的。
4、使用迭代器对multiset中的元素进行遍历,可以得到有序的序列
5、multiset中的元素不能修改
6、在multiset中找某个元素,时间复杂度为O(logN)
7、multiset的作用:可以对元素进行排序

也就是说当我们用multiset进行升序排列时,插入的数据可以重复,但是有些接口就会和set有些不一样。

2.2multiset的修改 

 multiset的count和erase跟set相比有很大去别:

  1. set.count(val)是如果找到val就返回1因为multiset的数可以重复,multiset.count(val)找到val后返回multiset中有几个val。
  2. set.erase(val)是找到要删除的val后,返回1multiset.erase(val)找到要删除的val后返回val有几个,并且全删val。
void test2()
{
	multiset<int> multiset;
	multiset.insert(4);
	multiset.insert(5);
	multiset.insert(2);
	multiset.insert(1);
	multiset.insert(1);
	multiset.insert(3);
	multiset.insert(3);
	multiset.insert(3);
	for (auto e : multiset)
	{
		cout << e << " ";   
	}
	cout << endl;
	set<int> set(multiset.begin(), multiset.end());
	cout << multiset.count(1) << endl;//2 返回1的个数
	cout << set.count(1) << endl;//1 返回一个bool值,存在返回1
}
void test2()
{
	multiset<int> multiset;
	multiset.insert(4);
	multiset.insert(5);
	multiset.insert(2);
	multiset.insert(1);
	multiset.insert(1);
	multiset.insert(3);
	multiset.insert(3);
	multiset.insert(3);
	for (auto e : multiset)
	{
		cout << e << " ";   
	}
	cout << endl;
	set<int> set(multiset.begin(), multiset.end());
	

	cout << multiset.erase(1) << endl;//2 返回删除的1的个数
	for (auto e : multiset)
	{
		cout << e << " ";  //2 3 3 3 4 5
	}
	cout << endl;
	cout << set.erase(4) << endl;//1 返回一个bool值,存在删除的值返回1
	for (auto e : multiset)
	{
		cout << e << " ";  //2 3 3 3 4 5
	}
	cout << endl;
	auto pos1 = multiset.find(3); //返回中序的第一个3的迭代器
	while (pos1 != multiset.end())
	{
		cout << *pos1 << " ";//3 3 3 4 5
		pos1++;
	}
}

3.map 

3.1介绍

  • 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通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。
  • 7头文件#include<map>

3.2插入

 

  • 1.创建map对象
#define  _CRT_SECURE_NO_WARNINGS  1
#include<iostream>
#include<map>
using namespace std;
 
int main()
{
    map<string, int> m;//创建一个map对象m
	//向m中插入pair
 
	map<string, int> m1(m.begin(), m.end);//用m的区间构造m1
	map<string, int> m2(m1);//用m1拷贝构造m2
 
	return 0;
}
  • 2.插入insert

map中插入的是pair是一个结构体,它的内部实现 

  • 普通插入:
void test1()
{
	map<string, string> dict;   //创造一个词典dict
	pair<string, string> kv("English", "英语");
	dict.insert(kv);
}
  • 匿名对象插入:
void test1()
{
	map<string, string> dict;   //创造一个词典dict
	
	dict.insert(pair<string, string>("English", "英语"));
	dict.insert(pair<string, string>("Math", "数学"));
	dict.insert(pair<string, string>("Chinese", "语文"));
}

make_pair模板

void test1()
{
	map<string, string> dict;   //创造一个词典dict
	dict.insert(make_pair("vector", "顺序表"));
	dict.insert(make_pair("list", "链表"));
}

3.3map的遍历

 map的迭代器和上面的set的一毛一样,不再写了,直接上手。

void test2()
{
		map<string, string> dict;
		dict.insert(make_pair("string", "字符串"));
		dict.insert(make_pair("vector", "顺序表"));
		dict.insert(make_pair("list", "链表"));
		dict.insert(make_pair("stack", "栈"));
		dict.insert(make_pair("queue", "堆"));
		//遍历
		map<string, string>::iterator it = dict.begin();
		  //auto mit = dict.begin();  跟上面的效果一样
		
		while (it != dict.end())
		{
			cout << it->first << ":" << it->second << endl;
			it++;
		}
		cout << endl;
		//范围for
		for (const auto& e : dict)
			cout << e.first << ":" << e.second << endl;
}

cout << it->first << ":" << it->second << endl;中的first和second是结构体pair的成员,本应该写成

cout <<(*it).first << ":" << (*it).second << endl;

但是pair不是结构体吗,支持->,但是应该出现->->编译器为了好看就省去第二个了。

map的key不允许被修改。

3.4operate[]

砍看一下这个例子:

void test3()
{
		string str[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		map<string, int> dict;
		for (auto& e : str)
		{
			map<string, int>::iterator it = dict.find(e);
			if (it != dict.end())
			{
				it->second++;
			}
			else
			{
				dict.insert(make_pair(e, 1)); //当水果第一次出现就赋值为1
			}
		}
		for (const auto& kv : dict)
			cout << kv.first << ":" << kv.second << endl;
}

为啥it->second而不是first?

fist是第一个模板参数T1,second是第二个模板参数T2。也可以认为T1是key类型在题中是<string>T2是value类型在题中是<int>。 

map可以用[]进行访问(不是下标访问,一定要注意),我们修改一下源代码。

void test3()
{
		string str[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		map<string, int> dict;
		for (auto& e : str)
		{
			dict[e]++;
		}
		for (const auto& kv : dict)
			cout << kv.first << ":" << kv.second << endl;
}

 

  •  运算符重载
mapped_type& operator[] (const key_type& k);

具体实现代码如下所示

mapped_type& operator[] (const key_type& k)
{
	return (*((this->insert(make_pair(k, mapped_type()))).first)).second;
}

这个如果不好理解的话,看一下下面这个

mapped_type& operator[] (const key_type& k)
{
    //1、调用insert返回迭代器区间
	pair<iterator, bool> ret = insert(make_pair(k, mapped_type()));
    //2、利用ret调用value值
	return ret.first->second;//return (*(ret.first)).second;
}
  • 首先调用insert函数插入键值对返回迭代器ret
  • 通过返回的迭代器ret调用元素值value。

在这里我们又分两种情况去处理:

  1. 如果k在map对象中,则插入失败,返回的bool类型为false,返回的迭代器为k所在结点的迭代器,而迭代器解引用*(ret.first)获得的就是pair,最后再通过pair访问到value值,整体可优化成ret.first->second,这里返回引用的好处为查找k对应v,修改k对应v。
  2. 如果k不在map对象中,则插入成功,返回的bool类型为true,返回的迭代器为新插入的k所在结点的迭代器位置,接着调用ret.first获得pair的迭代器,再通过->second获得value,这里返回引用的好处为插入和修改。

3.5查找(find)和删除(erase)

  • 查找find:
iterator find (const key_type& k);
const_iterator find (const key_type& k) const;

返回值类型:

根据k值在map中寻找,找到后,返回对应k值位置的迭代器若未找到,则返回map::end的迭代器。

void test4()
{
	map<string, string> dict;
	dict.insert(make_pair("left", "左"));
	dict.insert(make_pair("right", "右"));
	dict.insert(make_pair("front", "前"));
	dict.insert(make_pair("back", "后"));
	string str;
	cin >> str;//输入left
	map<string, string>::iterator pos = dict.find(str);
	if (pos != dict.end())
	{
		cout << pos->first << " : " << pos->second << endl;//left : 左
	}
	else
	{
		cout << "没找到" << endl;
	}
}

  • 删除erase 
size_type erase (const key_type& k);//通过k删除
void test6()
{
	map<int, string> m;
	m.insert(make_pair(1, "one"));
	m.insert(make_pair(2, "two"));
	m.insert(make_pair(3, "three"));
	m.insert(make_pair(4, "four"));
	m.insert(make_pair(5, "five"));
	m.insert(make_pair(6, "six"));
	m.insert(make_pair(9, "nine"));
	for (const auto& kv : m)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	//指定key值删除
	cout << m.erase(4) << endl;   //找到4返回1
	map<int, string>::iterator pos = m.find(5);
	//删除迭代器区间
	if (pos != m.end())
	{
		m.erase(pos, m.end());//删除5到9的数字
	}
	for (const auto& kv : m)
	{
		cout << kv.first << ":" << kv.second << endl;  // 1 2 3
	}
}
int main()
{
	test6();
}

 4.multimap

multimap基本和map一毛一样,除了multimap中的key是可以重复的

4.1介绍

  •  Multimap是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对<key,value>,其中多个键值对之间的key是可以重复的
  • 在multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内容。key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起,value_type是组合key和value的键值对:typedef pair<const Key, T> value_type;
  • 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对key进行排序的。
  • multimap通过key访问单个元素的速度通常比unordered_multimap容器慢但是使用迭代器直接遍历multimap中的元素可以得到关于key有序的序列。
  • multimap在底层用二叉搜索树(红黑树)来实现。

multimap和map的区别:

  1. map中的valuevalue是唯一的,不允许重复。multimap可以。
  2. multimap中没有[]因为它的key和value是一对多的关系,比如上面的苹果,那key是苹果,但是苹果出现了好几次,每一个都有一个value,那他到底返回哪一个呢?为啥map能返回呢,因为在map中每一个key(苹果)都对应一个自己的value,所以每次返回一个value.但是multimap是一个key有好几个value.
void test7()
{
	multimap<string, string> mlp1;
	mlp1.insert(make_pair("one", "一"));
	mlp1.insert(make_pair("two", "二"));
	mlp1.insert(make_pair("three", "三"));
	mlp1.insert(make_pair("four", "四"));
	mlp1.insert(make_pair("spring", "五"));

	for (auto e : mlp1)
	{
		cout << e.first << ":" << e.second << endl;
	}
}
int main()
{
	test7();
}

相关文章:

  • visual studio snippet常用注释片段
  • java基础之对线程的理解
  • 第 8 章 机器人底盘Arduino端PID控制(自学二刷笔记)
  • 智慧教育平台:选课系统的Spring Boot实现
  • WebRTC实时音视频通话之语音通话设计与实践
  • 【DevOps】linux 的网络绑定 (Bonding)应用
  • nginx之重写功能 模块指令 防盗链
  • 大数据面试总结三
  • C 程序结构
  • 【ArcGIS】基本概念-空间参考与变换
  • shardingsphere 集成springboot【水平分表】
  • Python爬虫实战入门:爬取360模拟翻译(仅实验)
  • Jetson Orin 平台相机调试报四次“err_data” 后stream stop,其它平台工作正常
  • 【工具】Git-码农“吃饭的碗”要拿好
  • pytest fixture及conftest详解一 (各个参数的使用说明)
  • MySQL 一键卸载
  • AI二次开发C#图形样式
  • 项目实战(管理员管理(续),Spring Security框架)
  • JS原型概念讲解
  • 【REST系列】详解REST架构风格 —— 带你阅读Web发展史上的一个重要技术文献
  • 用C++写了基于gec6818开发板上LCD操作的一个类--附带注释(详细)
  • 3、spring cloud 五大组件
  • Redis_在Windows上启动多个Redis服务端
  • 项目启动端口被占用 -- Web server failed to start. Port 8082 was already in use.
  • 做运营如何蹭热点?
  • JAVA反序列化学习笔记
  • 小程序-网络数据请求
  • Vue中KeepAlive 原理与源码分析
  • 蛇形矩阵(模拟)
  • SpringMVC---->自我实现底层机制(吃透springMVC)
  • 计算机毕业设计(附源码)python医疗健康查询系统
  • 找个好用的录屏软件,怎么这么难?