接下来我们要介绍四种树形结构的关联式容器:set、map、multiset、multimap
这四种容器的共同点是:使用**平衡搜索树(即红黑树)**作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。
因为这四个容器的底层容器比较复杂,会放在其他博客进行细讲,这里仅为它们的使用讲解。
set
set的介绍
std::set是 C++ 标准库中的一种关联式容器 。它最核心的特点是:存储的元素既是键也是值,且自动排序、唯一。
它的核心特性如下:
-
唯一性:容器中不允许有重复的元素。插入重复元素时,新元素会被忽略。
-
自动排序 :元素始终按照"严格弱排序"标准(默认是
std::less<T>,即升序)自动排列,因此遍历set时会得到有序序列。 -
元素不可直接修改 :因为元素的位置由其值决定,所以迭代器指向的是
const元素。若要修改,需先删除原元素,再插入新元素。 -
底层实现 :通常是红黑树 (一种平衡二叉搜索树),因此插入、删除、查找的时间复杂度都是 O(log n)。
它的常用模板参数如下:

-
Compare:可自定义比较函数,实现降序或按结构体某成员排序。 -
Alloc:一般使用默认值即可。
set的使用
1.set的构造
|--------------------------------------------------------------------------------------------------------------------|------------------------------|
| 函数声明 | 内容介绍 |
| 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的拷贝构造 |
void TestSet1()
{
//构造空的set
set<int> set1;
//迭代区间构造
vector<int> v = { 4,3,7,1,20,4,37,44,3,2,2,2,2,1 };
set<int> set2(v.begin(), v.end());
for (auto& e : set2)
{
cout << e << " ";
}
cout << endl;//输出结果为:1 2 3 4 7 20 37 44
//降重+排序
//拷贝构造
set<int> set3(set2);
for (auto& e : set3)
{
cout << e << " ";
}
cout << endl;//1 2 3 4 7 20 37 44
}
2.set的迭代器
|----------------------------------------|---------------------------------------|
| 函数声明 | 功能介绍 |
| 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 |
因为const迭代器与普通迭代的使用方法相似,所以这里就不进行演示const迭代器的使用方法:
void TestSet2()
{
set<int> s = { 1,2,3,4,5,6 };
//正序遍历 1 2 3 4 5 6
auto begin = s.begin();
auto end = s.end();
while (begin != end)
{
cout << *begin << " ";
begin++;
}
cout << endl;
//逆序遍历 6 5 4 3 2 1
auto rbegin = s.rbegin();
auto rend = s.rend();
while (rbegin != rend)
{
cout << *rbegin << " ";
rbegin++;
}
cout << endl;
}
3.set的容量
|------------------------|----------------------------|
| 函数声明 | 功能介绍 |
| bool empty ( ) const | 检测set是否为空,空返回true,否则返回true |
| size_type size() const | 返回set中有效元素的个数 |
void TestSet3()
{
set<int> set1 = { 1,2,3,4,5,6 };
set<int> set2;
cout << set1.empty() << endl;//0
cout << set2.empty() << endl;//1
cout << set1.size() << endl;//6
cout << set2.size() << endl;//0
}
4.set的修改操作
|-------------------------------------------------------|-----------------------------------------------------------------------------------------------------------|
| 函数声明 | 功能介绍 |
| pair<iterator,bool> insert ( const value_type& x ) | 在set中插入元素x,实际插入的是<x, x>构成的 键值对,如果插入成功,返回<该元素在set中的位置,true>,如果插入失败,说明x在set中已经 存在,返回<x在set中的位置,false> |
| oid erase ( iterator position ) | oid erase ( iterator position ) |
| size_type erase ( const key_type& x ) | 删除set中值为x的元素,返回删除的元素的个数 |
| void erase ( iterator first, iterator last ) | 删除set中[first, last)区间中的元素 |
| void clear ( ) | 将set中的元素清空 |
| iterator find ( const key_type& x ) const | 返回set中值为x的元素的位置 |
| size_type count ( const key_type& x ) const | 返回set中值为x的元素的个数 |
此处要注意的是:set的删除会出现迭代器失效的问题。
void TestSet4()
{
set<int> set1 = { 1,2,3,4,5 };
//输入set内未有的值则插入成功,否则插入失败
int x;
cin >> x;
if (set1.insert(x).second)
{
cout << "插入成功!" << endl;
}
else
{
cout << "插入失败!" << endl;
}
for (auto& e : set1)
{
cout << e << " ";
}
cout << endl;
//三种删除演示
//1.迭代器删除,配合find使用
int tmp = 3;
auto find = set1.find(3);
set1.erase(find);
for (auto& e : set1)
{
cout << e << " ";
}
cout << endl;
//但这样删除操作太冗余了,不如直接使用下面的值删除
//2.删除某个值
set1.erase(5);
for (auto& e : set1)
{
cout << e << " ";
}
cout << endl;
//3.迭代区间删除
set1.erase(--set1.end(), set1.end());
for (auto& e : set1)
{
cout << e << " ";
}
cout << endl;
//count在set中用来实现快速查找
//判断1在不在set中
if (set1.count(1))
{
cout << "1在set1中" << endl;
}
//clear,一键清空
set1.clear();
for (auto& e : set1)
{
cout << e << " ";
}
cout << endl;
}
multiset
multiset的介绍
std::multiset与std::set非常相似,核心区别在于它允许存储重复的元素。
它的核心特性如下:
| 特性 | 说明 |
|---|---|
| 允许重复键 | 这是与 set 最本质的区别。多个元素可以拥有相同的值。 |
| 自动排序 | 元素依然按照 Compare 准则(默认升序)自动排序。 |
| 元素不可直接修改 | 迭代器同样指向 const 元素,修改需先删后插。 |
| 底层实现 | 通常也是红黑树 ,插入、删除、查找时间复杂度 O(log n)。 |
总的来说,multiset 可以看作"允许重复的 set",在需要有序存储且不强制唯一性的场景下非常实用。
multiset的使用
此处只简单介绍演示set与multiset的不同,其他接口接口与set相同,可参考set。
插入 (
insert)总是成功,返回指向新插入元素的迭代器(
set返回pair<iterator,bool>以指示是否插入成功)。计数 (
count)返回值可能 大于 1,表示有多少个元素等于给定键。
删除 (
erase)
erase(key):会删除 所有 等于该键的元素,返回删除的个数。
erase(pos):删除指定迭代器位置的单个元素。查找 (
find)若存在多个相同键,
find返回指向其中 任意一个 的迭代器(通常是第一个)。区间操作
lower_bound、upper_bound、equal_range对于处理重复键尤其有用。
equal_range(key)返回一个pair,包含区间[first, last),该区间覆盖所有等于key的元素。
void TestMultiSet1()
{
multiset<int> ms = { 1, 3, 2, 1, 2 };// 允许重复
ms.insert(2);// 插入重复值
for (int x : ms)
{
cout << x << " ";//1 1 2 2 2 3
}
cout << endl;
cout << "2的个数为: " << ms.count(2) << endl; // 输出 3
// 删除所有值为 2 的元素
ms.erase(2);
cout << "删除之后,ms的大小为:" << ms.size() << endl; // 输出 2 (两个1和一个3)
map
map的介绍
std::map是 C++ 标准库中一个有序的键值对容器 。它的核心特点是:存储的元素是键值对(pair<const Key, T>),键唯一且自动排序。
它的核心特性如下:
| 特性 | 说明 |
|---|---|
| 键值分离 | 元素是 pair<const Key, T>,Key 是键(用于索引),T 是值(存储的数据)。 |
| 键唯一 | 不允许两个元素拥有相同的键。插入重复键时,新元素会被忽略。 |
| 自动排序 | 元素按照键的顺序排列,排序准则由 Compare(默认 std::less<Key>)定义,即升序。 |
| 键不可修改 | 因为键用于确定元素位置,所以键是 const 的。若要修改,需删除原元素再插入新键值对。 |
| 值可修改 | 键对应的值(mapped_type)可以直接修改。 |
| 底层实现 | 通常是红黑树 (平衡二叉搜索树),插入、删除、查找的时间复杂度均为 O(log n)。 |
它的常用模板参数如下:
-
Key:键的类型。 -
T:值的类型。 -
Compare:排序准则,默认升序。可自定义实现降序或按特定规则排序。
map的使用
1.map的构造
|-------|-----------|
| 函数声明 | 功能介绍 |
| map() | 构造一个空的map |
void TestMap1()
{
map<int, int> m;
}
2.map的迭代器
|-------------------|-----------------------------------------------------------|
| 函数声明 | 功能介绍 |
| begin()和end() | begin:首元素的位置,end最后一个元素的下一个位置 |
| cbegin()和cend() | 与begin和end意义相同,但cbegin和cend所指向的元素不 能修改 |
| rbegin()和rend() | 反向迭代器,rbegin在end位置,rend在begin位置,其 ++和--操作与begin和end操作移动相反 |
| crbegin()和crend() | 与rbegin和rend位置相同,操作相同,但crbegin和crend所 指向的元素不能修改 |
同样的,因为const迭代器与普通迭代的使用方法相似,所以这里就不进行演示const迭代器的使用方法:
void TestMap2()
{
map<int, int> m = { {7,7},{5,5},{3,3},{4,4},{1,1},{5,5} };
auto begin = m.begin();
auto end = m.end();
while (begin != end)
{
cout << begin->first << ":" << begin->second << endl;
begin++;
}
/*1:1
3 : 3
4 : 4
5 : 5
7 : 7*/
auto rbegin = m.rbegin();
auto rend = m.rend();
while (rbegin != rend)
{
cout << rbegin->first << ":" << rbegin->second << endl;
rbegin++;
}
/*7:7
5 : 5
4 : 4
3 : 3
1 : 1*/
}
3.map的容量与元素访问
|-------------------------------------------------|----------------------------------|
| 函数声明 | 功能介绍 |
| bool empty ( ) const | 检测map中的元素是否为空,是返回 true,否则返回false |
| size_type size() const | 返回map中有效元素的个数 |
| mapped_type& operator[] (const key_type& k) | 返回去key对应的value |
要注意:operator[] 会插入默认值, 如果键不存在,map[key] 会自动插入一个值初始化后的元素。所以operator[ ]在map中有查找、修改和插入的功能:
void TestMap3()
{
map<string, string> m1;
map<int, int> m = { {7,7},{5,5},{3,3},{4,4},{1,1},{5,5} };
cout << m1.empty() << endl; //1;
cout << m.empty() << endl; //0;
cout << m1.size() << endl;//0
cout << m.size() << endl;//5
//[]的插入功能
m1["string"];
cout << m1.size() << endl;//此时m1的size变为1,证明已经插入
//[]的插入加修改功能
m1["insert"] = "插入";
cout << m1.size() << endl;
auto begin = m1.begin();
auto end = m1.end();
while (begin != end)
{
cout << begin->first << ":" << begin->second << endl;
begin++;
}
//insert:插入
//string :
//[]的修改功能
m1["insert"] = "我是修改值";
begin = m1.begin();
end = m1.end();
while (begin != end)
{
cout << begin->first << ":" << begin->second << endl;
begin++;
}
//insert:我是修改值
//string :
}
4.map的修改操作
|--------------------------------------------------|--------------------------------------------------------------------------------|
| 函数声明 | 功能介绍 |
| pair 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& 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中 |
void TestMap4()
{
map<string, string> m1;
map<string, string> m2;
m1.insert({ "left", "左边" });
m1.insert({ "right", "右边" });
m1.insert({ "string", "字符串" });
m1.insert({ "insert","插入" });
m1.insert({ "erase","删除" });
m1.insert({ "find", "查找" });
auto begin = m1.begin();
auto end = m1.end();
while (begin != end)
{
cout << begin->first << ":" << begin->second << endl;
begin++;
}
cout << endl;
//用find找到位置后删除
auto tmp = m1.find("left");
m1.erase(tmp);
begin = m1.begin();
end = m1.end();
while (begin != end)
{
cout << begin->first << ":" << begin->second << endl;
begin++;
}
cout << endl;
//直接删除某值
m1.erase("string");
begin = m1.begin();
end = m1.end();
while (begin != end)
{
cout << begin->first << ":" << begin->second << endl;
begin++;
}
cout << endl;
//删除find和right这个区间的所有值
auto begin1 = m1.find("find");
auto end1 = m1.find("right");
m1.erase(begin1, end1);
begin = m1.begin();
end = m1.end();
while (begin != end)
{
cout << begin->first << ":" << begin->second << endl;
begin++;
}
cout << endl;
//交换两个map
m1.swap(m2);
begin = m1.begin();
end = m1.end();
while (begin != end)
{
cout << begin->first << ":" << begin->second << endl;
begin++;
}
cout << endl;
//count用来查找
if (m2.count("right"))
{
cout << "找到了" << endl;
}
//clear清除m2
m2.clear();
begin = m1.begin();
end = m1.end();
while (begin != end)
{
cout << begin->first << ":" << begin->second << endl;
begin++;
}
cout << endl;
}
multimap
multimap的介绍
std::multimap是 C++ 标准库中的有序键值对容器 ,它与std::map非常相似,核心区别在于允许存储重复的键。
它的核心特性如下:
| 特性 | 说明 |
|---|---|
| 键值分离 | 元素是 pair<const Key, T>,Key 是键(用于索引),T 是值(存储的数据)。 |
| 键可重复 | 这是与 map 最本质的区别。允许多个元素拥有相同的键。 |
| 自动排序 | 元素按照键的顺序排列,排序准则由 Compare(默认 std::less<Key>)定义,即升序。 |
| 键不可修改 | 因为键用于确定元素位置,所以键是 const 的。若要修改,需删除原元素再插入新键值对。 |
| 值可修改 | 键对应的值(mapped_type)可以直接修改(通过迭代器)。 |
| 底层实现 | 通常是红黑树 (平衡二叉搜索树),插入、删除、查找的时间复杂度均为 O(log n)。 |
std::multimap 可以看作"允许重复键的 map",在需要存储一对多关系(如一个学生多门课程成绩、一个作者多本书籍)且希望按键有序排列时非常实用。
multimap的使用
同样的,此处只简单介绍演示map与multimap的不同,其他接口接口与map相同,可参考map。
| 操作 | map |
multimap |
|---|---|---|
插入 (insert) |
返回 pair<iterator, bool>,bool 指示是否成功(键唯一) |
返回 iterator(指向新插入元素),总是成功 |
下标访问 (operator[]) |
支持,键不存在时插入默认值 | 不支持(因为无法确定返回哪个值) |
元素访问 (at) |
支持 | 不支持 |
计数 (count) |
返回 0 或 1 | 返回 可能大于 1(等于该键的元素个数) |
删除 (erase(key)) |
删除该键的元素(最多一个) | 删除 所有 匹配该键的元素,返回删除个数 |
查找 (find) |
返回指向该键的迭代器 | 若存在多个相同键,返回指向 其中任意一个 的迭代器(通常是第一个) |
void TestMap5()
{
multimap<std::string, int> scores;
// 插入元素(可以重复键)
scores.insert({ "Alice", 90 });
scores.insert({ "Bob", 85 });
scores.insert({ "Alice", 95 }); // 允许相同键
scores.insert({ "Bob", 88 });
for (const auto& e : scores) {
std::cout << e.first << ": " << e.second << std::endl;
}
// Alice: 90
// Alice: 95
// Bob: 85
// Bob: 88
// 统计键的个数
std::cout << "Alice 出现次数: " << scores.count("Alice") << std::endl; // 输出 2
// 删除所有匹配键的元素
scores.erase("Alice");
std::cout << "删除 Alice 后 size: " << scores.size() << std::endl; // 输出 2
}
oj当中的使用

题目链接:692. 前K个高频单词 - 力扣(LeetCode)
这题思路很简单,就是利用map的性质来统计单词列表words中每个单词出现的个数后,再利用vector进行排序。这里要注意自己写一个比较的仿函数,因为pair的比较规则为:先比较第一个元素(first),如果 first 不相等,则结果由 first 决定;只有当 first 相等时,才比较第二个元素(second)。而这并不是我们想要的,所以需要我们自己编写。
然后注意,对vector进行排序时要使用stable_sort,因为题目要求我们如果不同的单词有相同出现频率, 按字典顺序 排序。而map自身的性质已经做到了这一点。所以我们只需要保证后续的排序不破坏它的顺序即可。即使用稳定的排序:stable_sort可以做到这一点。
class Solution {
public:
struct Compare
{
bool operator()(const pair<string, int>& a, const pair<string, int>& b)
{
return a.second > b.second;
}
};
vector<string> topKFrequent(vector<string>& words, int k) {
map<string, int> countMap;
for(auto& e : words)
{
countMap[e]++;
}
vector<pair<string, int>> v(countMap.begin(), countMap.end());
stable_sort(v.begin(), v.end(), Compare());
vector<string> ans;
for(int i = 0; i < k; i++)
{
ans.push_back(v[i].first);
}
return ans;
}
};

题目链接:349. 两个数组的交集 - 力扣(LeetCode)
这题比较简单,利用set的去重+排序的性质就非常好做。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
set<int> set1(nums1.begin(), nums1.end());
set<int> set2(nums2.begin(), nums2.end());
auto begin1 = set1.begin();
auto begin2 = set2.begin();
vector<int> ans;
while(begin1 != set1.end() && begin2 != set2.end())
{
if(*begin1 < *begin2)
{
++begin1;
}
else if(*begin1 > *begin2)
{
++begin2;
}
else
{
ans.push_back(*begin1);
begin1++;
begin2++;
}
}
return ans;
}
};