目录
set类介绍与简单使用
set类
multiset类
map类介绍与简单使用
map类
multimap类
set类介绍与简单使用
set类是一种关联式容器,在数据检索时比序列式容器效率更高。本质是一个常规的二叉搜索树,但是为了防止出现单支树导致效率下降进行了相关优化
set类也满足二叉搜索树的特点:
-
元素不重复:因此可以用来去重
-
默认中序遍历是升序
-
比较的平均次数为
-
set中的元素不可以修改
-
set中的底层使用二叉搜索树(红黑树)来实现
-
默认按照
key
升序排序
set类
使用set类需要包含头文件
<set>
set官方文档
简单使用实例:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> s;
// 使用数组构造set,去重+排序
int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
for (auto e : array)
{
// 插入数据
s.insert(e);
}
// 使用正向迭代器遍历set
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
// 使用反向迭代器遍历set
set<int>::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
// 计数
// set会去重,所以每一种数字只会出现1次
cout << s.count(3) << endl;
// 查找+删除
s.erase(s.find(3));
// 范围for遍历
for (auto e : s)
{
cout << e << " ";
}
return 0;
}
需要注意的两个函数lower_bound()
和upper_bound()
,这两个函数放在一起的作用是获取到当前中序遍历的[lower_bound, upper_bound]区间
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> s;
// 使用数组构造set,去重+排序
int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
for (auto e : array)
{
// 插入数据
s.insert(e);
}
// lower_bound与upper_bound
set<int>::iterator it = s.lower_bound(3);
while (it != s.upper_bound(8))
{
cout << *it << " ";
++it;
}
return 0;
}
输出结果:
3 4 5 6 7 8
首先解释lower_bound(3)
的意思,在这个函数中,lower_bound()
会取到第一个大于或者等于3的数值,返回其位置的迭代器,所以lower_bound(3)
返回的是3所在位置的迭代器,接着upper_bound(8)
,对于upper_bound(8)
会返回除8以外的比8大的数值位置的迭代器,也就是第一个大于8的数值位置的迭代器,所以上面的程序结果最后会输出8是因为取出了[3, 8]中的所有位于set容器中的值
multiset类
multiset类与set类不同的是,multiset类允许数据出现重复
multiset类官方文档
简单使用实例:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <set>
using namespace std;
int main()
{
// 使用数组构造multiset,排序
int array1[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
multiset<int> ms;
for (auto num : array1)
{
ms.insert(num);
}
// 范围for遍历
for (auto num : ms)
{
cout << num << " ";
}
cout << endl;
// 统计3的次数
cout << ms.count(3) << endl;
// lower_bound和upper_bound
// 在multiset中会打印[第一个4, 最后一个4]中的所有4
multiset<int>::iterator it = ms.lower_bound(4);
while (it != ms.upper_bound(4))
{
cout << *it << " ";
++it;
}
return 0;
}
需要注意erase()
函数在multiset中的两个用法有些许不同:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <set>
using namespace std;
int main()
{
int array2[] = { 1, 3, 5, 3, 3, 2, 4, 6, 8, 0, 1, 3, 3, 7, 9, 2, 4, 6, 3, 0 };
multiset<int> ms1;
for (auto num : array2)
{
ms1.insert(num);
}
for (auto num : ms1)
{
cout << num << " ";
}
cout << endl;
// 使用find查找后删除
ms1.erase(ms1.find(3));
for (auto num : ms1)
{
cout << num << " ";
}
cout << endl;
// 直接删除
ms1.erase(3);
for (auto num : ms1)
{
cout << num << " ";
}
return 0;
}
输出结果:
0 0 1 1 2 2 3 3 3 3 3 3 4 4 5 6 6 7 8 9
0 0 1 1 2 2 3 3 3 3 3 4 4 5 6 6 7 8 9
0 0 1 1 2 2 4 4 5 6 6 7 8 9
如果multiset
中指定数值有重复,multiset
类中find()
函数会找到左子树第一个值为指定值(不存在则返回其他与指定值相同的节点)的位置,返回该位置的迭代器,所以此时调用erase()
函数,将find()
返回的迭代器传给erase()
函数,删除的就是左子树的第一个值,而如果直接调用erase()
函数,传入指定值,则一次性全部删除
map类介绍与简单使用
与set类不同的是,map类是KV模型的平衡二叉树(红黑树),因为是Key_Value模型,所以map类总是以key进行排序,map也是用来存储数据的,与序列式容器(forward_list)不同的是,其里面存储的是<key, value>结构的键值对(pair
)
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量
key
和value
,key
代表键值,value
表示与key
对应的信息。下面是SGI版本的键值对定义: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) {} template <class U1, class U2> pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {} };
map类的特点:
-
因为底层还是类似于二叉搜索树,但是进行了优化,所以效率为
-
map类中的
key
值无法被修改,一旦插入了就没有再次修改的机会 -
map类支持下标访问
-
map类按照
key
升序排序
map类
map官方文档
简单使用实例:
map类没有直接添加key_value键值对的构造函数,所以需要使用其他方式进行内容添加
首先介绍map类中的insert()
函数,与set类的insert()
不同的是,map类需要使用pair对象作为参数传递给insert()
函数,下面是insert()
函数原型之一
pair<iterator,bool> insert (const value_type& val);
// 其中value_type为pair<const key_type, mapped_type>
// key_type为第一个模版参数Key
// mapped_type为第二个模版参数T
所以在插入数据时,首先需要一个pair对象,前面提供了pair结构的原型,其中有三种构造函数
-
无参构造:
first
和second
给类型初始值 -
有参构造:给定
first
和second
对应的值进行初始化 -
拷贝构造:使用已经存在的
pair
对象构造
有了pair
对象的构造,结合insert()
函数就可以为map类添加对象,下面提供五种方式:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <map>
using namespace std;
int main()
{
// map的五种构造方式
// 1. 创建pair对象再通过insert()函数插入到map中
pair<string, string> p1("字符串1", "字符串2");
map<string, string> m1;
m1.insert(p1);
// 2. 匿名对象插入
map<string, string> m2;
m2.insert(pair<string, string>("字符串1", "字符串2"));
// 3. 无explicit修饰下的隐式类型转换
map<string, string> m3;
m3.insert({ "字符串1", "字符串2" });
// 4. make_pair函数
map<string, string> m4;
m4.insert(make_pair("字符串1", "字符串2"));
// 5. initializer_list
map<string, string> m5 = { {"字符串1", "字符串2"}, {"字符串3", "字符串4"} };
return 0;
}
上面代码中的第三种方式与下面的过程等价
pair<string, string> p2 = { "字符串1", "字符串2" }; // 隐式类型转换
m3.insert(p2);
以二叉搜索树中:统计水果出现的次数为例
#include <iostream>
#include <map>
using namespace std;
int main()
{
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
map<string, int> m;
// 插入数据
for (auto str : arr)
{
m.insert({str, 0});
}
// 迭代器遍历
auto it = m.begin();
while (it != m.end())
{
cout << (*it).first << "->" << it->second << endl;
++it;
}
cout << endl;
// 查找+删除
auto pos = m.find("苹果");
m.erase(pos);
auto it1 = m.begin();
while (it1 != m.end())
{
cout << (*it1).first << "->" << it1->second << endl;
++it1;
}
return 0;
}
需要注意map中的operator[]
函数,如果想要实现在二叉搜索树中的计数,可以使用该函数
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <map>
using namespace std;
int main()
{
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
map<string, int> m;
for (auto str : arr)
{
m[str]++;
}
auto it = m.begin();
while (it != m.end())
{
cout << it->first << "->" << it->second << endl;
++it;
}
return 0;
}
在map类中,operator[]
函数的本质是通过[]中的key
查找key
对应的value
值,如果key
不存在就插入,将value
设置为对应类型的默认值,如果key
存在就返回value
这个函数的调用可以类比成下面的思路:
(*((this->insert(make_pair(k,mapped_type()))).first)).second
将其拆解为三部分:
-
(this->insert(make_pair(k,mapped_type())))
该部分本质是调用了一个
insert()
函数,在map类中insert()
函数返回pair<iterator, bool>
,如果插入成功证明插入的键值对一开始不存在,返回插入后位置的迭代器,并将bool
类型的变量设置为true
表示插入成功;如果键值对一开始存在,返回存在的键值对位置的迭代器,并将bool
类型的变量设置为false
表示插入失败 -
(*(pair<iterator, bool>.first))
该部分本质是调用插入节点的迭代器访问该迭代器的first值,因为这个键值对中的iterator存储的成功插入或者已经存在于map中的键值对位置的迭代器,所以该迭代器指向的是一个实际的节点,即一个实际的键值对节点,解引用该节点就可以取到其中的内容
-
(*iterator).second // iterator表示已经插入的节点或者原有节点位置的迭代器
该部分就是取出迭代器指向的节点中的
second
的值
所以,在map类中operator[]
可以用于下面的行为:
-
不存在[]中的key,插入该key
-
存在[]中的key,返回key对应的value
-
存在[]中的key,修改key对应的value
multimap类
与map类基本相同,但是multimap类允许数据出现重复,并且multimap类不支持operator[]
函数和at
函数
multimap官方文档
基本使用与map类和multiset类似,不再做演示