1. set系列的使用
1.1 set和multiset参考文档
1.2 set类的介绍
set 是一个关联容器,用于存储唯一且有序的元素。其模板定义如下:
cpp
template <class Key, class Compare = less<Key>, class Allocator = allocator<Key>>
class set;
- Key:表示集合中元素的类型,即关键字类型。
- Compare :比较函数对象,默认为
less<Key>,要求支持<操作符或可调用的仿函数,用于确定元素之间的顺序。 - Allocator:内存分配器,默认使用标准分配器。
⚠️ 关键点:
set要求元素类型Key必须支持"小于"比较(即a < b成立),否则无法构建红黑树结构。- 若需自定义排序规则(如降序、字符串忽略大小写等),可通过自定义仿函数传入
Compare参数。- 底层实现为 红黑树(Red-Black Tree) ,保证插入、删除、查找的时间复杂度均为 O(log N)。
- 所有元素在内部按升序排列,迭代器遍历遵循中序遍历顺序。
- 不允许重复元素,每个键值唯一。
cpp
// 示例:自定义比较方式(降序)
struct Greater {
bool operator()(const int& a, const int& b) const {
return a > b; // 大的在前
}
};
set<int, Greater> s; // 降序排列的 set
1.3 set的构造与迭代器
set 支持多种构造方式:
cpp
set<int> s1; // 空 set
set<int> s2 = {1, 5, 3, 2}; // 构造并初始化
set<int> s3(s2.begin(), s2.end()); // 迭代器范围构造
set<int> s4(s2); // 拷贝构造
迭代器类型:
iterator:可读可写(但不可修改键值)const_iterator:只读
❗ 重要限制:
- 不能通过迭代器修改
set中的元素值(即key)。- 因为修改键值会破坏红黑树的有序性,导致未定义行为。
cpp
set<int> s = {10, 20, 30};
for (auto it = s.begin(); it != s.end(); ++it) {
// *it = 100; // 错误!不允许修改 key
}
✅ 支持范围 for 循环:
cpp
for (const auto& x : s) {
cout << x << " "; // 输出:10 20 30
}
反向遍历:
cpp
for (auto rit = s.rbegin(); rit != s.rend(); ++rit) {
cout << *rit << " "; // 输出:30 20 10
}
1.4 set的增删查操作
插入操作
cpp
set<int> s;
// insert(value) ------ 只插入,返回一个 pair<iterator, bool>
auto ret = s.insert(5);
if (ret.second) {
cout << "插入成功" << endl;
} else {
cout << "已存在,插入失败" << endl;
}
// insert(first, last) ------ 插入范围
vector<int> v = {1, 2, 3};
s.insert(v.begin(), v.end());
// emplace() ------ 直接构造元素,效率更高
s.emplace(100);
✅
insert()返回值是一个pair<iterator, bool>:
first:指向插入位置的迭代器second:true表示插入成功,false表示已有该元素
查找操作
cpp
// find(key) ------ 找到返回迭代器,未找到返回 end()
auto it = s.find(5);
if (it != s.end()) {
cout << "找到: " << *it << endl;
} else {
cout << "未找到" << endl;
}
// count(key) ------ 统计出现次数(set中永远是 0 或 1)
int cnt = s.count(5); // 0 或 1
🔍 注意:
count()在set中无实际意义(总是 0 或 1),但在multiset中非常有用。
删除操作
cpp
// erase(iterator)
auto it = s.find(5);
if (it != s.end()) {
s.erase(it);
}
// erase(key) ------ 删除所有等于 key 的元素(但 set 中只有一个)
s.erase(5);
// erase(first, last) ------ 删除区间
s.erase(s.begin(), s.end());
📌
erase(key)会删除所有匹配的元素,但由于set不允许重复,最多只删一个。
1.5 multiset 和 set 的差异
multiset 与 set 基本结构一致,区别在于:
- 允许重复元素(多重集)
- 底层仍为红黑树,保持有序
- 插入、删除、查找时间复杂度仍为 O(log N)
cpp
multiset<int> ms = {1, 1, 2, 3, 3, 3};
// count() 返回重复数量
cout << ms.count(3) << endl; // 输出:3
// find() 只返回第一个匹配的迭代器
auto it = ms.find(3);
if (it != ms.end()) {
cout << *it << endl; // 输出:3
}
// erase(key) 会删除所有相同键值
ms.erase(3); // 移除全部三个 3
⚠️
multiset不支持operator[],因为多个键对应多个值,无法确定唯一映射。
2. map系列的使用
2.1 map和multimap参考文档
2.2 map类的介绍
map 是一个关联容器,存储键值对(key-value),其中每个键唯一。
模板定义:
cpp
template <class Key, class T, class Compare = less<Key>, class Allocator = allocator<pair<const Key, T>>>
class map;
- Key :键类型,必须支持
<比较 - T:值类型(mapped_type)
- Compare:比较函数对象
- Allocator:内存分配器
✅ 特性:
- 底层实现为 红黑树
- 按照
Key升序排列- 支持快速插入、删除、查找,时间复杂度均为 O(log N)
map中的键是唯一的,不允许重复
cpp
map<string, int> scores = {
{"Alice", 95},
{"Bob", 87},
{"Charlie", 92}
};
2.3 pair类型介绍
map 内部节点以 pair<const Key, T> 形式存储键值对。
cpp
typedef pair<const Key, T> value_type;
🔑
const Key表明:键一旦创建就不能被修改,否则破坏红黑树结构。
cpp
auto p = make_pair("x", 100);
// p.first = "y"; // 编译错误!first 是 const
2.4 map的构造方式
cpp
map<string, int> m1; // 空 map
map<string, int> m2 = {{"A", 1}, {"B", 2}}; // 初始化列表
map<string, int> m3(m2.begin(), m2.end()); // 迭代器范围构造
map<string, int> m4(m2); // 拷贝构造
支持正向与反向迭代:
cpp
for (auto it = m4.begin(); it != m4.end(); ++it) {
cout << it->first << ": " << it->second << endl;
}
// 反向遍历
for (auto rit = m4.rbegin(); rit != m4.rend(); ++rit) {
cout << rit->first << ": " << rit->second << endl;
}
✅ 支持范围 for:
cpp
for (const auto& [k, v] : m4) {
cout << k << " -> " << v << endl;
}
2.5 map的增删查操作
插入操作
cpp
map<string, int> m;
// insert(pair) ------ 插入键值对
m.insert({"Alice", 95});
// insert({key, value}) ------ C++11 风格
m.insert(make_pair("Bob", 87));
// emplace() ------ 更高效,直接构造
m.emplace("Charlie", 92);
// insert(iterator, value) ------ 位置提示优化性能
auto hint = m.begin();
m.insert(hint, {"David", 90});
✅
insert()返回pair<iterator, bool>:
second == true:插入成功second == false:键已存在,插入失败
查找操作
cpp
// find(key) ------ 找到返回迭代器,未找到返回 end()
auto it = m.find("Alice");
if (it != m.end()) {
cout << "分数: " << it->second << endl;
}
// count(key) ------ 0 或 1(因为键唯一)
int cnt = m.count("Alice"); // 1
删除操作
cpp
// erase(iterator)
m.erase(m.find("Alice"));
// erase(key) ------ 删除指定键的所有条目(仅一个)
m.erase("Bob");
// erase(first, last) ------ 删除区间
m.erase(m.begin(), m.end());
2.6 map的数据修改机制
map 支持修改 value,但禁止修改 key。
1. 通过迭代器修改值
cpp
auto it = m.find("Charlie");
if (it != m.end()) {
it->second = 95; // ✅ 可以修改 value
}
2. 通过 operator[] ------ 强大的复合接口
cpp
m["Alice"] = 98; // ✅ 存在则更新,不存在则插入
💡
operator[]的行为:
- 如果键存在,返回对应的
value引用,可修改;- 如果键不存在,自动插入一个默认值(如 0 或空构造),然后返回引用。
cpp
m["NewUser"] = 100; // 键不存在 → 自动插入 ("NewUser", 0),再赋值为 100
⚠️ 使用注意:
operator[]会隐式插入新键,可能造成意外数据膨胀。- 若只想查询而不插入,应使用
find()。
2.7 multimap 和 map 的差异
multimap 与 map 结构相似,主要区别如下:
| 特性 | map |
multimap |
|---|---|---|
| 键是否唯一 | 是 | 否(允许多个相同键) |
operator[] |
支持 | 不支持(因无法确定唯一值) |
insert() |
插入单个键值对 | 可插入多个相同键 |
find() |
找到第一个匹配项 | 找到第一个匹配项 |
count() |
总是 0 或 1 | 返回相同键的数量 |
erase(key) |
删掉所有同键元素(仅一个) | 删掉所有同键元素 |
cpp
multimap<string, int> mm = {
{"Math", 90},
{"Math", 85},
{"English", 92}
};
// find 只返回第一个匹配的
auto it = mm.find("Math");
if (it != mm.end()) {
cout << "第一门数学成绩: " << it->second << endl; // 90
}
// count 统计数量
cout << mm.count("Math") << endl; // 2
// erase(key) 删掉所有相同键
mm.erase("Math"); // 两个都删了
❌
mm["Math"] = 95;------ 编译错误!multimap不支持operator[]
总结对比表
| 容器 | 是否允许重复键 | 是否支持 operator[] |
是否支持修改 key |
底层结构 | 排序依据 |
|---|---|---|---|---|---|
set |
否 | 否 | 否 | 红黑树 | Key 升序 |
multiset |
是 | 否 | 否 | 红黑树 | Key 升序 |
map |
否 | 是 | 否 | 红黑树 | Key 升序 |
multimap |
是 | 否 | 否 | 红黑树 | Key 升序 |
实用建议
- 使用
set/map时,确保Key类型支持<比较。 - 若需自定义排序,提供仿函数作为
Compare模板参数。 - 优先使用
find()查询,避免operator[]导致不必要的插入。 - 对于需要重复键的场景,选择
multiset/multimap。 - 迭代时避免修改
key,否则程序崩溃或行为异常。 emplace()比insert()效率更高,尤其适用于复杂对象。