1. 概述
定义
template<
class Key,
class Compare = std::less<Key>,
class Allocator = std::allocator<Key>
>
class std::set;
特点
-
有序:所有元素按严格弱序(由 Compare 决定)排列,不保证相同键的重复出现(键唯一)。
-
唯一键:插入时如果集合中已有相同元素,不会增加新的节点。
-
底层结构:通常基于红黑树(balanced binary search tree)实现。
2. 内部实现
-
红黑树
- 平衡二叉查找树,保证从根到任一叶子路径的“黑色节点数”相同,从而令树高度为 O(log n)。
-
节点结构
- 每个节点存储:
- 元素值 Key
- 左/右子指针和父指针
- 一个颜色标记(红或黑)
- 每个节点存储:
-
拷贝与移动
- 拷贝时会对整棵树进行深拷贝;移动构造则接管底层指针,无需逐节点复制。
3. 性能特征
操作 | 平均复杂度 | 最坏复杂度 | 备注 |
---|---|---|---|
insert | O(log n) | O(log n) | 包括查找插入位置与调整平衡 |
erase | O(log n) | O(log n) | 按键删除时需旋转与重着色 |
find | O(log n) | O(log n) | |
clear | O(n) | O(n) | 释放所有节点 |
遍历 | O(n) | O(n) | 中序遍历即可 |
-
内存开销:
- 每个节点需存储三个指针(左右、父)和一个颜色字段,相比哈希表更紧凑但比 std::vector 等顺序容器要高。
-
迭代器失效规则:
- 插入和删除会使仅被删除的节点的迭代器失效,其他节点迭代器保持有效。
4. 常用 API
// 构造与析构
std::set<Key> s1; // 默认构造
std::set<Key> s2({k1, k2, k3}); // 初始化列表
std::set<Key, Compare> s3(comp); // 指定比较器
// 大小与容量
bool empty() const noexcept;
size_t size() const noexcept;
size_t max_size() const noexcept;
// 元素访问
iterator find(const Key& key);
size_t count(const Key& key) const; // 要么 0,要么 1
// 插入
std::pair<iterator,bool> insert(const Key& key);
iterator insert(iterator hint, const Key& key);
template<class... Args>
std::pair<iterator,bool> emplace(Args&&... args);
// 删除
size_t erase(const Key& key);
iterator erase(iterator pos);
iterator erase(iterator first, iterator last);
// 遍历
iterator begin() noexcept;
iterator end() noexcept;
const_iterator cbegin() const noexcept;
const_iterator cend() const noexcept;
// 有序查询
iterator lower_bound(const Key& key); // ≥ key 的第一个位置
iterator upper_bound(const Key& key); // > key 的第一个位置
std::pair<iterator,iterator> equal_range(const Key& key);
// 比较器与分配器访问
Compare key_comp() const;
Compare value_comp() const; // 同 key_comp
Allocator get_allocator() const noexcept;
// 交换
void swap(std::set& other) noexcept;
5. 使用示例
#include <set>
#include <iostream>
int main() {
std::set<int> s;
// 插入
s.insert(3);
s.emplace(1);
s.insert({5, 2, 4});
// 查找
if (s.find(2) != s.end()) {
std::cout << "2 存在\n";
}
// 遍历(中序:1,2,3,4,5)
for (int x : s) {
std::cout << x << " ";
}
std::cout << "\n";
// 有序查询
auto it = s.lower_bound(3); // 指向 3
std::cout << "lower_bound(3): " << *it << "\n";
// 删除
s.erase(3); // 删除值为 3 的节点
// 范围删除
s.erase(s.begin(), s.lower_bound(4)); // 删除所有 <4 的元素
// 清空
s.clear();
}
6. 自定义比较器
当需要自定义排序规则(如降序或复合键)时,可提供自定义比较器:
struct Desc {
bool operator()(int a, int b) const {
return a > b; // 降序
}
};
std::set<int, Desc> s_desc; // 插入后,将按从大到小顺序存储
对于复合类型:
struct Person {
std::string name;
int age;
};
struct ByAgeName {
bool operator()(Person const& a, Person const& b) const {
if (a.age != b.age) return a.age < b.age;
return a.name < b.name;
}
};
// 年龄升序,若年龄相同则按名字升序
std::set<Person, ByAgeName> roster;
7. 注意事项与优化
-
避免不必要的拷贝
- insert(const Key&) 会拷贝一次;若可移动,优先使用 insert(Key&&) 或 emplace()。
-
hint 参数
- insert(hint, key):若能提供一个接近正确位置的迭代器 hint,可将插入复杂度降到常数时间。
-
批量插入
- 对初始化列表或范围插入,建议先 reserve()(C++23 起支持)或构造时传入范围,以减少重平衡次数。
-
避免迭代器失效
- 删除或插入仅影响相关节点迭代器,其他迭代器保持有效。
8. 使用建议
-
适用场景
- 需要自动排序且元素唯一时,首选 std::set。
- 需要查询前驱/后继、区间操作(lower_bound、upper_bound、中序遍历)时非常方便。
-
不适用场景
- 需要允许重复键或多对一映射时,改用 std::multiset 或 std::map。
- 对性能有极致要求且不在意顺序时,可考虑 std::unordered_set(哈希实现,平均 O(1))。