在 C++ 标准库中,set 和 multiset 是两个非常常见的关联容器,主要用于存储和管理具有一定规则的数据集合。本文将详细讲解如何使用这两个容器,并结合实例代码,分析其操作和特性。
0.基础操作概览
0.1.构造:
set<T> st;
// 默认构造函数:
set(const set& st);
//拷贝构造函数
0.2.赋值:
set& operator=(const set& st);
//重载等号操作符
0.3.统计set容器大小以及交换set容器
size();
//返回容器中元素的数目
empty();
//判断容器是否为空
swap(st);
//交换两个集合容器
0.4.set容器进行插入数据和删除数据
insert(elem);
//在容器中插入元素。
clear();
// 清除所有元素
erase(pos);
//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end);
//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem);
//删除容器中值为elem的元素。
0.5.set查找和统计:对set容器进行查找数据以及统计数据
find(key);
//查找key是否存在,若存在,返回该键的元素的迭代器;
//若不存在,返回set.end();
count(key);
//对于set而言,统计key的元素个数
//只有两种结果:如果容器中不存在key,返回0; 否则,返回1
0.6.排序
set<int> st1;
// 储存int的集合(从小到大)
set<int, greater<int>> st2;
// 储存int的集合(从大到小)
1. set 与 multiset 的基本概念
- set:它是一种自动去重且按顺序排列的集合。每次插入元素时,set 会自动判断该元素是否已存在,若存在则不会插入。
- multiset:允许重复元素的集合,因此可以存储多个相同的元素。
- 关联容器:关联容器中的元素在插入时自动排序,因此不同于顺序容器如 vector 或 list,不需要手动排序。
2. set 容器的构造与赋值
在 C++ 中,set 提供了多种构造方式:
- 默认构造:
set <int> st;
创建一个空的整数集合。 - 拷贝构造:
set<int> st2(st);
从已有的集合 st 创建一个副本。 - 赋值操作:
set& operator=(const set& st);
可以通过重载的 = 操作符将一个集合的内容赋值给另一个集合。
构造与赋值实例:
void test0()
{
// 1.默认构造
set<int>s;
s.insert(1); // 插入元素1
s.insert(-4); // 插入元素-4
s.insert(2); // 插入元素2
s.insert(5); // 插入元素5
s.insert(8); // 插入元素8
s.insert(1); // 插入重复元素1,自动忽略
print(s); // 打印集合中的元素,输出为:-4 1 2 5 8
cout << endl;
// 2.拷贝构造
set<int>s2(s); // 拷贝构造函数
print(s2); // 输出:-4 1 2 5 8
cout << endl;
// 3.赋值操作
set<int>s3 = s2; // 赋值操作符
print(s3); // 输出:-4 1 2 5 8
}
在此代码中,set 的插入操作可以看出,重复元素(如插入的 1)不会出现在集合中,这是 set 自动去重的特性。
3.遍历 set 容器
在 set 中,不能使用下标访问元素,因此遍历集合有两种常见方式:
- 使用迭代器遍历:通过迭代器访问集合元素,适合所有关联容器。
- 基于范围的 for 循环:简化迭代器操作,代码更加简洁。
迭代器遍历:
void print(set<int>& s)
{
for (set<int>::iterator it = s.begin(); it != s.end(); it++)
{
cout << *it << " "; // 输出每个元素
}
cout << endl;
/*for (auto it = s.begin(); it != s.end(); it++)
{
cout << *it << " ";
}*/
}
基于范围的 for 循环
void print(set<int>& s)
{
for (int elem : s)
{
cout << elem << " "; // 直接输出元素
}
cout << endl;
/*for (auto it : s)
{
cout << it << " ";
}*/
}
降序遍历:如果集合是降序排序的,例如使用
set<int, greater>,需要在定义迭代器时添加相应的比较器。
void print(set<int, greater<int>>& s)
{
for (set<int, greater<int>>::iterator it = s.begin(); it != s.end(); it++)
{
cout << *it << " "; // 输出降序排列的元素
}
cout << endl;
}
遍历操作
void test_traversal()
{
set<int> s;
s.insert(5);
s.insert(1);
s.insert(3);
s.insert(7);
cout << "使用迭代器遍历 set:" << endl;
print(s); // 输出:1 3 5 7
cout << "使用基于范围的 for 循环遍历 set:" << endl;
print2(s); // 输出:1 3 5 7
}
总结:遍历 set 的两种方式都非常直观,迭代器方式适合所有关联容器,而基于范围的 for 循环则能让代码更简洁。
5. set 容器的大小统计与交换
size();
返回集合中的元素数量。empty();
检查集合是否为空,若为空返回 true,否则返回false。swap();
交换两个集合的内容。
统计大小与交换:
void test1()
{
//构造2个有数据的set容器
set<int>s;
s.insert(0);
s.insert(1);
s.insert(0);
s.insert(9);
s.insert(6);
s.insert(11);
set<int>s1;
s1.insert(0);
s1.insert(0);
s1.insert(0);
s1.insert(99);
s1.insert(4);
s1.insert(-1);
cout << endl;
//判断是否为空
if (!s.empty())
{
cout << "s容器的长度为:" << s.size() << endl;
}
cout << endl;
//交换
cout<< "交换前:" << endl;
cout << "s: " ;
print(s);
cout << endl << "s1:";
print(s1);
s.swap(s1);
cout << endl << endl;
cout << "交换后:" << endl ;
cout << "s: " ;
print(s);
cout << endl << "s1: ";
print(s1);
}
通过 swap() 操作,s 和 s1 的内容得到了交换。这样可以有效简化代码,避免使用临时变量来保存集合的内容。
6. set 容器的插入与删除操作
insert(elem);
向集合中插入元素。由于 set 自动排序,插入元素时不会指定位置。
删除元素有三种方式:
-
erase(pos);
删除pos迭代器所指的元素,返回下一个元素的迭代器。 -
erase(beg, end);
删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。 -
erase(elem);
删除容器中值为elem的元素。
void test2()
{
set<int>s1;
s1.insert(100);
s1.insert(100);
s1.insert(20);
s1.insert(0);
s1.insert(44);
s1.insert(12);
s1.insert(666);
s1.insert(9);
print(s1);
cout << endl;
//删除迭代器指定元素
s1.erase(s1.begin());//这里删掉的是第一个元素0,因为set容器自动排序
print(s1);
cout << endl;
//删除指定元素
s1.erase(20);
print(s1);
cout << endl;
//删除迭代器指定区间元素
s1.erase(s1.begin(), s1.end());
//等价于清空操作:
//s1.clear();
cout <<"当前s1的大小为:" << s1.size() << endl;
}
在此代码中,我们展示了三种不同的 erase 操作,可以方便地删除单个元素或一组元素。
7. set 容器的查找与统计
find(key);
查找key是否存在,若存在,返回该键的元素的迭代器;
若不存在,返回set.end();
count(key);
对于set而言,统计key的元素个数
只有两种结果:如果容器中不存在key,返回0; 否则,返回1
查找与统计:
void test3()
{
set<int>s1;
s1.insert(1);
s1.insert(100);
s1.insert(23);
s1.insert(0);
s1.insert(404);
s1.insert(12);
s1.insert(999);
s1.insert(9);
// 查找元素 404
auto it = s1.find(404);
if (it != s1.end()) {
cout << "找到了元素 404" << endl; // 输出:找到了元素404
} else {
cout << "未找到元素 404" << endl;
}
// 使用 count 统计元素个数
int n = s1.count(404);
cout << "s1 中元素 404 的个数为:" << n << endl; // 输出:1
}
通过 find() 和 count() 函数,可以轻松实现元素的查找与存在性检查。
8. set 容器的排序特性
默认情况下,set 按照升序排列。可以通过
set<int, greater<int>>
指定降序排列。
set<int> st1;
储存int的集合(从小到大)set<int, greater<int>> st2;
储存int的集合(从大到小)
排序:
void test4()
{
//默认构造
set<int>s1;
//等价于set<int, less<int>>s1;
s1.insert(10);
s1.insert(40);
s1.insert(20);
s1.insert(0);
s1.insert(-10);
print(s1);
//降序构造,用到比较器greater(类型)
cout << endl;
set<int, greater<int>>s2;
s2.insert(10);
s2.insert(40);
s2.insert(20);
s2.insert(0);
s2.insert(-10);
print2(s2);
}
通过比较器 greater 可以实现集合的降序排列。
9.相关注意事项
9.1. 自动排序与元素唯一性
自动排序:set 会自动将插入的元素按升序(或根据提供的自定义比较器排序)进行排序。因此,插入顺序与实际存储顺序可能不同。
注意:由于 set 是自动排序的容器,插入操作可能会引发排序操作,这使得插入操作的平均时间复杂度为 O(log n),适合需要快速查找和去重的场景。
元素唯一性:set 不允许重复元素。如果插入的元素已经存在于集合中,set 会自动忽略该元素。即使多次插入相同的值,集合中只会保留一个副本。
set<int> s;
s.insert(1);
s.insert(1); // 插入重复元素,set 会自动忽略
print(s); // 输出:1
9.2. 不能使用下标访问
与 vector 不同,set 作为关联容器不能通过下标访问元素,也不能像顺序容器那样直接修改元素。只能通过迭代器或基于范围的 for 循环来遍历集合。
set<int> s = {10, 20, 30};
// s[0] = 5; // 错误!set 不能使用下标
for (int elem : s)
{
cout << elem << " "; // 正确的遍历方式
}
9.3. 修改元素的限制
在 set 中,由于其自动排序的特性,不能直接通过迭代器修改元素的值。如果需要修改元素值,必须先删除该元素,然后插入新的值。
set<int> s = {10, 20, 30};
auto it = s.find(20);
if (it != s.end())
{
s.erase(it); // 先删除 20
s.insert(25); // 再插入新值 25
}
print(s); // 输出:10 25 30
9.4. 迭代器的有效性
当删除或插入元素时,set 的迭代器可能会失效。特别是在删除操作中,如果需要使用迭代器操作,建议在删除之后重新获取下一个有效的迭代器。
set<int> s = {1, 2, 3, 4, 5};
auto it = s.begin();
while (it != s.end())
{
if (*it == 3)
{
it = s.erase(it); // erase 返回下一个有效迭代器
}
else
{
it++;
}
}
print(s); // 输出:1 2 4 5
9.6. 自定义排序规则
如果需要按自定义顺序排序 set 中的元素,可以通过传入自定义的比较器来改变排序方式。例如,可以使用 greater 来实现降序排列,或者提供自定义的比较函数。
set<int, greater<int>> s = {10, 20, 30};
print2(s); // 输出:30 20 10
9.8. 避免重复调用 find 和 count
如果你想要同时查找元素和统计某个元素的出现次数,不必重复调用 find() 和 count(),因为 count() 可以直接返回是否存在目标元素(对于 set,返回值只会是 0 或 1)。count() 本质上相当于 find() 的简化版。
set<int> s = {10, 20, 30};
if (s.count(20))
{
cout << "元素 20 存在" << endl;
}