map,set的使用

目录

🌟1. 关联式容器  

🌟2. 键值对(pair,make_pair)

🌟3. set

3.1 set文档介绍

3.2 set构造函数

3.3 set的常用函数

3.4 lower_bound函数

3.5 upper_bound函数

3.6 set的特点

🌟4.multiset 

🌟5.map

🌟5.1map文档介绍

🌟5.2 map的构造函数

🌟5.3 map的常用函数

🌟5.4 insert插入

5.4.1 insert不能插入相同的值

🌟5.5 operator[ ]

5.5.1  [ ]插入功能

5.5.2 修改功能

5.5.3 插入 + 修改功能

5.5.4 查找功能

5.5.5 最开始问题讲解

5.6 map的特点 

🌟6. multimap

🌟7.完结


  Explosion !

(最新更新时间——2025.2.17)

🌟1. 关联式容器  

我们之前学习过的序列式容器底层为线性序列的数据结构,例如 list ,其节点是线性存储的,一个节点存储一个数据,但这些数据未必有序。而关联式容器则比较特殊,存储的是 <key, value>  的键值对, 这意味着可以按照键值大小 key 以某种规则放置在适当的位置,因此,关联式容器没有首位的概念,因此没有头插尾插等相关操作。

那什么是键值对呢?我们接着来看


🌟2. 键值对(pair,make_pair)

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


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


在标准库中,专门定义了键值对 pair :

我们看一看它的源代码->:
 

//SGI 版 STL 中的实现
template <class T1, class T2>
struct pair {
  typedef T1 first_type;
  typedef T2 second_type;
 
  T1 first;	
  T2 second;	
  pair() : first(T1()), second(T2()) {}
  pair(const T1& a, const T2& b) : first(a), second(b) {}
 
};

可见,它有两个模板参数,这两个模板参数又被重命名为first_type与second_type,并且创建了两个对象,first和second

其中, pair 中的 first 表示键值, second 表示实值,在给  关联式容器中插入数据时,就可以构建键值对 pair 对象。 


这就意味着我们可以使用first与second这两个对象,对其两个模板参数 T1 , T2 进行访问,

也就是说,first指的是模板第一个参数所对应的值,second指的是模板第二个参数所对应的值


那么到底是什么意思,我们还是看一下代码 -> :

例如,下面构建了一个键值 key 为 string,实值 value 为 int 的匿名键值对 pair 对象

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<string>
using namespace std;
int main()
{
	pair<string, int> p1 ("hello", 123);
	cout << p1.first << " " << p1.second;
}


 此外,库中还设计了一个函数模板 make_pair, 可以根据传入的参数,去调用 pair 构建对象并返回 pair 对象

我们直接看代码->:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<string>
using namespace std;
int main()
{
	//这样的写法会导致编译报错
	//make_pair p2 ("hello", 123);

	auto p2 = make_pair("hello world", 123);
	cout << p2.first << " " << p2.second;

}


🌟3. set

1.set 实则是二叉搜索树中的 key 模型。key 模型主要的应用场景是判断在不在,例如:门禁系统,车库系统,检查文章中单词拼写是否正确等

2.它的底层就是二叉搜索树(红黑树)

3.set 只包含实值 value,或者说, set中->:实值就是键值,键值就是实值


3.1 set文档介绍

模板参数:

🌟T:底层存储的数据类型
🌟Compare:  比较方式,默认按照小于方式比较
🌟Alloc:  set中元素空间的管理方式,使用STL提供的空间配置器管理


set文档介绍:

🌟1.set是按照一定次序存储元素的容器
🌟2.在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。

🌟3.set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。

在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
🌟4.set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
🌟5.set在底层是用二叉搜索树(红黑树)实现的


set要点:
⭐️⭐️ set底层是一颗二叉树,内容可以删除,但不允许修改,查找某个元素时间复杂度为logN。
⭐️⭐️ set中不允许出现重复元素,本质是排序+去重
⭐️⭐️ set的正向迭代器遍历是一个升序序列,反向迭代器是一个降序序列。
⭐️⭐️ set存的是一个值value,但是在底层也是一个键值对,不过这个键值对两个值都相同,即<value,value>,我们在使用时,只需要插入就可以,不需要构建键值对。
⭐️⭐️set比较默认按照小于比较
⭐️⭐️对于unique算法,去重相邻重复元素,去重之前需要先排序,才可以达到去重的效果。并不会改变容器的大小,随后调用erase删除这些重复元素


3.2 set构造函数

在这里我们创建时,需要指定实值的类型。

代码展示一下->:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<vector>
#include<set>
using namespace std;
int main()
{
	//C++11中的万物都可{ }初始化
	set<int> s1 = { 8,3,10,15,3,6,9 };

	//迭代器区间构造
	vector<int> v1 = { 6,9,8,4,56,34,78 };
	set<int> s2(v1.begin(),v1.end());

	set<int> s3(s2);//拷贝构造set

	for (const auto& e : s2)
	{
		cout << e << " ";
	}
}


我们上述的构造函数默认是升序的,如果想要更改为逆序,我们需要这样写->:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<vector>
#include<string>
#include<set>
using namespace std;
int main()
{
	//迭代器区间构造
	vector<int> v1 = { 6,9,8,4,56,34,78 };
	set<int,greater<int>> s1(v1.begin(),v1.end());
	
	for (const auto& e : s1)
	{
		cout << e << " ";
	}
}


3.3 set的常用函数

set的常用函数如下:

接下来我们会逐一代码展示功能:


其实这些函数的使用与我们的vector等的用法相同:

唯一不同的点就是count的使用

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<vector>
#include<string>
#include<set>
using namespace std;
int main()
{
	//迭代器区间构造
	vector<int> v1 = { 6,9,8,4,56,34,78 };
	set<int> s1(v1.begin(), v1.end());

	//为空返回1,不为空返回0
	if (s1.empty())
	{
		cout << "s1为空" << endl;//这里不会打印
	}

	set<string> s2;
	if (s2.empty())
	{
		cout << "s2为空" << endl;//这里会打印
	}

	s2 = { "hello","world" };

	cout << s1.size() << " ";//打印7
	cout << s2.size() << " ";//打印2

	cout << endl;
	cout << s1.max_size() << endl;//打印576460752303423487

	s2.insert("null");//这里只是插入的意思,因为set的底层是二叉搜索树,所以只是插入
	s2.insert(s2.begin(), "oppo");//迭代器位置插入
	set<string> s9;//迭代器区间插入
	s9.insert(s2.begin(), s2.end());
	//字符串的排序是根据ascII值
	for (const auto& e : s2)
	{
		cout << e << " ";//打印hello null oppo world
	}
	cout << endl;
	for (const auto& e : s9)
	{
		cout << e << " ";//打印hello null oppo world
	}

	cout << endl;
	s2.erase("null");
	s2.erase(s2.begin());//也支持迭代器删除
	for (const auto& e : s2)
	{
		cout << e << " ";//打印hello null world
	}

	set<int> s3;
	s1.swap(s3);
	for (const auto& e : s1)
	{
		cout << e << " ";//什么都不打印
		//因为s1与s3交换了,所以s1没东西了
	}

	s2.clear();//清空s2的所有内容
	auto k = s3.find(4);
	cout << endl << typeid(k).name() << endl;//打印迭代器类型

	cout << endl << s3.count(4);//统计元素4出现了几次
	//这里打印1
}

结果如下->:


这里多了解一下:

count 在 set 中只能用作查找,因为 set 键值就是实值,因为其不允许冗余,因此只有一个键值

这样我们用count实现一个查找功能:

	//如果存在4,那么s3.count(4)会返回1,也就会打印4存在
	if (s3.count(4))
	{
		cout << "4存在" << endl;
	}


我们上述提到过,set不支持修改key值,因此我们这样写是会报错的->:

因为如果改变了,会破坏二叉搜索树的原则,set 中的普通迭代器在本质上也是 const 迭代器


3.4 lower_bound函数


lower_bound函数会记录大于等于val的迭代器
如果这个值存在,返回这个值的迭代器。
如果这个值不存在,返回大于这个值的迭代器。

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<vector>
#include<string>
#include<set>
using namespace std;
int main()
{
	//迭代器区间构造
	vector<int> v1 = { 6,9,8,4,56,34,78 };
	set<int> s1(v1.begin(), v1.end());
	
	set<int>::iterator l1 = s1.lower_bound(6);
	cout << *l1 << endl;//打印6

	set<int>::iterator l2 = s1.lower_bound(35);
	cout << *l2;//打印56
}

3.5 upper_bound函数

upper_bound也是同样的道理,但是不同的是,这个返回大于val的迭代器

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<vector>
#include<string>
#include<set>
using namespace std;
int main()
{
	//迭代器区间构造
	vector<int> v1 = { 6,9,8,4,56,34,78 };
	set<int> s1(v1.begin(), v1.end());
	
	set<int>::iterator l1 = s1.upper_bound(6);
	cout << *l1 << endl;//不打印6了,打印8

	set<int>::iterator l2 = s1.upper_bound(35);
	cout << *l2;//打印56
}

我们如果想要查找或者删除左闭区间,右闭区间就可以使用这个


3.6 set的特点


🌟4.multiset 

multiset的99%都与set相同,唯一不同的是multiset允许重复的值存在,本质就是排序。其他的都与set相同。


🌟5.map

map就是对应我们二叉树中的KV模型


🌟5.1map文档介绍

包括四个值:
🌟Key:键值对中的key值
🌟T:键值对中的value值
🌟Compare:比较器类型,一般是按照小于比较。如果是自定义类型,需要自己传递这个比较方式,达到要求。
🌟Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的


map文档介绍->:

🌟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通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树)


看一下要点:
🌟🌟map中存储的是一个键值对,键值对中第一个元素,也就是key,根据key值进行排序,确定唯一元素。
第二个值value,与key的内容关联。
🌟🌟元素不可以修改key的值,但是可以修改value的值。
🌟🌟支持下标访问,把key放在[ ]中,可以找到与之对应的value元素


🌟5.2 map的构造函数

map有以下构造方法

这里我们以代码为例->:

1.map是key和value都存在的,所以构造初始化时,需要两个模板参数

2.map的初始化需要依靠pair对象初始化


因此,它的构造函数如下构造:
 

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<vector>
#include<string>
#include<map>
using namespace std;
int main()
{
    //C++11中的万物都可{ }初始化
    std::map<std::string, int> myMap = {{"apple", 5}};

	//第一种方法,但是巨麻烦
	map<string, int> m1 = { pair<string,int>{"apple",1} };
	//也可以像下面这样写
	pair<string, int> a1 = { "apple", 1 };
	map<string, int> m1;
	m1.insert(a1);

	//第二种方法,运用make_pair,方便很多
	map<string, int> m2 = { make_pair("pear",2) };
    
    //第三种方法
	vector<pair<string, int>> v1 = { {"apple",1},{"pear",2} };
	map<string, int> m3;
	m3.insert(v1[0]);
	m3.insert(v1[1]);

    //注意这里的遍历,后面会讲
	for (const auto& e : m1)
	{
		cout << e.first << " " << e.second;
	}
	cout << endl;
	for (const auto& e : m2)
	{
		cout << e.first << " " << e.second;
	}
	cout << endl;
	for (const auto& e : m3)
	{
		cout << e.first << " " << e.second;
	}
}

先展示运行代码,再讲解auto

接下来讲解auto遍历

我们知道,map是需要依靠pair来初始化的,而每一个pair具有两块内容,第一块为first,第二块为second,如果我们不指名输出first还是second,那么就会编译报错,因为它不知道该输出哪个


🌟5.3 map的常用函数

可见,map的常用函数与set的常用函数99%都一样(一样指的是只传键值即可,因为map存在key值,与value值,而map的函数用法除了insert与operator[ ]),只有一点不一样,那就是operator[ ]

我们下面展示几个不同的


🌟5.4 insert插入

map的insert插入需要插入pair对象,代码如下->:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<vector>
#include<string>
#include<map>
using namespace std;
int main()
{
	vector<pair<string, int>> v1 = { {"vivo",1900},{"oppo",2000} };
	map<string, int> m3;
	m3.insert(pair<string,int> {"apple",5 });
	m3.insert(make_pair("pear",10));
	m3.insert(v1.begin(), v1.end());
	
	for (const auto& e : m3)
	{
		cout << e.first << " " << e.second;
		cout << endl;
	}
}

效果图如下->:

因为map的底层是KV树,它也是二叉搜索树,所以会自动排序


5.4.1 insert不能插入相同的值

我们把上面的代码多加几句->:

map无法插入相同的值,map判断插入的值相不相同只会根据key值去比较,与value值无关


🌟5.5 operator[ ]

我们先看这样的一段代码与效果图->:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<vector>
#include<string>
#include<map>
using namespace std;
int main()
{
	vector<string> word = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨" };
	map<string, int> table;

	for (auto& e : word)
		table[e]++;

	for (auto e : table)
		cout << "<" << e.first << ":" << e.second << ">" << endl;

	return 0;
}

这是怎么实现的? ? ?

其实这是因为operator[ ] 兼顾了这几种功能:插入、修改、插入+修改、查找,接下来进行讲解->:


5.5.1  [ ]插入功能

当使用 operator[] 访问一个 map 中不存在的键时,它会自动插入一个新的键值对到 map 中,并且该键对应的值会被初始化为该值类型的默认值

#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> myMap;
    // 访问不存在的键 "apple",会插入该键,值初始化为 0
    myMap["apple"]; 
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    return 0;
}

输出结果

apple: 0

5.5.2 修改功能

如果 map 中已经存在某个键,使用 operator[ ] 可以直接修改该键对应的值

或者说,如果 map 中已经存在某个键,使用 operator[ ]返回的值是该键对应的值

#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> myMap = {{"apple", 5}};
    // 修改 "apple" 对应的值
    myMap["apple"] = 10; 
    //如果不写10(什么都不赋值)不会自动增加
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    return 0;
}

输出结果

apple: 10

5.5.3 插入 + 修改功能

可以通过 operator[] 先插入一个新的键值对,然后立即修改该值。实际上,这就是前面插入和修改功能的结合使用

#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> myMap;
    // 插入 "banana" 键,值初始化为 0,然后修改为 20
    myMap["banana"] = 20; 
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    return 0;
}

输出结果

banana: 20

5.5.4 查找功能

可以通过 operator[] 来尝试访问某个键对应的值,从而实现查找的目的。不过需要注意的是,如果键不存在,它会进行插入操作,这可能不是我们期望的行为。如果只是单纯想查找,建议使用 find 成员函数
#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> myMap = {{"apple", 5}};
    // 查找 "apple" 对应的值
    int value = myMap["apple"]; 
    std::cout << "Value of apple: " << value << std::endl;
    return 0;
}

输出结果

Value of apple: 5

5.5.5 最开始问题讲解

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<vector>
#include<string>
#include<map>
using namespace std;
int main()
{
	vector<string> word = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨" };
	map<string, int> table;

	for (auto& e : word)
		table[e]++;

	for (auto e : table)
		cout << "<" << e.first << ":" << e.second << ">" << endl;

	return 0;
}

我们回到这个代码,我们以苹果为例,第一次出现苹果时,执行table["苹果"],检测到map中没有改键值,就自动插入,插入后的table["苹果"]会返回该键对应的值,也就会返回0,之后碰到++,就变为了1

以此类推,每一次插入苹果,value值都会++,因此输出了最后的5


5.6 map的特点 


🌟6. multimap

multimap 允许键值出现重复


由于 multimap 允许出现多个重复的键值,因此无法使用 operator[ ],因为 operator[ ] 无法确认调用者的意图 -> 不知道要返回哪个键对应的实值。

除此此外 multimap  与 map 没有区别。


最后 : multiset / multimap 用的较少,重点掌握 set / map 即可。


🌟7.完结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值