1. 关联容器概述
在 C++ 的标准模板库(STL)里,容器可分为以下几类:
- 顺序容器:像
vector、list、deque等,元素的存储顺序由插入顺序决定。 - 关联容器:例如
set、map,这类容器会根据键(key)来自动对元素进行排序和存储。 - 无序容器:包含
unordered_set、unordered_map,基于哈希表实现,元素是无序的。
std::set 属于关联容器,它的主要特点是存储唯一的元素,并且这些元素会按照键的顺序排列。
2. std::set 的核心特性
- 元素唯一性:
set中不会存在重复的元素。当你尝试插入一个已经存在的元素时,插入操作会被忽略。 - 自动排序:元素会根据键的比较规则自动排序,默认是按照升序排列。
- 不可修改键值:一旦元素被插入到
set中,就不能直接修改其键值,因为这可能会破坏排序结构。若要修改元素,需先删除该元素,再插入新元素。 - 高效操作:插入、删除和查找操作的时间复杂度都是 O (log n),这是因为
set通常是基于红黑树(一种自平衡二叉搜索树)实现的。
3. 基本用法
3.1 头文件与命名空间
使用 std::set 时,需要包含 <set> 头文件,代码如下:
#include <set>
using namespace std; // 或者使用 std::set
3.2 集合的定义与初始化
下面是几种常见的定义和初始化 set 的方式:
// 定义一个空的 set,存储 int 类型的元素
set<int> s1;
// 用初始化列表初始化 set
set<int> s2 = {3, 1, 4, 1, 5}; // 实际存储 {1, 3, 4, 5}
// 拷贝构造函数
set<int> s3(s2);
// 范围初始化(从另一个容器)
vector<int> v = {5, 2, 6};
set<int> s4(v.begin(), v.end()); // {2, 5, 6}
3.3 插入元素
可以使用 insert() 方法向 set 中插入元素,示例如下:
set<int> s;
s.insert(10); // 插入单个元素
s.insert(20);
s.insert(10); // 重复元素,插入失败
int arr[] = {30, 40, 30};
s.insert(arr, arr + 3); // 从数组插入范围,实际插入 {30, 40}
// 检查插入是否成功(返回值是一个 pair)
auto result = s.insert(50);
if (result.second) {
cout << "插入成功,元素: " << *(result.first) << endl;
} else {
cout << "插入失败,元素已存在" << endl;
}
3.4 查找元素
查找元素主要有以下几种方法:
set<int> s = {1, 3, 5, 7};
// 使用 find() 方法查找元素
auto it = s.find(5);
if (it != s.end()) {
cout << "找到元素: " << *it << endl;
} else {
cout << "元素不存在" << endl;
}
// 使用 count() 方法判断元素是否存在
if (s.count(7) > 0) {
cout << "元素 7 存在" << endl;
}
// 查找第一个不小于给定值的元素(lower_bound)
auto lb = s.lower_bound(4);
cout << "lower_bound(4): " << *lb << endl; // 输出 5
// 查找第一个大于给定值的元素(upper_bound)
auto ub = s.upper_bound(4);
cout << "upper_bound(4): " << *ub << endl; // 输出 5
// 查找范围 [lower, upper)
auto range = s.equal_range(3);
cout << "equal_range(3): [" << *(range.first)
<< ", " << *(range.second) << ")" << endl;
3.5 删除元素
删除元素的操作如下:
set<int> s = {10, 20, 30, 40, 50};
s.erase(30); // 删除值为 30 的元素
s.erase(s.find(40)); // 删除迭代器指向的元素
// 删除范围 [first, last)
auto first = s.find(10);
auto last = s.find(50);
s.erase(first, last); // 删除 10 和 20
s.clear(); // 清空集合
3.6 遍历集合
可以通过以下几种方式遍历 set 中的元素:
set<int> s = {2, 4, 1, 3};
// 使用迭代器(升序)
cout << "升序遍历: ";
for (auto it = s.begin(); it != s.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 使用范围-based for 循环(C++11 及以后)
cout << "升序遍历(简化版): ";
for (int x : s) {
cout << x << " ";
}
cout << endl;
// 使用反向迭代器(降序)
cout << "降序遍历: ";
for (auto it = s.rbegin(); it != s.rend(); ++it) {
cout << *it << " ";
}
cout << endl;
4. 自定义比较函数
默认情况下,set 使用 std::less 进行元素比较,实现升序排列。你也可以自定义比较规则:
4.1 使用函数对象(Functor)
// 自定义比较函数:降序排列
struct Greater {
bool operator()(const int& a, const int& b) const {
return a > b;
}
};
set<int, Greater> s; // 降序排列的集合
s.insert(1);
s.insert(3);
s.insert(2);
// 集合中的元素顺序:3, 2, 1
4.2 使用 Lambda 表达式(C++11 及以后)
// 使用 lambda 表达式定义降序比较
auto cmp = [](int a, int b) { return a > b; };
set<int, decltype(cmp)> s(cmp);
s.insert(10);
s.insert(20);
s.insert(15);
// 集合中的元素顺序:20, 15, 10
5. 存储自定义类型
当 set 存储自定义类型的对象时,需要定义比较函数,以便集合进行排序:
class Person {
public:
string name;
int age;
Person(string n, int a) : name(n), age(a) {}
// 重载 < 运算符,用于集合排序
bool operator<(const Person& other) const {
return age < other.age; // 按年龄排序
}
};
// 使用自定义类型的集合
set<Person> people;
people.insert(Person("Alice", 25));
people.insert(Person("Bob", 20));
people.insert(Person("Charlie", 30));
// 遍历集合(按年龄升序)
for (const auto& p : people) {
cout << p.name << " (" << p.age << ")" << endl;
}
6. 常用方法总结
下面是 std::set 的一些常用方法:
| 方法 | 描述 |
|---|---|
insert(value) | 插入元素,如果元素已存在则不插入,返回一个包含迭代器和布尔值的 pair。 |
erase(value) | 删除指定值的元素,返回删除的元素个数(0 或 1)。 |
erase(iterator) | 删除迭代器指向的元素。 |
find(value) | 查找元素,返回指向该元素的迭代器,如果未找到则返回 end()。 |
count(value) | 检查元素是否存在,返回 0 或 1。 |
lower_bound(key) | 返回第一个不小于 key 的元素的迭代器。 |
upper_bound(key) | 返回第一个大于 key 的元素的迭代器。 |
equal_range(key) | 返回一个 pair,包含 lower_bound 和 upper_bound。 |
size() | 返回集合中元素的数量。 |
empty() | 判断集合是否为空。 |
clear() | 清空集合中的所有元素。 |
begin() | 返回指向集合第一个元素的迭代器(升序)。 |
end() | 返回指向集合末尾的迭代器(升序)。 |
rbegin() | 返回指向集合第一个元素的反向迭代器(降序)。 |
rend() | 返回指向集合末尾的反向迭代器(降序)。 |
7. 与其他容器的对比
| 容器 | 元素唯一性 | 元素顺序 | 查找效率 | 插入 / 删除效率 | 支持随机访问 |
|---|---|---|---|---|---|
std::set | 唯一 | 自动排序 | O(log n) | O(log n) | 否 |
std::unordered_set | 唯一 | 无序 | O (1) 平均 | O (1) 平均 | 否 |
std::vector | 不强制 | 插入顺序 | O(n) | O (n) 尾部 O (1) | 是 |
std::list | 不强制 | 插入顺序 | O(n) | O(1) | 否 |
8. 性能考虑
- 插入 / 删除 / 查找:时间复杂度为 O (log n),因为
set是基于红黑树实现的。 - 空间开销:由于使用树结构,每个节点需要额外存储父节点和子节点的指针,空间开销比顺序容器大。
- 适用场景:适用于需要快速查找、保持元素有序且唯一的场景,例如去重、排序查询等。
9. 示例代码
下面是一个综合示例,展示了 std::set 的各种用法:
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main() {
// 创建一个存储字符串的集合,按字符串长度排序
struct LengthCmp {
bool operator()(const string& a, const string& b) const {
return a.length() < b.length();
}
};
set<string, LengthCmp> words;
// 插入元素
words.insert("apple");
words.insert("banana");
words.insert("cherry");
words.insert("date"); // 长度为 4,会排在 "apple" 之前
// 遍历集合(按字符串长度升序)
cout << "按长度排序的单词:" << endl;
for (const auto& word : words) {
cout << word << " (长度: " << word.length() << ")" << endl;
}
// 查找长度为 5 的单词
auto it = words.lower_bound("*****"); // 5 个字符的字符串
if (it != words.end() && it->length() == 5) {
cout << "找到长度为 5 的单词: " << *it << endl;
} else {
cout << "未找到长度为 5 的单词" << endl;
}
// 删除长度大于 5 的单词
for (auto it = words.begin(); it != words.end(); ) {
if (it->length() > 5) {
it = words.erase(it); // erase 返回下一个迭代器
} else {
++it;
}
}
// 输出剩余元素
cout << "删除长度大于 5 的单词后:" << endl;
for (const auto& word : words) {
cout << word << endl;
}
return 0;
}
10. 输出结果
按长度排序的单词:
date (长度: 4)
apple (长度: 5)
banana (长度: 6)
cherry (长度: 6)
找到长度为 5 的单词: apple
删除长度大于 5 的单词后:
date
apple
总结
std::set 是 C++ 中一个非常实用的关联容器,它能够高效地存储唯一元素并自动排序。通过自定义比较函数,你可以灵活控制元素的排序规则。在需要快速查找、去重和有序遍历的场景中,std::set 是一个理想的选择。
2226

被折叠的 条评论
为什么被折叠?



