标准库容器——<set>
在 C++ 标准库中,set 是一种关联容器,主要用于存储不重复的元素,并能自动维持元素的有序性。
set 的底层原理
set 的底层通常通过红黑树(Red-Black Tree) 实现(一种自平衡的二叉搜索树),这决定了它的核心特性:
红黑树的特性(支撑 set 的行为)
红黑树是一种平衡二叉树,通过严格的规则(如节点颜色、左右子树高度差限制)保证树的 “平衡”,避免出现极端倾斜的情况(如退化为链表)。其核心优势是:
- 有序性:二叉搜索树的特性决定了中序遍历(左→根→右)可以得到有序序列,因此
set中的元素天然按升序(默认)排列。 - 高效操作:插入、删除、查找元素的时间复杂度均为 O(log n)(n 为元素数量),因为红黑树的高度始终保持在 log n 级别。
set 的核心特性(由红黑树决定)
- 元素唯一:红黑树不允许重复键值,因此
set会自动去重(插入重复元素时会被忽略)。 - 元素不可修改:
set中的元素是 “常量”(const),不能通过迭代器直接修改。因为修改元素值会破坏红黑树的有序性(需要先删除旧元素,再插入新元素)。 - 有序存储:元素始终按默认升序(或自定义排序规则)排列,遍历
set时会按序访问元素。
set 的基本用法
使用 set 需包含头文件 <set>,并使用命名空间 std(或显式指定 std::set)。
定义与初始化
set 的模板定义为:template <class T, class Compare = less<T>, class Allocator = allocator<T>> class set;
T:元素类型(如int、string等)。Compare:排序规则(默认less<T>,即升序;可指定greater<T>为降序,或自定义比较器)。
#include <set>
#include <iostream>
using namespace std;
int main() {
// 1. 空set(默认升序)
set<int> s1;
// 2. 用初始化列表初始化
set<int> s2 = {3, 1, 4, 1, 5}; // 自动去重+排序,结果:{1,3,4,5}
// 3. 用迭代器范围初始化(从其他容器复制元素)
vector<int> vec = {2, 7, 1};
set<int> s3(vec.begin(), vec.end()); // 结果:{1,2,7}
// 4. 自定义排序(降序)
set<int, greater<int>> s4 = {3, 1, 4}; // 结果:{4,3,1}
return 0;
}
核心操作
插入元素(insert)
- 插入单个元素:返回
pair<iterator, bool>,其中bool表示是否插入成功(重复元素会失败)。 - 插入迭代器范围:批量插入元素(自动去重 + 排序)。
set<int> s; // 插入单个元素 auto res = s.insert(3); // 插入成功,res.second = true res = s.insert(3); // 重复插入,res.second = false(元素不变) // 批量插入 s.insert({1, 4, 1, 5}); // 最终s:{1,3,4,5}查找元素(
find)返回指向目标元素的迭代器;若不存在,返回
s.end()。时间复杂度 O (log n)。set<int> s = {1,3,4,5}; auto it = s.find(3); if (it != s.end()) { cout << "找到元素:" << *it << endl; // 输出:3 } else { cout << "元素不存在" << endl; }删除元素(
erase)支持通过值、迭代器或范围删除元素,返回删除的元素个数(通过值删除时)。
set<int> s = {1,3,4,5}; // 1. 通过值删除 int cnt = s.erase(3); // 删除值为3的元素,cnt=1(若不存在则返回0) // 2. 通过迭代器删除(需先找到元素) auto it = s.find(4); if (it != s.end()) { s.erase(it); // 删除迭代器指向的元素(4) } // 3. 删除所有元素(清空) s.clear(); // 等价于 s.erase(s.begin(), s.end())其他常用方法
方法 功能 size()返回元素个数 empty()判断是否为空(空返回 true)begin()/end()返回首元素 / 尾后迭代器(用于遍历) rbegin()/rend()返回反向迭代器(从尾到头遍历) count(val)返回值为 val的元素个数(只能是 0 或 1),简洁判断val是否存在
遍历 set
由于 set 是有序的,遍历会按排序规则访问元素。支持迭代器遍历和范围 for 循环:
// 迭代器遍历
for (auto it = s.begin(); it != s.end(); ++it) {
…………
}
for (auto t : s) {
…………
}
不同的set
| 对比维度 | set | multiset | unordered_set |
|---|---|---|---|
| 底层实现 | 红黑树(自平衡二叉搜索树) | 红黑树(同 set,仅处理重复逻辑不同) | 哈希表(链地址法解决冲突) |
| 重复元素支持 | ❌ 不允许(自动去重) | ✅ 允许(可存储多个相同值) | ❌ 不允许(自动去重) |
| 排序特性 | ✅ 自动按规则排序(默认升序) | ✅ 自动按规则排序(同 set,重复元素相邻) | ❌ 无序(按哈希值存储) |
| 查找效率 | O (log n)(红黑树二分查找) | O (log n)(同 set,需定位首个匹配元素) | 平均 O (1),最坏 O (n)(哈希冲突极端情况) |
| 插入 / 删除效率 | O (log n)(红黑树旋转平衡) | O (log n)(同 set,插入重复元素无需重新平衡) | 平均 O (1),最坏 O (n) |
| 迭代器类型 | 双向迭代器(支持 ++/--) | 双向迭代器(同 set) | 前向迭代器(仅支持 ++) |
count(val) 返回值 | 0 或 1(仅判断存在性) | ≥0(返回 val 的实际重复次数) | 0 或 1(仅判断存在性) |
erase() 行为 | 按值删:删除 0 或 1 个元素;按迭代器删:删除 1 个元素 | 按值删:删除所有值为 val 的元素;按迭代器删:删除 1 个元素 | 同 set(无重复,删 0 或 1 个) |
| 内存开销 | 中等(红黑树节点存储元素 + 颜色 / 指针) | 中等(同 set,节点数更多) | 较高(哈希表需预留桶空间,可能有冗余) |
一些细节
如果把一个数组加入unordered_set可以用insert或者初始化的时候使用迭代器
insert的方法:哈希表无法预知最终元素数量,只能在元素数量超过当前容量时被动扩容(每次扩容需要重新计算所有元素的哈希值并迁移,耗时较高)。但是insert 方法在插入重复元素时,会先检查元素是否已存在,若存在则直接返回(不执行插入),整个过程几乎没有额外开销(仅一次哈希查找)。
迭代器初始化:范围构造函数(unordered_set(InputIt first, InputIt last))可以通过迭代器范围提前计算出需要插入的元素总数(通过 distance(first, last)),从而预先分配足够大的内存空间(桶数量),避免插入过程中多次触发哈希表扩容。但是范围构造逻辑是遍历整个迭代器范围(包括所有重复元素),然后逐个插入非重复元素。即使元素重复,它也需要先遍历完所有元素(计算总长度用于预分配空间),再对每个元素执行 “检查是否存在 + 插入”。对于重复率极高的数组,这种 “先全量遍历再去重” 的操作,比第一种 “边遍历边去重” 更耗时(多了一次全量遍历的成本)。

浙公网安备 33010602011771号