跳到主要内容C++ STL 进阶:set 与 map 容器详解 | 极客日志C++算法
C++ STL 进阶:set 与 map 容器详解
详细介绍 C++ STL 中 set、multiset、map、multimap 容器的构造、常用操作及底层原理。涵盖元素插入删除、查找迭代、比较操作等接口,并通过 pair 类说明键值对存储机制。结合 LeetCode 经典例题(如数组交集、环形链表、随机链表复制、前 K 个高频单词),演示容器在实际算法场景中的应用,帮助读者掌握关联式容器的核心用法。
星河入梦32 浏览 前言
本文介绍 C++ STL 中 set、multiset、map、multimap 等关联式容器的使用。
容器分类
序列容器和关联容器
STL 容器主要分为序列式容器和关联式容器。
序列式容器核心特征:
- 线性逻辑结构:元素按存储位置顺序排列,相邻元素在物理存储上可能连续(如 vector)或离散(如 list),但元素间仅通过线性顺序关联。
- 元素独立性:任意交换两个元素的位置,容器仍保持合法结构,仅改变元素的排列顺序。
- 位置驱动访问:通过位置索引(如
vector[i])或迭代器顺序访问,不依赖元素值的大小或关系。
关联式容器核心特征:
- 非线性逻辑结构:通常基于树(如红黑树)或哈希表实现,元素间通过键值的有序性或哈希映射建立关联。
- 结构敏感性:交换元素会破坏容器的内部逻辑结构(如树的有序性或哈希表的映射关系),导致后续操作(如查找、插入)失效。
- 关键字驱动访问:元素按关键字(Key)存储和检索,而非位置。例如
map 容器通过键值对 (key, value) 存储数据,查找时直接通过 key 定位。
| 维度 | 序列式容器 | 关联式容器 |
|---|
| 逻辑结构 | 线性序列(数组、链表等) | 非线性结构(树、哈希表等) |
| 元素关联 | 仅通过位置的顺序关联 | 通过键值的有序或哈希的映射关联 |
| 访问依据 | 存储位置(索引或迭代器) | 关键字(Key) |
| 典型实现 | vector、list、deque | set、map、unordered_set、unordered_map |
set 容器
介绍
std::set 是关联式容器,底层通常由红黑树实现,元素自动排序且唯一。
template<class T,
class Compare = less<T>,
class Alloc = allocator<T>
class set;
- 元素类型(T):set 底层存储的关键字类型,需保证该类型支持比较操作。
- 比较器(Compare,默认 less):用于定义元素间的排序规则。
- 内存分配器(Allocator,默认 allocator):负责管理 set 的内存分配与释放。
如需优化内存使用,可自定义分配器;若 T 不支持默认比较,可通过仿函数重载比较规则。
常见构造
#include<iostream>
#include<set>
bool fncomp(int lhs, int rhs) {
return lhs < rhs;
}
struct classcomp {
bool operator()(const int& lhs, const int& rhs) const {
return lhs < rhs;
}
};
int main() {
std::set<int> s1;
int myints[] = {10, 20, 30, 40, 50};
std::set<int> s2(myints, myints + 5);
std::set<int> s3(s2);
std::set<int> s4(s2.begin(), s2.end());
std::set<int, classcomp> s5;
bool (*fn_pt)(int, int) = fncomp;
std::set<int, bool(*)(int, int)> s6(fn_pt);
return 0;
}
容量操作
std::set::size
#include<iostream>
#include<set>
int main() {
std::set<int> myints;
std::cout << "初始状态下的 set 大小的 size 为:" << myints.size() << '\n';
for (int i = 0; i < 10; ++i) myints.insert(i);
std::cout << "插入 10 个元素后 set 大小的 size 为:" << myints.size() << '\n';
myints.insert(100);
std::cout << "插入元素 100 后 set 大小的 size 为:" << myints.size() << '\n';
myints.erase(5);
std::cout << "删除 5 个元素后 set 大小的 size 为:" << myints.size() << '\n';
return 0;
}
std::set::empty
#include<iostream>
#include<set>
int main() {
std::set<int> myset;
myset.insert(20);
myset.insert(30);
myset.insert(10);
std::cout << "myset 容器中的内容为:";
while (!myset.empty()) {
std::cout << ' ' << *myset.begin();
myset.erase(myset.begin());
}
std::cout << '\n';
return 0;
}
修改操作
std::set::clear
std::set::swap
std::set::insert
插入元素,返回 pair<iterator, bool>,表示是否插入成功及迭代器位置。
#include<iostream>
#include<set>
int main() {
std::set<int> myset;
std::set<int>::iterator it;
std::pair<std::set<int>::iterator, bool> ret;
for (int i = 1; i <= 5; ++i) {
myset.insert(i * 10);
}
ret = myset.insert(20);
if (ret.second == false) it = ret.first;
myset.insert(it, 25);
myset.insert(it, 24);
myset.insert(it, 26);
int myints[] = {5, 10, 15};
myset.insert(myints, myints + 3);
return 0;
}
std::set::erase
#include<iostream>
#include<set>
int main() {
std::set<int> myset;
std::set<int>::iterator it;
for (int i = 1; i < 10; i++) {
myset.insert(i * 10);
}
it = myset.begin(); ++it;
myset.erase(it);
myset.erase(40);
it = myset.find(60);
myset.erase(it, myset.end());
return 0;
}
比较操作
std::set::key_comp
std::set::value_comp
其他操作
std::set::find
std::set::count
std::set::lower_bound / upper_bound / equal_range
#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);
itlow = myset.lower_bound(30);
itup = myset.upper_bound(60);
myset.erase(itlow, itup);
return 0;
}
使用示例
#include<iostream>
#include<set>
using namespace std;
int main() {
set<int> s;
s.insert(5); s.insert(2); s.insert(7); s.insert(5);
auto it = s.begin();
while (it != s.end()) {
cout << *it << " ";
++it;
}
cout << endl;
s.insert({2, 8, 3, 9});
for (auto e : s) {
cout << e << " ";
}
cout << endl;
set<string> strset = {"sort", "insert", "add"};
for (auto& e : strset) {
cout << e << " ";
}
return 0;
}
#include<iostream>
#include<set>
using namespace std;
int main() {
set<int> s = {4, 2, 7, 2, 8, 5, 9};
s.erase(s.begin());
int x; cin >> x;
int num = s.erase(x);
if (num == 0) cout << x << " 不存在!" << endl;
else cout << x << " 存在!" << endl;
cin >> x;
auto pos = s.find(x);
if (pos != s.end()) s.erase(pos);
cin >> x;
if (s.count(x)) cout << x << " 在!" << endl;
else cout << x << " 不存在!" << endl;
return 0;
}
multiset 容器
介绍
std::multiset 允许元素重复,底层同样由红黑树实现,元素自动排序。
区别
set:自动去重,元素唯一。
multiset:允许元素重复,侧重按序存储重复数据。
#include<iostream>
#include<set>
using namespace std;
int main() {
multiset<int> s = {4, 2, 7, 2, 4, 8, 4, 5, 4, 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;
cin >> x;
s.erase(x);
return 0;
}
pair 类模板
介绍
std::pair 用于将两个不同类型的数据组合成一个逻辑单元,位于 <utility> 头文件。
语法:std::pair<Type1, Type2> pairName(value1, value2);
特性
- 元素访问:通过
first 和 second 成员变量访问。
- 构造与赋值:支持默认构造、拷贝构造、赋值操作。
- 比较操作:支持按字典序比较(先比 first,若相等再比 second)。
使用
在 map 中,键值对的类型是 pair<const Key, T>。
#include<utility>
template<class T1, class T2>
inline pair<T1, T2> make_pair(T1 x, T2 y) {
return pair<T1, T2>(x, y);
}
map 容器
介绍
std::map 是关联式容器,存储键值对 (Key, Value),按键排序,键唯一。
template<class Key,
class T,
class Compare = less<Key>,
class Alloc = allocator<pair<const Key, T>>
class map;
- 键类型(Key):用于索引和排序的关键字类型。
- 映射值类型(T):与键关联的具体数据类型。
- 比较器(Compare):定义键之间的排序规则。
常见构造
#include<iostream>
#include<map>
int main() {
std::map<char, int> m1;
m1['a'] = 10; m1['b'] = 30;
std::map<char, int> m2(m1.begin(), m1.end());
std::map<char, int> m3(m2);
return 0;
}
容量操作
std::map::size:返回元素数量。
std::map::empty:判断是否为空。
访问操作
std::map::operator[]
多功能接口,支持修改已存在的键值对、插入新数据或查找数据(键不存在时自动插入默认值)。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main() {
map<string, string> dict;
dict["insert"];
dict["left"] = "左边";
dict["left"] = "左边、剩余";
cout << dict["left"] << endl;
return 0;
}
修改操作
std::map::clear:清空容器。
std::map::swap:交换内容。
std::map::insert:插入键值对。
std::map::erase:删除元素。
其他操作
std::map::find:查找键。
std::map::count:统计键出现次数(map 中为 0 或 1)。
std::map::lower_bound / upper_bound / equal_range:区间查找。
使用示例
#include<iostream>
#include<map>
using namespace std;
int main() {
map<string, string> dict = {{"left", "左边"}, {"right", "右边"}};
auto it = dict.begin();
while (it != dict.end()) {
cout << it->first << ":" << it->second << endl;
++it;
}
dict.insert(make_pair("sort", "排序"));
string str;
while (cin >> str) {
auto ret = dict.find(str);
if (ret != dict.end()) {
cout << "=> " << ret->second << endl;
} else {
cout << "无此单词" << endl;
}
}
return 0;
}
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main() {
string arr[] = {"苹果", "西瓜", "苹果", "西瓜", "苹果"};
map<string, int> countMap;
for (const auto& str : arr) {
countMap[str]++;
}
for (const auto& e : countMap) {
cout << e.first << ":" << e.second << endl;
}
return 0;
}
multimap 容器
介绍
std::multimap 允许键冗余,适合存'一对多'的映射关系。
区别
map:键唯一,每个键对应唯一的映射关系。
multimap:键可重复,同一个键能记多个解释。
注意:multimap 不支持 operator[],因为键可冗余的场景下无法明确指定修改哪一个键对应的值。
OJ 练习
set 应用
349. 两个数组的交集
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;
}
};
142. 环形链表 II
思路:使用 set 存储访问过的节点,若插入失败则说明有环。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
set<ListNode*> s;
ListNode* cur = head;
while (cur) {
auto ret = s.insert(cur);
if (ret.second == false)
return cur;
cur = cur->next;
}
return nullptr;
}
};
map 应用
138. 随机链表的复制
思路:使用 map 存储原节点与拷贝节点的映射关系。
class Solution {
public:
Node* copyRandomList(Node* head) {
map<Node*, Node*> m;
Node* copyHead = nullptr;
Node* copyTail = nullptr;
Node* curr = head;
while (curr) {
if (copyTail == nullptr) {
copyHead = copyTail = new Node(curr->val);
} else {
copyTail->next = new Node(curr->val);
copyTail = copyTail->next;
}
m[curr] = copyTail;
curr = curr->next;
}
curr = head;
Node* copy = copyHead;
while (curr) {
if (curr->random == nullptr) {
copy->random = nullptr;
} else {
copy->random = m[curr->random];
}
curr = curr->next;
copy = copy->next;
}
return copyHead;
}
};
692. 前 K 个高频单词
class Solution {
public:
struct Compare {
bool operator()(const pair<string, int>& x, const pair<string, int>& y) const {
return x.second > y.second;
}
};
vector<string> topKFrequent(vector<string>& words, int k) {
map<string, int> m;
for (auto& e : words) {
m[e]++;
}
vector<pair<string, int>> v(m.begin(), m.end());
stable_sort(v.begin(), v.end(), Compare());
vector<string> ret;
for (int i = 0; i < k; i++) {
ret.push_back(v[i].first);
}
return ret;
}
};
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> m;
for (auto& e : words) {
m[e]++;
}
vector<pair<string, int>> v(m.begin(), m.end());
sort(v.begin(), v.end(), Compare());
vector<string> ret;
for (int i = 0; i < k; i++) {
ret.push_back(v[i].first);
}
return ret;
}
};
方法三:基于 priority_queue 大顶堆
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> m;
for (auto& e : words) {
m[e]++;
}
priority_queue<pair<string, int>, vector<pair<string, int>>, Compare> p(m.begin(), m.end());
vector<string> ret;
for (int i = 0; i < k; i++) {
ret.push_back(p.top().first);
p.pop();
}
return ret;
}
};
相关免费在线工具
- 加密/解密文本
使用加密算法(如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