在算法竞赛和编程能力认证考试(如GESP)中,std::set是最常用的STL容器之一。它基于红黑树实现,具有三个核心特性:
这些特性使set特别适合解决以下三类问题:
提示:虽然unordered_set的查找效率更高(O(1)),但它不保证元素顺序,在需要有序输出的场景中无法替代set。
给定n个可能重复的整数,要求:
传统方法需要:
而使用set只需:
虽然时间复杂度相同,但代码量减少50%以上,且不易出错。
cpp复制#include <iostream>
#include <set>
using namespace std;
int main() {
int n, x;
set<int> s;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> x;
s.insert(x); // 自动去重
}
cout << s.size() << endl;
for (auto it = s.begin(); it != s.end(); ++it) {
cout << *it << " "; // 自动有序输出
}
return 0;
}
s.size()直接获取去重后的元素数量begin()到end()默认按升序排列需要维护一个固定大小的集合,当新元素到来时:
虽然队列更适合处理FIFO逻辑,但set可以:
cpp复制#include <iostream>
#include <set>
#include <queue>
using namespace std;
int main() {
int m, n, x, ans = 0;
set<int> s;
queue<int> q;
cin >> m >> n;
for (int i = 0; i < n; ++i) {
cin >> x;
if (s.find(x) == s.end()) { // 快速存在性判断
++ans;
if (s.size() >= m) {
s.erase(q.front()); // 移除最早元素
q.pop();
}
s.insert(x);
q.push(x);
}
}
cout << ans;
return 0;
}
需要实时维护一个不断增长的序列,在每次插入后立即输出当前中位数。
使用两个set构成对顶堆结构:
left:存较小的一半,有序递减right:存较大的一半,有序递增left.size() == right.size()(偶数个)left.size() == right.size()+1(奇数个)cpp复制#include <iostream>
#include <set>
using namespace std;
int main() {
int n, x;
multiset<int, greater<int>> left; // 允许重复的降序set
multiset<int> right; // 允许重复的升序set
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> x;
// 插入策略
if (left.empty() || x <= *left.begin()) {
left.insert(x);
} else {
right.insert(x);
}
// 平衡两个set
if (left.size() > right.size() + 1) {
right.insert(*left.begin());
left.erase(left.begin());
} else if (right.size() > left.size()) {
left.insert(*right.begin());
right.erase(right.begin());
}
// 输出中位数
if (i % 2) cout << *left.begin() << endl;
}
return 0;
}
cpp复制struct cmp {
bool operator()(const int& a, const int& b) const {
return a > b; // 改为降序
}
};
set<int, cmp> customSet;
cpp复制s.erase(key); // 删除特定值
s.erase(iterator); // 删除迭代器位置
s.erase(first, last); // 删除区间
cpp复制auto minVal = *s.begin(); // 最小元素
auto maxVal = *s.rbegin(); // 最大元素
cpp复制set<int> s;
s.reserve(10000); // 预先分配空间减少rehash
cpp复制s.emplace(42); // 避免临时对象构造
cpp复制vector<int> v{1,2,3};
s.insert(v.begin(), v.end()); // 比循环插入更高效
cpp复制auto it = s.begin();
s.erase(it++); // 正确:先使用后递增
// s.erase(it); it++; // 错误:迭代器已失效
cpp复制struct Point {
int x,y;
bool operator<(const Point& p) const {
return x < p.x || (x == p.x && y < p.y);
}
};
set<Point> pointSet;
当数据范围很大但实际值较少时,可以用set实现离散化:
cpp复制set<int> uniqueValues;
map<int, int> discretized;
// 收集所有可能的值
for (int x : originalData) {
uniqueValues.insert(x);
}
// 建立映射关系
int idx = 0;
for (int val : uniqueValues) {
discretized[val] = ++idx;
}
利用有序特性快速查找最近元素:
cpp复制auto findClosest(set<int>& s, int target) {
auto it = s.lower_bound(target);
if (it == s.begin()) return *it;
if (it == s.end()) return *--it;
int a = *it, b = *--it;
return abs(a-target) < abs(b-target) ? a : b;
}
统计值在[L,R]范围内的元素数量:
cpp复制int countInRange(set<int>& s, int L, int R) {
auto first = s.lower_bound(L);
auto last = s.upper_bound(R);
return distance(first, last); // O(k)复杂度
}
在实际编程竞赛中,熟练掌握set的这些高级用法可以大幅提升解题效率。建议从基础题目开始练习,逐步过渡到复杂场景的应用。