跳到主要内容
C++ map 与 set 容器使用详解 | 极客日志
C++ 算法
C++ map 与 set 容器使用详解 综述由AI生成 C++ STL 中的 set 和 map 是常用的关联式容器。set 存储唯一键值并自动排序,底层为红黑树;map 存储键值对,同样基于红黑树实现高效查找。两者的构造函数、迭代器使用、增删查改操作及 multiset/multimap 的区别。通过 pair 类型讲解键值对存储,并结合力扣题目演示了实际应用场景,如数组交集、环形链表检测及随机链表复制等。
时间旅人 发布于 2026/2/10 更新于 2026/6/8 6.2K 浏览
1. 序列式容器和关联式容器
1.1 序列式容器
核心特征
按插入顺序存储:元素的物理存储顺序与插入顺序一致。
允许重复元素:可以存储多个相同的值。
通过位置访问:通过索引(如数组)或迭代器直接访问元素。
常见容器
vector: 动态数组,支持快速随机访问,尾部插入高效。
list: 双向链表,支持快速插入/删除,但随机访问效率低。
deque: 双端队列,支持头尾高效插入/删除。
array: 固定大小数组,编译时确定大小。
forward_list: 单向链表,内存占用更小。
适应场景
需要保持插入顺序(如日志记录,操作历史)。
频繁通过索引随机访问(如 vector 的 [] 操作)。
需要高效头尾操作(如 deque)。
1.2 关联式容器
核心特征
按键存储:元素基于键值对(key-value)键本身存储。
自动排序:元素按键的排序规则(默认升序)组织。
唯一性:set和map要求键唯一;multiset和multimap允许重复键。
常见容器
set: 存储唯一键的有序集合。
map: 存储键值对的有序映射。
multiset: 允许重复键的有序集合。
multimap: 允许重复键的有序映射。
适用场景
需要快速查找(如字典,数据库索引)。
需自动排序(如排行榜,有序事件调度)。
需要唯一性约束(如用户 ID 管理)。
2. set 系列的使用
2.1 set 和 multiset 的参考文档
点击快速到达
2.2 set 类的介绍
set的声明如下,T 就是set底层关键字的类型。
set默认要求 T 支持小于比较,如果不支持,可以手动实现一个仿函数传给第二个模板参数。
set底层存储数据的内存是从空间配置器上申请的,如果需要可以自己手动实现一个内存池,传给第三个参数。
一般情况下我们都不需要传后面的两个模板参数。
set的底层是用红黑树实现的,增删查的效率是 O(log n),迭代器遍历走的是搜索树的中序遍历,所以遍历后的元素是有序的。
template <class T ,
class Compare = less<T>,
class Alloc = allocator<T>
>
class set;
2.3 set 的构造函数和迭代器
explicit set (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()) ;
template <class InputIterator >
set (InputIterator first, InputIterator last, const key_compare& comp = key_compare (), const allocator_type& alloc = allocator_type ());
set (const set& x);
set (initializer_list<value_type> il, const key_compare& comp = key_compare (), const allocator_type& alloc = allocator_type ());
iterator begin () ;
iterator end () ;
reverse_iterator rbegin () ;
reverse_iterator rend () ;
2.4 set 的增删查 #include <iostream>
#include <set>
using namespace std;
int main () {
set<int > s;
s.insert (2 );
s.insert (1 );
s.insert (5 );
s.insert (6 );
auto it = s.begin ();
while (it != s.end ()) {
cout << *it << " " ;
it++;
}
cout << endl;
s.insert ({1 , 5 , 3 , 2 , 7 , 9 });
for (auto e : s) {
cout << e << " " ;
}
cout << endl;
set<string> strset = {"sort" , "add" , "insert" };
for (auto &e : strset) {
cout << e << " " ;
}
cout << endl;
return 0 ;
}
2.5 find 和 erase 的使用样例 int main () {
set<int > s = {2 , 7 , 4 , 3 , 1 , 9 , 5 , 0 };
for (auto &e : s) {
cout << e << " " ;
}
cout << endl;
s.erase (s.begin ());
for (auto &e : s) {
cout << e << " " ;
}
cout << endl;
int x;
cin >> x;
int num = s.erase (x);
if (num == 0 ) {
cout << x << "不存在!" << endl;
}
for (auto &e : s) {
cout << e << " " ;
}
cout << endl;
cin >> x;
auto pos = s.find (x);
if (pos != s.end ()) {
s.erase (pos);
} else {
cout << x << "不存在" << endl;
}
for (auto &e : s) {
cout << e << " " ;
}
cout << endl;
auto pos2 = s.find (x);
cin >> x;
if (s.count (x)) {
cout << x << "在!" << endl;
} else {
cout << x << "不存在!" << endl;
}
return 0 ;
}
int main () {
set<int > myset;
for (int i = 1 ; i < 10 ; i++) {
myset.insert (i * 10 );
}
for (auto e : myset) {
cout << e << " " ;
}
cout << endl;
auto itlow = myset.lower_bound (30 );
auto itup = myset.upper_bound (60 );
myset.erase (itlow, itup);
for (auto e : myset) {
cout << e << " " ;
}
cout << endl;
return 0 ;
}
2.6 multiset 和 set 的差异 multiset和set基本完全相似,主要的区别点在于multiset支持键值冗余,那么insert/find/count/erase都围绕着支持键值冗余有所差异。
int main () {
multiset<int > s = {4 , 6 , 4 , 3 , 6 , 7 , 8 , 9 , 2 , 5 , 3 , 7 , 8 , 9 };
auto it = s.begin ();
while (it != s.end ()) {
cout << *it << " " ;
++it;
}
cout << endl;
int x;
cin >> x;
auto pos = s.find (x);
while (pos != s.end () && *pos == x) {
cout << *pos << " " ;
++pos;
}
cout << endl;
cout << s.count (x) << endl;
s.erase (x);
for (auto e : s) {
cout << e << " " ;
}
cout << endl;
return 0 ;
}
2.7 两个数组的交集 - 力扣(LeetCode) 解题思路: 两个数组依次比较,小的++,相等的就是交集,同时++,其中一个结束就结束了
class Solution {
public :
vector<int > intersection (vector<int >& nums1, vector<int >& nums2) {
set<int > s1 (nums1. begin(), nums1. end()) ;
set<int > s2 (nums2. begin(), nums2. end()) ;
vector<int > ret;
auto it1 = s1. begin ();
auto it2 = s2. begin ();
while (it1 != s1. end () && it2 != s2. end ()) {
if (*it1 < *it2) it1++;
else if (*it1 > *it2) it2++;
else {
ret.push_back (*it1);
it1++; it2++;
}
}
return ret;
}
};
2.8 环形链表 II - 力扣(LeetCode) 解题思路: 遍历这个环形链表,如果count为 0,就把此节点插入进set,如果不为 0,则此节点为入口点。
class Solution {
public :
ListNode *detectCycle (ListNode *head) {
set<ListNode*> s;
ListNode* cur = head;
while (cur) {
if (s.count (cur)) return cur;
else s.insert (cur);
cur = cur->next;
}
return nullptr ;
}
};
3. map 系列的使用
3.1 map 和 multimap 的参考文档
3.2 map 类的介绍 map的声明如下,Key 就是 map 底层关键字的类型,T 是 map 底层 Value 的类型,map 默认要求 Key 支持小于比较,如果不支持或者需要的话可以自行实现仿函数传给第二个模板参数。map底层存储数据的内存是从空间配置器申请的。一般情况下我们都不需要传后面两个模板参数。map的底层使用红黑树实现的,增删查改的效率是 O(log n),迭代器遍历走的是中序遍历,所以 Key 的值是有序的。
template <class Key ,
class T ,
class Compare = less<Key>,
class Alloc = allocator<pair<const Key, T>>
>
class map;
3.3 pair 类型介绍 map底层红黑树节点中的数据,使用 pair<Key,T>存储键值对数据。
pair是一个类模板,里面有两个成员变量,一个是first,一个是second。
typedef pair<const Key, T> value_type;
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 U , class V >
pair (const pair<U, V>& pr): first (pr.first), second (pr.second) {}
};
template <class T1 , class T2 >
inline pair<T1, T2> make_pair (T1 x, T2 y) {
return pair <T1, T2>(x, y);
}
3.4 map 的构造 map的构造我们只需关注以下接口即可
map支持正向迭代器遍历和反向迭代器遍历,遍历默认按 Key 的升序,因为底层是二叉搜索树,走的是中序遍历。支持迭代器也就支持范围 for。map支持value数据的修改,但不支持Key的修改。修改关键字的数据破坏了底层搜索树的结构。
explicit map (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()) ;
template <class InputIterator >
map (InputIterator first, InputIterator last, const key_compare& comp = key_compare (), const allocator_type& alloc = allocator_type ());
map (const map& x);
map (initializer_list<value_type> il, const key_compare& comp = key_compare (), const allocator_type& alloc = allocator_type ());
iterator begin () ;
iterator end () ;
reverse_iterator rbegin () ;
reverse_iterator rend () ;
3.5 map 的增删查 map的增删查关注以下接口即可
map的增接口,插入的是 pair 的键值对数据,跟set有所不同,但是查和删的接口只用关键字 Key,跟set完全相似。不过 find 返回的是iterator,不仅仅可以找到 Key 在不在,还能找到映射的 Value,同时通过迭代还能修改 Value。
Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type, mapped_type>
pair<iterator, bool > insert (const value_type& val) ;
void insert (initializer_list<value_type> il) ;
template <class InputIterator >
void insert (InputIterator first, InputIterator last) ;
iterator find (const key_type& k) ;
size_type count (const key_type& k) const ;
iterator erase (const_iterator position) ;
size_type erase (const key_type& k) ;
iterator erase (const_iterator first, const_iterator last) ;
iterator lower_bound (const key_type& k) ;
const_iterator lower_bound (const key_type& k) const ;
3.6 map 的数据修改 map 第一个支持修改的方式是通过迭代器,迭代器遍历时或者 find 返回 Key 所在的 iterator 修改。map 还有一个非常重要的修改接口operator[],但是operator[]不仅仅支持数据的修改,还支持插入数据和查找数据,所以它是一个多功能复合接口。
Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type, mapped_type>
iterator find (const key_type& k) ;
pair<iterator, bool > insert (const value_type & val) ;
mapped_type& operator [](const key_type& k);
mapped_type& operator [](const key_type& k) {
pair<iterator, bool > ret = insert ({k, mapped_type ()});
iterator it = ret.first;
return it->second;
}
3.7 构造遍历以及增删查改样例 #include <map>
int main () {
pair<string, string> kv1 = {"left" , "左边" };
map<string, string> dict = {{"left" , "左边" }, {"right" , "右边" }, {"insert" , "插入" }, {"erase" , "删除" }};
map<string, string>::iterator it = dict.begin ();
while (it != dict.end ()) {
cout << it->first << ":" << it->second << endl;
++it;
}
pair<string, string> kv2 ("first" , "第一个" ) ;
dict.insert (kv2);
dict.insert (pair <string, string>("second" , "第二个" ));
dict.insert (make_pair ("sort" , "排序" ));
dict.insert ({"auto" , "自动的" });
for (auto &e : dict) {
cout << e.first << ":" << e.second << endl;
}
cout << endl;
string str;
while (cin >> str) {
auto ret = dict.find (str);
if (ret != dict.end ()) {
cout << "->" << ret->second << endl;
} else {
cout << "无此单词,请重新输入" << endl;
}
}
return 0 ;
}
3.8 map 的迭代器功能和 [] 功能样例 #include <iostream>
#include <string>
#include <map>
using namespace std;
int main () {
string arr[] = {"苹果" , "西瓜" , "苹果" , "西瓜" , "苹果" , "苹果" , "西瓜" , "苹果" , "香蕉" , "苹果" , "香蕉" };
map<string, int > countMap;
for (const auto & str : arr) {
auto ret = countMap.find (str);
if (ret == countMap.end ()) {
countMap.insert ({str, 1 });
} else {
ret->second++;
}
}
for (const auto & e : countMap) {
cout << e.first << ":" << e.second << endl;
}
cout << endl;
return 0 ;
}
3.9 multimap 和 map 的差异 multimap 和 map 的使用基本完全类似,主要区别是 multimap 支持关键字 Key 冗余,那么insert/find/count/erase都围绕着支持键值冗余有所差异。这里和set和multiset基本一样,比如 find 时有多个 key 返回中序中的第一个。其次是 multimap 不支持 [],因为支持 key 冗余,[]就只能支持插入了,不支持修改。
3.10 随机链表的复制 - 力扣(LeetCode) 解题思路: 让原链表与拷贝链表通过map建立映射关系
class Solution {
public :
Node* copyRandomList (Node* head) {
map<Node*, Node*> nodeMap;
Node* copyhead = nullptr , *copytail = nullptr ;
Node* cur = head;
while (cur) {
if (copytail == nullptr ) {
copyhead = copytail = new Node (cur->val);
}
else {
copytail->next = new Node (cur->val);
copytail = copytail->next;
}
nodeMap[cur] = copytail;
cur = cur->next;
}
cur = head;
Node* copy = copyhead;
while (cur) {
if (cur->random == nullptr ) {
copy->random = nullptr ;
} else {
copy->random = nodeMap[cur->random];
}
cur = cur->next;
copy = copy->next;
}
return copyhead;
}
};
3.11 前 K 个高频单词 - 力扣(LeetCode) 解题思路 1: 用排序找前 k 个单词,因为 map 中已经对 Key 单词排序过,也就意味着遍历 map 时次序相同的单词,字典序小的在前面,字典序大的在后面,那么我们将数据放到 vector 中用一个稳定的排序就可以实现上面特殊的要求,但 sort 底层是快排,是不稳定的,所以我们要用stable_sort,是稳定的。
class Solution {
public :
struct Compare {
bool operator () (const pair<string, int >& kv1, const pair<string, int >& kv2) {
return kv1. second > kv2. second;
}
};
vector<string> topKFrequent (vector<string>& words, int k) {
map<string, int > countMap;
for (auto & str : words) {
countMap[str]++;
}
vector<pair<string, int >> v (countMap.begin (), countMap.end ());
stable_sort (v.begin (), v.end (), Compare ());
vector<string> retv;
for (size_t i = 0 ; i < k ;++i) {
retv.push_back (v[i].first);
}
return retv;
}
};
解题思路 2: 将 map 统计出来的次序,放到 vector 中排序,或者放到 priority_queue 中选出前 k 个,利用仿函数强制控制次数相等的,字典序小的在前面。
class Solution {
public :
struct Compare {
bool operator () (const pair<string, int >& x, const pair<string, int >& y) {
return x.second > y.second || (x.second == y.second && x.first < y.first);
}
};
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 ());
sort (v.begin (), v.end (), Compare ());
vector<string> strV;
for (int i = 0 ; i < k;++i) {
strV.push_back (v[i].first);
}
return strV;
}
};
class Solution {
public :
struct Compare {
bool operator () (const pair<string, int >& x, const pair<string, int >& y) const {
return x.second < y.second || (x.second == y.second && x.first > y.first);
}
};
vector<string> topKFrequent (vector<string>& words, int k) {
map<string, int > countMap;
for (auto & e : words) {
countMap[e]++;
}
priority_queue<pair<string, int >, vector<pair<string, int >>, Compare> p (countMap.begin (), countMap.end ());
vector<string> strV;
for (int i = 0 ; i < k;++i) {
strV.push_back (p.top ().first);
p.pop ();
}
return strV;
}
};
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online