我们之前已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。
而今天我们学习的map、set、multimap、multiset
是关联式容器,关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。
根据应用场景的不同,STL总共实现了两种不同结构的关联式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:map、set、multimap、multiset。
这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面依次介绍每一个容器。
C++标准库中的set是一个容器类,它可以存储一组不重复的元素,并按照一定的排序规则进行排序。set容器是基于平衡二叉树(红黑树)实现的,所以插入、删除、查找等操作的时间复杂度都是O(
)。
与map/multimap
不同,map/multimap
中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对,但是set中插入元素时,只需要插入value即可,不需要构造键值对。
函数声明 | 功能介绍 |
---|---|
set (const Compare& comp = Compare(), const Allocator& = Allocator() ); | 构造空的set |
set (InputIterator first, InputIterator last, const Compare& comp = Compare(), const Allocator& = Allocator() ); | 用[first, last)区间中的元素构造set |
set ( const set<Key,Compare,Allocator>& x); | set的拷贝构造 |
使用set时记得包含set的头文件:
#include<set>
#include<set>
#include<vector>
int main()
{
//构造空的set
set<int> s1;
//用[first, last)区间中的元素构造
vector<int> v = { 0,1,2,3,4,5,6,7 };
set<int> s2(v.begin(), v.end());
//set的拷贝构造
set<int> s3(s2);
return 0;
}
函数声明 | 功能介绍 |
---|---|
iterator begin() | 返回set中起始位置元素的迭代器 |
iterator end() | 返回set中最后一个元素后面的迭代器 |
const_iterator cbegin() const | 返回set中起始位置元素的const迭代器 |
const_iterator cend() const | 返回set中最后一个元素后面的const迭代器 |
reverse_iterator rbegin() | 返回set第一个元素的反向迭代器,即end |
reverse_iterator rend() | 返回set最后一个元素下一个位置的反向迭代器,即rbegin |
const_reverse_iterator crbegin() const | 返回set第一个元素的反向const迭代器,即cend |
const_reverse_iterator crend() const | 返回set最后一个元素下一个位置的反向const迭代器,即crbegin |
#include<set>
#include<vector>
int main()
{
//用[first, last)区间中的元素构造
vector<int> v = { 0,1,2,3,4,5,6,7 };
set<int> s(v.begin(), v.end());
//正向迭代器遍历
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//反向迭代器遍历
set<int>::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
++rit;
}
return 0;
}
结果如下:
函数声明 | 功能介绍 |
---|---|
bool empty ( ) const | 检测set是否为空,空返回true,否则返回true |
size_type size() const | 返回set中有效元素的个数 |
函数声明 | 功能介绍 |
---|---|
pair<iterator,bool> insert ( const value_type& x ) | 在set中插入元素x,实际插入的是<x, x>构成的键值对,如果插入成功,返回<该元素在set中的位置,true>,如果插入失败,说明x在set中已经存在,返回<x在set中的位置,false> |
void erase ( iterator position ) | 删除set中position位置上的元素 |
size_type erase ( const key_type& x ) | 删除set中值为x的元素,返回删除的元素的个数 |
void erase ( iterator first, iterator last ) | 删除set中[first, last)区间中的元素 |
void swap ( set<Key,Compare,Allocator>& st ) | 交换set中的元素 |
void clear ( ) | 将set中的元素清空 |
iterator find ( const key_type& x ) const | 返回set中值为x的元素的位置 |
size_type count ( const key_type& x ) const | 返回set中值为x的元素的个数 |
iterator lower_bound (const value_type& val) const | 返回大于等于val的值的迭代器 |
iterator upper_bound (const value_type& val) const | 返回大于等于val的值的迭代器 |
#include <set>
void TestSet()
{
//创建一个set容器
set<int> s;
//插入数值
s.insert(1);
s.insert(3);
s.insert(5);
s.insert(7);
s.insert(7);
//查看s中元素个数
cout <<"size:"<< s.size() << endl;
// 正向打印set中的元素,从打印结果中可以看出:set可去重
cout << "s:";
for (auto& e : s)
cout << e << " ";
cout << endl;
// set中值为7的元素出现了几次
cout << "7出现的次数:" << s.count(7) << endl;
//使用set中存储的数值删除元素
s.erase(7);
//使用迭代器删除
s.erase(s.begin());
cout << "删除元素后s:";
for (auto& e : s)
cout << e << " ";
cout << endl;
// 用数组array中的元素构造set
int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4,6, 8, 0 };
set<int> s1(array, array + sizeof(array) / sizeof(array[0]));
//清空s中的元素
s.clear();
//交换s和s1
s.swap(s1);
cout << "交换后s:";
for (auto& e : s)
cout << e << " ";
cout << endl;
//找到元素3并删除
s.erase(s.find(3));
cout << "删除3后s:";
for (auto& e : s)
cout << e << " ";
cout << endl;
//找到1到5的位置,并删除该区间中的值
s.erase(s.find(1), s.find(5));//左闭右开
cout << "删除1~5后s:";
for (auto& e : s)
cout << e << " ";
cout << endl;
}
结果如下:
对于lower_bound() 与upper_bound()函数:
#include <iostream>
#include <set>
int main ()
{
std::set<int> myset;
std::set<int>::iterator itlow,itup;
for (int i=1; i<10; i++) myset.insert(i*10); // 10 20 30 40 50 60 70 80 90
itlow=myset.lower_bound (30); // ^
itup=myset.upper_bound (60); // ^
myset.erase(itlow,itup); // 10 20 70 80 90
std::cout << "myset contains:";
for (std::set<int>::iterator it=myset.begin(); it!=myset.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
上述代码删除的是[30,60]区间的值,所以lower_bound() 应该返回大于等于30的值,而upper_bound()应该返回大于60的值,而不是大于等于,才能保证删除上边界60
set中的元素默认按照小于来比较;
这就是set与我们后续学习的multiset的区别,multiset是可以存储重复的元素的;
所以如果你想要将一组数据去重并排序就可以考虑使用set容器;
也就是说set中的元素不可以重复(因此可以使用set进行去重),也不可以修改; 不可以修改是因为set底层是基于红黑树,修改set中的值可能会破坏其结构,因为set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序的。
这是因为其底层是二叉搜索树,所以迭代器遍历可以得到有序序列;
这也与其底层结构有关,我们后续学习其底层实现的时候再细说。
multiset是C++中的一个容器,它是一个有序的集合,允许重复的元素存在。multiset的实现使用红黑树,因此它的元素也是按照一定的顺序存储的。multiset的定义使用了模板,可以存储任意类型的元素。在使用multiset之前,需要包含头文件#include<set>
。
在multiset中,元素的value也会识别它(因为multiset中本身存储的就是<value, value>组成的键值对,因此value本身就是key,key就是value,类型为T),在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则进行排序。
multiset与set的区别是,multiset中的元素可以重复,set是中value是唯一的。
multiset使用和set非常类似,无非可以继续插入相同的值,所以我们着重看一下multiset使用和set的使用时的区别就好。
multiset中插入相等的值,插入平衡二叉树的左边右边都可以,因为旋转后,插入右边也可能旋转到左边,我们后续会学习。
✨对于find查找函数:
find查找x,有多个x值,返回中序遍历第一个x
这是因为返回中序遍历第一个会有很多好处,比如查找所有的x值,只需要找到第一个x的迭代器,然后一直++,直到所有的x找出:
#include <set>
int main()
{
multiset<int> m = { 1,5,3,5,4,5,2,6,1,7 };
auto pos = m.find(5);
while (pos != m.end() && *pos == 5)
{
cout << *pos << " ";
++pos;
}
cout << endl;
cout << m.count(5);
return 0;
}
上图中可以看出multiset中可以存储多个相同的值,并且是按照一定顺序存储的,这样才可以根据迭代器++来找到所有相同的值。
✨对于erase删除函数:
如果使用erase值删除,如果有多个相同的值,会将它们都删除
#include <set>
int main()
{
multiset<int> m = { 1,5,3,5,4,5,2,6,1,7 };
cout << "删除前:";
for (auto i : m)
{
cout << i << " ";
}
cout << endl;
//删除5
m.erase(5);
cout << "删除后:";
for (auto i : m)
{
cout << i << " ";
}
return 0;
}
结果如下:
。
在C++中,map是一种关联容器,它将键和值存储在一个有序的集合中。每个键唯一对应一个值,而且键和值是成对存储的。map容器是也基于平衡二叉树(红黑树)实现的,所以插入、删除、查找等操作的时间复杂度都是O(
),并且在插入键值对时会根据键的顺序进行排序。map的使用需要包含头文件#include<map>
,并使用std命名空间。
在map中,键值key通常用于排序和唯一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair: typedef pair<const key, T> value_type;
//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)
{}
};
函数声明 | 功能介绍 |
---|---|
map (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()); | 构造一个空的map |
map (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()) | 使用迭代器构造map |
map (const map& x) | 拷贝构造 |
#include<map>
#include<vector>
int main()
{
//构造空的map
map<string, string> m1;
//使用迭代器构造器
vector<pair<string, string>> v = {{"上", "up"}, { "下", "down"}, { "左", "left"}, { "右", "right"}};
map<string, string> m2(v.begin(),v.end());
//拷贝构造
map<string, string> m3(m2);
return 0;
}
函数声明 | 功能介绍 |
---|---|
begin()和end() | begin:首元素的位置,end最后一个元素的下一个位置 |
cbegin()和cend() | 与begin和end意义相同,但cbegin和cend所指向的元素不能修改 |
rbegin()和rend() | 反向迭代器,rbegin在end位置,rend在begin位置,其++和–操作与begin和end操作移动相反 |
crbegin()和crend() | 与rbegin和rend位置相同,操作相同,但crbegin和crend所指向的元素不能修改 |
#include<map>
#include<vector>
int main()
{
//使用迭代器构造器
vector<pair<string, string>> v = { {"上", "up"}, { "下", "down"}, { "左", "left"}, { "右", "right"} };
map<string, string> m(v.begin(), v.end());
//正向迭代器遍历
cout << "正向迭代器遍历: ";
map<string, string>:: iterator it = m.begin();
while (it != m.end())
{
cout << it->first << "->" << it->second << " ";
++it;
}
//反向迭代器遍历
cout << endl << endl;;
cout << "反向迭代器遍历: ";
map<string, string>::reverse_iterator rit = m.rbegin();
while (rit != m.rend())
{
cout << rit->first << "->" << rit->second << " ";
++rit;
}
return 0;
}
因为map中存储的是pair键值对,pair没有重载流插入与流提取,所以我们使用->或*来逐一访问pair中的两个元素。
结果如下:
函数声明 | 功能简介 |
---|---|
bool empty ( ) const | 检测map中的元素是否为空,是返回true,否则返回false |
size_type size() const | 返回map中有效元素的个数 |
mapped_type& operator[] (const key_type& k) | 返回key对应的value |
也就是说调用operator[]时先构造一个键值对,调用insert插入带map中,看key是否存在,然后将对应的value返回。
#include<map>
#include<vector>
int main()
{
//迭代器构造
vector<pair<string, string>> v = { {"上", "up"}, { "下", "down"}, { "左", "left"}, { "右", "right"} };
map<string, string> m(v.begin(), v.end());
cout << m["上"] << endl;
cout << m["下"] << endl;
cout << m["左"] << endl;
cout << m["右"] << endl;
return 0;
}
结果如下:
对于operator[]
,当key不在map中时:
在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过key找到与key对应的value然后返回其引用,不同的是:当key不存在时,operator[]用默认value与key构造键值对然后插入,返回该默认value,at()函数直接抛异常。
结果如下:
上图中,可以看出当m中没有相应的"中"时,会构造一个相应的键值对插入m中。
利用这一性质我们就可以在map中插入元素:
#include<map>
#include<vector>
int main()
{
//迭代器构造
vector<pair<string, string>> v = { {"上", "up"}, { "下", "down"}, { "左", "left"}, { "右", "right"} };
map<string, string> m(v.begin(), v.end());
//借用operator[]向map中插入元素
m["北"] = "north";
// 将<"北", "">插入map中,插入成功,返回value的引用,将“north”赋值给该引用结果
//遍历打印
map<string, string>:: iterator it = m.begin();
while (it != m.end())
{
cout << it->first << "->" << it->second << " ";
++it;
}
return 0;
}
结果如下:
函数声明 | 功能简介 |
---|---|
pair<iterator,bool> insert ( const value_type& x ) | 在map中插入键值对x,注意x是一个键值对,返回值也是键值对:iterator代表新插入元素的位置,bool代表释放插入成功 |
void erase ( iterator position ) | 删除position位置上的元素 |
size_type erase ( const key_type& x ) | 删除键值为x的元素 |
void erase ( iterator first, iterator last ) | 删除[first, last)区间中的元素 |
void swap (map<Key,T,Compare,Allocator>& mp ) | 交换两个map中的元素 |
void clear ( ) | 将map中的元素清空 |
iterator find ( const key_type& x ) | 在map中插入key为x的元素,找到返回该元素的位置的迭代器,否则返回end |
const_iterator find ( const key_type& x ) const | 在map中插入key为x的元素,找到返回该元素的位置的const迭代器,否则返回cend |
size_type count ( const key_type& x ) const | 返回key为x的键值在map中的个数,注意map中key是唯一的,因此该函数的返回值要么为0,要么为1,因此也可以用该函数来检测一个key是否在map中 |
map<string, string> m;
m.insert("dog", "狗");//这样是错误的
这是因为map中存储的是pair类型的键值对,所以可以通过以下几种方式插入:
map<string, string> m;
//m.insert("dog", "狗");//错误
// 向map中插入元素的方式:
//1.创建一个有名对象
pair<string, string> kv1("dragon", "龙");
m.insert(kv1);
//2.匿名对象插入
m.insert(pair<string, string>("lion", "狮子"));
//3.使用pair提供的函数模板make_pair来构造键值对
m.insert(make_pair("tiger", "老虎"));
//4.多参数的隐式类型转换
m.insert({"pig","猪"})
#include <string>
#include <map>
void TestMap()
{
map<string, string> m;
//m.insert("dog", "狗");//错误
// 向map中插入元素的方式:
//1.创建一个有名对象
pair<string, string> kv1("dragon", "龙");
m.insert(kv1);
//2.匿名对象插入
m.insert(pair<string, string>("lion", "狮子"));
//3.使用pair提供的函数模板make_pair来构造键值对
m.insert(make_pair("tiger", "老虎"));
//4.多参数的隐式类型转换
m.insert({ "pig","猪" });
// 用迭代器去遍历map中的元素,可以得到一个按照key排序的序列
for (auto& e : m)
cout << e.first << "->" << e.second << endl;
cout << endl;
// map中的键值对key一定是唯一的,如果key存在将插入失败
auto ret = m.insert(make_pair("lion", "大师"));
if (ret.second)
cout << "<lion, 大师>不在map中, 已经插入" << endl;
else
cout << "键值为lion的元素已经存在:" << ret.first->first << "->"
<< ret.first->second << " ,插入失败" << endl;
// 删除key为"apple"的元素
m.erase("tiger");
if (1 == m.count("tiger"))
cout << "tiger还在" << endl;
else
cout << "tiger不在" << endl;
}
multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对<key, value>,其中多个键值对之间的key是可以重复的。使用时要包含头文件#include<map>
。
它类似于map,但可以允许一个键对应多个值。multimap允许多个键值对具有相同的键值,即一个键可以对应多个值。
在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对key进行排序的。
注意:multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以重复的。
multimap中的接口可以参考map,功能都是类似的。
✨对于插入函数:
#include <string>
#include <map>
void TestMultiMap()
{
multimap<string, string> m;
m.insert({ "rose","玫瑰" });
m.insert({ "lily","百合" });
m.insert({ "peony","牡丹" });
m.insert({ "cherry","樱花" });
m.insert({ "lris","鸢尾花" });
//插入完全相同的键值对
m.insert({ "cherry","樱花" });
//插入只有键相同,值不同
m.insert({ "cherry","樱桃" });
//插入值相同,键不同
m.insert({ "yingtao","樱桃" });
// 用迭代器去遍历map中的元素,可以得到一个按照key排序的序列
for (auto& e : m)
cout << e.first << "->" << e.second << endl;
cout << endl;
}
结果如下:
✨ 对于count函数:
对于count(x),只要键是x的元素,无论键对应的值是否相同都会被计数。
#include <string>
#include <map>
void TestMultiMap()
{
multimap<string, string> m;
m.insert({ "rose","玫瑰" });
m.insert({ "lily","百合" });
m.insert({ "peony","牡丹" });
m.insert({ "cherry","樱花" });
m.insert({ "lris","鸢尾花" });
//插入完全相同的键值对
m.insert({ "cherry","樱花" });
//插入只有键相同,值不同
m.insert({ "cherry","樱桃" });
//插入值相同,键不同
m.insert({ "yingtao","樱桃" });
//查看键为cherry的元素个数
cout << "cherry:" << m.count("cherry") << endl;
}
结果如下:
✨对于erase删除元素:
删除键为x的元素,如果有多个键为x,那么会将键为x的全部删除,即使键对应的值不同。
#include <string>
#include <map>
void TestMultiMap()
{
multimap<string, string> m;
m.insert({ "rose","玫瑰" });
m.insert({ "lily","百合" });
m.insert({ "peony","牡丹" });
m.insert({ "cherry","樱花" });
m.insert({ "lris","鸢尾花" });
//插入完全相同的键值对
m.insert({ "cherry","樱花" });
//插入只有键相同,值不同
m.insert({ "cherry","樱桃" });
//插入值相同,键不同
m.insert({ "yingtao","樱桃" });
//查看键为cherry的元素个数
cout << "cherry:" << m.count("cherry") << endl;
// 删除key为"cherry"的元素
m.erase("cherry");
if (1 == m.count("cherry"))
cout << "cherry还在" << endl;
else
cout << "cherry不在" << endl;
// 用迭代器去遍历map中的元素,可以得到一个按照key排序的序列
for (auto& e : m)
cout << e.first << "->" << e.second << endl;
cout << endl;
}
结果如下:
可以看到上图中将键为cherry的全部删除了
map、set、multimap、multiset
的区别在于set存储不重复的元素,map存储不重复的键值对,而multiset中可以存储重复的元素,multimap可以存储重复的键值对。但是map、set、multimap、multiset
的底层都是平衡二叉树。
set适用于需要存储不重复元素的场景,可以用于去重操作。map适用于需要存储键值对的场景,可以根据键快速查找对应的值。以上就是今天所有的内容啦~ 完结撒花 ~🥳🎉🎉