跳到主要内容
C++ STL 关联容器实战:set、map 与 multiset 详解 | 极客日志
C++ 算法
C++ STL 关联容器实战:set、map 与 multiset 详解 C++ STL 关联容器 set 和 map 是处理唯一键值对及映射关系的核心工具。深入解析了 set、multiset、map、multimap 的底层原理、常用接口(insert、erase、find 等)及 pair 结构体用法。通过对比序列容器与关联容器的差异,结合实际代码示例与 LeetCode 经典题目(如数组交集、环形链表、高频单词),展示了如何在去重、排序、查找及统计场景中高效应用这些容器。重点讲解了迭代器操作、自定义比较器及内存管理细节,帮助开发者掌握 STL 在算法竞赛与工程实践中的最佳用法。
道系青年 发布于 2026/3/23 更新于 2026/4/25 1 浏览C++ STL 关联容器实战:set、map 与 multiset 详解
前言
在 C++ 标准模板库(STL)中,关联容器是处理键值映射和唯一性约束的核心工具。相比序列式容器,它们基于树或哈希表实现,提供了高效的查找、插入和删除能力。本文将深入解析 set、multiset、map、multimap 及其底层数据结构红黑树的应用场景,并通过实际代码示例与经典算法题,展示如何在工程实践中高效使用这些容器。
容器分类概览
序列容器与关联容器
序列容器(如 vector、list)强调元素的线性存储顺序,访问依赖位置索引;而关联容器(如 set、map)则基于关键字(Key)进行组织,通常通过树结构维护有序性或哈希表实现快速检索。
维度 序列式容器 关联式容器 逻辑结构 线性序列(数组、链表) 非线性结构(树、哈希表) 元素关联 仅通过位置顺序关联 通过键值的有序或哈希映射关联 访问依据 存储位置(索引或迭代器) 关键字(Key) 典型实现 vector, list, dequeset, map, unordered_map
set 容器详解
std::set 是一个按键排序且不允许重复元素的关联容器,底层通常由红黑树实现。
1. 构造方式
#include <iostream>
#include <set>
struct MyComparator {
bool operator () (const int & a, const int & b) {
a > b;
}
};
{
std::set< > s1;
arr[] = { , , };
;
std::set< , MyComparator> s3;
;
}
const
return
int main ()
int
int
5
2
8
std::set<int > s2 (arr, arr + 3 )
int
return
0
2. 容量操作
size(): 返回元素个数。
empty(): 判断容器是否为空。
std::set<int > myset;
myset.insert (10 );
if (!myset.empty ()) {
std::cout << "Size: " << myset.size () << std::endl;
}
3. 修改操作
clear(): 清空所有元素。
swap(): 交换两个容器的内容。
insert(): 插入元素,若已存在则不插入。
erase(): 支持按迭代器、按值或按范围删除。
std::set<int > s;
s.insert (1 ); s.insert (2 ); s.insert (3 );
s.erase (2 );
auto it = s.find (1 );
if (it != s.end ()) s.erase (it);
auto low = s.lower_bound (2 );
auto high = s.upper_bound (4 );
s.erase (low, high);
4. 查找与遍历
find(key): 返回指向元素的迭代器,未找到返回 end()。
count(key): 集合中元素唯一,返回 0 或 1。
lower_bound/upper_bound: 二分查找边界。
std::set<int > s = {10 , 20 , 30 };
auto it = s.find (20 );
if (it != s.end ()) {
std::cout << *it << std::endl;
}
auto range = s.equal_range (20 );
5. 实战示例 #include <iostream>
#include <set>
#include <string>
int main () {
std::set<int > nums = {5 , 2 , 9 , 2 , 5 };
for (const auto & n : nums) {
std::cout << n << " " ;
}
std::set<std::string> words = {"banana" , "apple" , "cherry" };
for (const auto & w : words) {
std::cout << w << " " ;
}
return 0 ;
}
multiset 容器 std::multiset 允许存储重复元素,同样保持有序。其接口与 set 高度相似,主要区别在于 count() 可返回大于 1 的值,erase(key) 会删除所有匹配项。
std::multiset<int > ms = {1 , 2 , 2 , 3 };
std::cout << ms.count (2 ) << std::endl;
ms.erase (2 );
pair 结构体 std::pair 用于将两个数据组合成一个单元,常用于 map 的键值对存储。
#include <utility>
std::pair<int , std::string> p (1 , "hello" ) ;
std::cout << p.first << ": " << p.second << std::endl;
auto p2 = std::make_pair (1 , "world" );
map 容器详解 std::map 存储键值对(key-value),键唯一且有序,值可修改。
1. 构造与访问 std::map<std::string, int > scores;
scores["Alice" ] = 90 ;
scores["Bob" ] = 85 ;
if (scores.count ("Alice" )) {
std::cout << scores["Alice" ] << std::endl;
}
2. 核心接口对比
operator[]: 便捷但可能隐式插入新键。若键不存在,会创建默认值。
find(): 推荐用于查找,避免意外插入。
insert(): 显式插入,返回 pair<iterator, bool> 指示是否成功。
std::map<int , int > m;
m.insert ({1 , 10 });
m.insert ({1 , 20 });
auto ret = m.find (1 );
if (ret != m.end ()) {
ret->second = 99 ;
}
3. 统计词频示例 #include <map>
#include <string>
#include <vector>
int main () {
std::vector<std::string> words = {"apple" , "banana" , "apple" };
std::map<std::string, int > freq;
for (const auto & w : words) {
freq[w]++;
}
for (const auto & [k, v] : freq) {
std::cout << k << ": " << v << std::endl;
}
return 0 ;
}
multimap 容器 std::multimap 允许键重复,适用于一对多映射关系。不支持 operator[],需使用 insert 和 equal_range 处理多个相同键。
OJ 实战演练
1. 两个数组的交集 (LeetCode 349) #include <vector>
#include <set>
using namespace std;
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 > result;
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 {
result.push_back (*it1);
++it1;
++it2;
}
}
return result;
}
};
2. 环形链表 II (LeetCode 142) class Solution {
public :
ListNode *detectCycle (ListNode *head) {
set<ListNode*> visited;
ListNode* cur = head;
while (cur) {
if (!visited.insert (cur).second) {
return cur;
}
cur = cur->next;
}
return nullptr ;
}
};
3. 前 K 个高频单词 (LeetCode 692) 结合 map 统计频率与 priority_queue 排序。
class Solution {
public :
struct Compare {
bool operator () (const pair<string, int >& x, const pair<string, int >& y) const {
if (x.second == y.second) return x.first > y.first;
return x.second < y.second;
}
};
vector<string> topKFrequent (vector<string>& words, int k) {
map<string, int > freq;
for (const auto & w : words) freq[w]++;
priority_queue<pair<string, int >, vector<pair<string, int >>, Compare> pq (freq.begin (), freq.end ());
vector<string> res;
for (int i = 0 ; i < k; ++i) {
res.push_back (pq.top ().first);
pq.pop ();
}
return res;
}
};
总结 STL 关联容器是 C++ 开发中的利器。set 和 map 适合需要唯一键和有序性的场景,multiset 和 multimap 则处理允许重复的情况。掌握它们的底层机制(如红黑树)、接口细节(如 insert 返回值含义)以及与其他容器的配合使用,能显著提升代码效率与可读性。在实际刷题或工程中,建议优先使用 find 而非 operator[] 以避免不必要的内存分配,并根据具体需求选择合适的数据结构。
相关免费在线工具 加密/解密文本 使用加密算法(如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