前言:前面我们学习了STL中的string、vector、list等等,今天我们就进一步学习更复杂的容器,map和set。
💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
关联式容器于键值对
什么是关联式容器
C++中的关联式容器是一种可以存储键-值对的容器,它使用键来快速检索值。与序列式容器不同的是,其里面存储的是<key, value>结构的键值对(不懂的可以去看看上期的博客),在数据检索时比序列式容器效率更高。C++标准库提供了四种关联式容器:std::set、std::multiset、std::map和std::multimap。
-
std::set:std::set是一个基于红黑树实现的有序集合,它存储唯一的元素。元素按照默认的比较函数进行排序,也可以通过自定义比较函数进行排序。
-
std::multiset:std::multiset是一个基于红黑树实现的有序集合,它可以存储重复的元素。元素按照默认的比较函数进行排序,也可以通过自定义比较函数进行排序。
-
std::map:std::map是一个基于红黑树实现的有序关联容器,它存储键-值对,并根据键进行排序。键是唯一的,如果插入重复的键,新的值将覆盖旧的值。
-
std::multimap:std::multimap是一个基于红黑树实现的有序关联容器,它可以存储重复的键-值对。键是可以重复的,插入重复的键-值对会按顺序插入。
关联式容器提供了快速的元素查找操作,时间复杂度为O(log n)。它们还提供了插入和删除元素的操作,并且保持元素的有序性。
键值对
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。
键值对- pair
在C++中,键值对可以使用pair模板类来表示,该类定义在头文件中。pair模板类包含两个公共成员变量first和second,分别表示键和值。
下面是一个使用pair的简单示例:
#include <iostream>
#include <utility>
int main() {
std::pair<int, std::string> myPair; // 创建一个键值对对象
myPair.first = 1; // 设置键的值
myPair.second = "hello"; // 设置值的值
std::cout << "Key: " << myPair.first << std::endl; // 输出键的值
std::cout << "Value: " << myPair.second << std::endl; // 输出值的值
return 0;
}
上述示例中,我们使用pair模板类创建了一个键值对对象myPair。然后,我们通过myPair的first和second成员变量分别设置了键和值的值。最后,我们通过访问myPair的first和second成员变量来输出键和值的值。
pair模板类还提供了一些有用的成员函数和操作符,例如make_pair函数用于创建一个pair对象,==和!=操作符用于比较两个pair对象是否相等等。我们可以根据具体的需求选择适合的成员函数和操作符来操作键值对。
pair的基本结构
在C++中,pair是一个模板类,定义在头文件中。它的基本结构如下:
template <class T1, class T2>
struct pair {
typedef T1 first_type; // 第一个元素的类型
typedef T2 second_type; // 第二个元素的类型
T1 first; // 第一个元素
T2 second; // 第二个元素
// 构造函数
pair();
pair(const T1& x, const T2& y);
template <class U, class V>
pair(const pair<U, V>& p);
// 比较操作符重载
bool operator==(const pair& rhs) const;
bool operator!=(const pair& rhs) const;
bool operator<(const pair& rhs) const;
bool operator>(const pair& rhs) const;
bool operator<=(const pair& rhs) const;
bool operator>=(const pair& rhs) const;
};
pair类有两个模板参数 T1
和 T2
,分别表示第一个元素和第二个元素的类型。
pair类提供了默认构造函数 pair()
,以及接受两个参数的构造函数 pair(const T1& x, const T2& y)
,用于初始化pair对象。
此外,pair类还提供了一个模板构造函数 template <class U, class V> pair(const pair<U, V>& p)
,用于将不同类型的pair对象进行转换。
对于比较操作,pair类重载了比较操作符,如 ==
、!=
、<
、>
、<=
、>=
,用于比较两个pair对象。
pair类的 first
成员变量表示第一个元素,second
成员变量表示第二个元素。
使用pair的时候,可以通过 .first
和 .second
成员变量来访问pair对象的两个元素。
make_pair
C++中的make_pair
是一个函数模板,定义在头文件中。它用于快速创建一个pair
对象,并自动推导出pair
中模板参数的类型。
make_pair
函数的定义如下:
template <class T1, class T2>
pair<T1, T2> make_pair(T1&& x, T2&& y);
make_pair
函数接受两个参数,分别是第一个元素和第二个元素的值。函数会自动推导出pair
中模板参数的类型,并返回一个pair
对象。
使用make_pair
函数可以方便地创建一个pair
对象,例如:
int main() {
int a = 1;
double b = 3.14;
auto myPair = make_pair(a, b);
std::cout << myPair.first << ", " << myPair.second << std::endl;
return 0;
}
在上面的例子中,通过make_pair(a, b)
创建了一个pair
对象,其中int
类型的变量a
被推导为pair
的第一个元素,double
类型的变量b
被推导为pair
的第二个元素。
make_pair
可以方便地创建一个pair
对象,避免了手动写出完整的pair
对象的模板参数类型。
pair与make_pair的总结
下面列举了一些pair的常见用法:
- 作为函数的返回值:pair可以用来在函数中返回多个值,特别适用于需要返回两个不同类型的值的情况。
pair<int,string> getStudentInfo()
{
int id = 1234;
string name = "Alice";
return make_pair(id, name);//因此C++无法返回多个参数,通过pair可以解决这一缺陷
}
int main() {
pair<int, string> student = getStudentInfo();
cout << "ID: " << student.first <<endl;//输出1234
cout << "Name: " << student.second << endl;//输出Alice
return 0;
}
- 作为容器的元素:pair可以作为一些序列式容器(如vector、list等)的元素,以便存储多个值。
int main()
{
vector<pair<int, string>> students;//存储在vector中
students.push_back(make_pair(1, "Alice"));
students.push_back(make_pair(2, "Bob"));
for (auto& s1 : students) {
cout << "ID: " << s1.first << ", Name: " << s1.second << endl;
}
list<pair<int, string>> grade;//存储在list中
grade.push_back(make_pair(100, "weiweiwei"));
grade.push_back(make_pair(90, "tang"));
grade.push_back(make_pair(99, "zhou"));
for (auto& s2 : grade) {
cout << "ID: " << s2.first << ", Name: " << s2.second << endl;
}
return 0;
}
树形结构的关联式容器
根据应用场景的不桶,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结
构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。
1.set
什么是set
在C++中,std::set是一个容器类模板,它实现了一个有序的集合。std::set内部使用二叉搜索树(红黑树)数据结构来存储元素,并根据元素的键值进行自动排序。
- set是按照一定次序存储元素的容器
- 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。
set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。 - 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行
排序。 - set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对
子集进行直接迭代。 - set在底层是用二叉搜索树(红黑树)实现的。
set的常见使用方法
- 包含 头文件并使用 std::set 命名空间。
#include <set>
using namespace std;
- 声明和初始化 std::set 对象
set<int> mySet; // 创建一个空的 set 对象
set<int> mySet = {1, 2, 3, 4, 5}; // 使用 initializer list 初始化 set 对象
- 添加元素到 std::set
int main()
{
set<int> mySet; // 创建一个空的 set 对象
mySet.insert(10); // 添加元素到 set 中
mySet.insert({ 20, 30, 40 }); // 添加多个元素到 set 中
return 0;
}
- 获取 std::set 的大小
int main()
{
set<int> mySet; // 创建一个空的 set 对象
mySet.insert({ 20, 30, 40 }); // 添加多个元素到 set 中
int size = mySet.size(); // 获取 set 的元素数量
return 0;
}
- 遍历 std::set 中的元素
注:和搜索树一样,他的遍历就是升序
int main()
{
set<int> mySet; // 创建一个空的 set 对象
//set<int> mySet = { 1, 2, 3, 4, 5 }; // 使用 initializer list 初始化 set 对象
mySet.insert(10); // 添加元素到 set 中
mySet.insert({ 20, 0, 40 }); // 添加多个元素到 set 中
int size = mySet.size(); // 获取 set 的元素数量
for (const auto& element : mySet)//按照升序的方式遍历
{
cout << element << " "; // 打印 set 中的元素
}
return 0;
}
- 查找 std::set 中的元素
int main()
{
set<int> mySet; // 创建一个空的 set 对象
mySet.insert(10); // 添加元素到 set 中
mySet.insert({ 20, 0, 40 }); // 添加多个元素到 set 中
auto it = mySet.find(10); // 查找元素 10 在 set 中的迭代器
if (it != mySet.end()) {
// 元素 10 存在于 set 中
cout << *it << endl;
}
else
cout << "不存在该元素" << endl;
return 0;
}
- 删除 std::set 中的元素
int main()
{
set<int> mySet; // 创建一个空的 set 对象
mySet.insert(10); // 添加元素到 set 中
mySet.insert({ 20, 0, 40 }); // 添加多个元素到 set 中
mySet.erase(10); // 从 set 中删除元素 10
mySet.clear(); // 清空 set 中的所有元素
return 0;
}
上述示例演示了一些常见的std::set的用法。std::set还有其他更多功能和操作,可根据具体需求查阅C++文档以获得更详细的了解。
multiset
在C++中,std::multiset是一个容器类模板,它是一个有序的包含重复元素的集合(可以理解成允许重复值的set)。std::multiset允许在集合中插入重复的元素,并且会按照特定的比较准则自动进行排序。
以下是std::multiset的基本特性和使用方法:
- 包含头文件并使用std::multiset命名空间。
#include <set>
using namespace std;
- 声明和初始化std::multiset对象
int main()
{
multiset<int>s1;//创建一个对象
multiset<int> s2 = {10,20,30,40,50,10,20,30};//使用initerator list初始化
return 0;
}
- 插入元素到multiset中
int main()
{
multiset<int> s1;
s1.insert(1);//插入数据
s1.insert(3);
s1.insert(0);
s1.insert(5);
s1.insert(2);
return 0;
}
- 遍历multiset中的元素
int main()
{
multiset<int> s1;
s1.insert(1);
s1.insert(3);
s1.insert(0);
s1.insert(5);
s1.insert(2);
for (auto it = s1.begin(); it != s1.end(); ++it)//遍历容器
{
cout << *it << " ";
}
return 0;
}
- count(key):返回multiset中值为key的元素的个数
int main()
{
multiset<int> my_set = { 10,20,30,10,10,50 };
int count = my_set.count(10);//查看10出现的次数
cout << count << endl;
return 0;
}
- find(key):返回指向第一个值为key的元素的迭代器,如果找不到则返回end()迭代器
int main()
{
multiset<int> myMultiset = { 10,20,30,10,10,50 };
auto it = myMultiset.find(20);
if (it != myMultiset.end()) {
cout << "找到了: " << *it << endl;//查找20,找到了返回指向第一个值为key的元素的迭代器
}
else {
cout << "未找到" << endl;//如果找不到则返回end()迭代器
}
return 0;
}
- erase(key):删除所有值为key的元素
int main()
{
multiset<int> myMultiset = { 10,20,30,10,10,50 };
myMultiset.erase(20);//删除20,删除中序遍历出现的第一个20
return 0;
}
- erase(iterator):删除指定迭代器所指向的元素
int main()
{
multiset<int> myMultiset = { 10,20,30,10,10,50 };
auto it = myMultiset.begin();
myMultiset.erase(it);//删除迭代器所指向的元素
return 0;
}
9 . clear():清空multiset中的所有元素
int main()
{
multiset<int> myMultiset = { 10,20,30,10,10,50 };
myMultiset.clear();//清空容器中所有的元素
return 0;
}
- size():返回multiset中的元素个数
int main()
{
multiset<int> myMultiset = { 10,20,30,10,10,50 };
int size = myMultiset.size();
cout << size << endl;//输出元素个数
return 0;
}
- empty():判断multiset是否为空
int main()
{
multiset<int> myMultiset = { 10,20,30,10,10,50 };
bool isEmpty = myMultiset.empty();//判断容器是否为空
return 0;
}
std::multiset还可以使用自定义的比较函数进行排序,方法和使用std::set相同。可以提供一个比较函数作为multiset的第二个模板参数。
需要注意的是,std::multiset中的元素默认是按照升序进行排序的,如果需要自定义排序准则,可以提供一个比较函数作为multiset的第二个模板参数。
综上所述,std::multiset是一个有序的允许重复元素存在的容器,可以根据指定的排序准则对元素进行自动排序。它提供了插入、遍历、查找、删除等常用功能,通过成员函数可以对multiset进行操作和查询。
map
C++中的map是一种关联容器,它以键-值对(key-value)的形式存储数据。每个键都唯一,且根据键的大小进行自动排序。map是基于红黑树实现的,因此查找、插入和删除操作的时间复杂度为O(logN)。
下面是一些常见的使用方法和函数:
- 声明和初始化map:
#include <map>
#include <iostream>
using namespace std;
int main()
{
map<string, int> my_map;
map<string, int> My_map = { {"weiwei",90},{"zhou",100}, {"tang", 90}};
return 0;
}
- 插入元素
int main()
{
map<string, int> my_map;
my_map.insert(make_pair("weiwei", 100));
my_map.insert({ "weiwei", 90 });
return 0;
}
- 访问元素
int main()
{
map<string, int> my_map;
my_map.insert(make_pair("weiwei", 100));
my_map.insert({ "zhou", 90 });
cout << my_map["weiwei"];//输出100,[]是输入对应的key值,然后输出对应的value
return 0;
}
- 删除元素- 根据键删除对应的键值对
int main()
{
map<string, int> my_map;
my_map.insert(make_pair("weiwei", 100));
my_map.insert({ "zhou", 90 });
my_map.erase("weiwei"); // 根据键删除对应的键值对
return 0;
}
- 查找元素
如果find函数在map中找到了指定的键,则返回一个指向该键值对的迭代器;如果找不到指定的键,则返回指向map末尾的迭代器,即map::end()
int main()
{
map<string, int> my_map;
my_map.insert(make_pair("weiwei", 100));
my_map.insert({ "zhou", 90 });
auto it = my_map.find("weiwei");//查找key值为"weiwei"的迭代器的位置
cout << it->second << endl;
return 0;
}
6.遍历map
int main()
{
map<string, int> my_map = { {"wei",90},{"zhou",100},{"tang",99} };
map<string, int>::iterator it = my_map.begin();
for (; it != my_map.end(); it++)
{
cout << it->first << " " << it->second << endl;
}
//范围for
for (auto e : my_map)
{
cout << e.first << " " << e.second << endl;
}
return 0;
}
- 获取map的大小
int main()
{
map<string, int> my_map = { {"wei",90},{"zhou",100},{"tang",99} };
cout << my_map.size() << endl;//输出map的大小
return 0;
}
- 运算符[ ]的用法
在C++的map中,可以使用[]运算符来访问和修改map中的元素。该运算符接受一个键作为参数,并返回与该键关联的值。如果该键不存在于map中,则会自动插入一个新键值对,并返回默认构造的值。
下面是使用[]运算符的示例代码:
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> myMap;
// 插入键值对
myMap["apple"] = 10;
myMap["banana"] = 5;
myMap["orange"] = 8;
// 访问和修改值
std::cout << "Number of apples: " << myMap["apple"] << std::endl;
myMap["apple"] = 15;
std::cout << "Updated number of apples: " << myMap["apple"] << std::endl;
// 尝试访问不存在的键
std::cout << "Number of pears: " << myMap["pear"] << std::endl; // 会自动插入pear键并返回默认值0
return 0;
}
上述代码中,我们使用[]运算符向map中插入了几个键值对,并通过[]运算符访问和修改了某些值。当我们使用[]访问一个不存在的键时,会自动插入该键并返回默认值。在上面的例子中,当我们访问"pear"键时,会自动插入"pear"键,并返回默认值0。
multimap
C++中的multimap
是标准模板库(STL)中的一个关联容器,它可以存储多个键值对,并按键进行排序。与map
不同的是,multimap
允许存在重复的键。
multimap
的特点如下:
- 元素按照键的自然顺序进行排序
- 允许存在重复的键
- 插入元素的时间复杂度为O(log n)
- 查找元素的时间复杂度为O(log n)
下面是一个使用multimap
的示例:
#include <iostream>
#include <map>
int main() {
std::multimap<int, std::string> myMultimap;
// 插入元素
myMultimap.insert(std::make_pair(1, "apple"));
myMultimap.insert(std::make_pair(2, "banana"));
myMultimap.insert(std::make_pair(3, "orange"));
myMultimap.insert(std::make_pair(1, "grape")); // 插入重复的键
// 遍历multimap
for (auto it = myMultimap.begin(); it != myMultimap.end(); ++it) {
std::cout << "Key: " << it->first << " Value: " << it->second << std::endl;
}
// 查找键为1的元素
auto range = myMultimap.equal_range(1);
for (auto it = range.first; it != range.second; ++it) {
std::cout << "Found key 1: " << it->second << std::endl;
}
return 0;
}
上述代码创建了一个multimap
,并插入了几个键值对,包括一个重复的键。然后,我们使用迭代器遍历multimap
,打印出所有键值对。接着,我们使用equal_range
函数查找键为1的元素,并使用迭代器遍历结果集。注意,由于multimap
允许存在重复的键,所以查找结果是一个迭代器范围。
总结起来,multimap
是一个按键排序并允许重复键的关联容器,可以用于存储和检索多个键值对。
好啦,今天的内容就到这里啦,下期内容预告AVL树的模拟实现.
结语:后面可以说是C++中最难的一部分了,大家一起加油。