应用场景
set
顾名思义是一个集合。数学中集合
具有无序性和互异性,也就是集合中元素互不相同没有顺序。而在C++
中,set
不同于vector
是顺序容器,它是一个关联容器,是一个内部自动有序(升序)、自动去重的容器。内部实现是基于红黑树,一种自平衡的二叉搜索树。
根据set
的特点,当我们需要去重或者按照关键字排序时,可以考虑使用set
。
使用
定义
跟其他的容器类差不多,先是包含头文件set
,然后确定元素类型
#include <set>
using namespace std;
void test_set() {
set<int> S; // 元素类是是int
}
查
find(value)
查找对应值为value的迭代器,如果不存在返回end()
末迭代器
迭代器很不方便
count(value)
返回值等于value的元素个数(0 或 1)
除了上面两种方法,c++20 引入了一个新方法
contains(value)
。
学过java
的朋友应该很熟悉,Set接口里有一个抽象方法就是contains
,名字完全一样:)作用也完全一样。查找集合中是否存在值为value的元素,返回值是布尔类型。
增
insert()
insert(val)
可将val插入set容器中,并自动递增排序和去重,时间复杂度O(logn)
。
void test_set() {
set<int> S;
S.insert(1);
S.insert(2);
S.insert(1); // set中存在1,拒绝插入
for(int num : S) printf("%d ",num); // 打印输出 1 2 递增排序
}
删
erase()
形式参数有两种:迭代器和值。
-
erase(it)
形式参数为待删元素的迭代器 -
erase(val)
形式参数为待删元素的值
遍历
迭代器
set<int> S;
// 插入操作
for(set<int>::iterator::it = S.begin(); it != S.end(); it++) {
printf("%d ",*it);
}
// C++11 可以使用auto关键字
for(auto it = S.begin(); it != S.end(); it++) {
printf("%d ",*it);
}
范围for循环(for-range)
set<int> S;
// 插入操作
for(int num : S) {
printf("%d ",num);
}
// 没错就是这么简单
其他成员函数
size()
与
vector
相同,返回set容器的大小
clear()
清空容器
存储自定义类型
set
是自动根据键值排序的,所以当我们要存储自定义类型(struct
、class
)时该怎么办呢?
我只介绍一种比较熟悉的方法——<
重载。除此之外,还有仿函数等方法。
设想这样的情景:一次期末考试,有n名同学参加。现在有若干名同学的学号和对应分数(整数)。需求是按照分数递减的顺序打印各位同学的学号及其对应分数。假设学号是字符串类型,并且分数互不相同。
- 定义结构体,重载运算符 <
- 定义
set
- 录入各学生学号和成绩,放到set中存储
- 遍历
set
,降序打印学号
#include <iostream>
#include <set>
using namespace std;
struct Student {
string id;
int score;
Student(string _id,int _score) : id(_id),score(_score) {
}
bool operator<(const Student &other) const {
return this->score > other.score;
}
/* 注意两个点:
1. 需要定义为常函数
2. 默认是升序,只需将 < 重载为 > 既可以达到降序结果
*/
};
int main() {
int n;
scanf("%d",&n);
set<Student> S;
string id;
int score;
for(int i = 0; i < n; i++) {
cin>>id;
cin>>score;
S.insert(Student(id,score));
}
// 范围for循环写起来很简单
for(Student student:S) {
cout<<student.id<<" "<<student.score<<"\n";
}
return 0;
}
这里要求的是分数不相同,但是现实情况是存在并列的情况的,那我们该怎么办?
解决办法——multiset
multiset
中文名字是多重集,与set
的唯一不同是不会自动去重,仍按照键值排序,头文件还是set
。所以上面的代码只需要修改定义set的那行代码
int main() {
int n;
scanf("%d",&n);
multiset<Student> S;
// ...
return 0;
}
例题
这道例题利用的是
set
的自动去重
分析
c = 2 ∗ ( a + b + c ) − ( a + a + b + c + b ) . \ c = 2 * (a +b +c) - (a +a+b+c+b). c=2∗(a+b+c)−(a+a+b+c+b).
思路就是将数组中元素去重然后求和乘2减掉原数组元素之和,结果就是只出现一次的数字。
// link:https://leetcode-cn.com/problems/single-number
// author: zyoung
class Solution {
int sum(vector<int> nums) {
int result = 0;
for(int num : nums) result += num;
return result;
}
int sum(set<int> S) {
int result = 0;
for(int num : S) result += num;
return result;
}
public:
int singleNumber(vector<int>& nums) {
if(1 == (int)nums.size()) return nums[0];
set<int> S;
for(int num:nums) S.insert(num);
int sum_vector = this->sum(nums);
int sum_set = this->sum(S);
return sum_set * 2 - sum_vector;
}
};
需要注意的是,这种思路在数字比较大的情况下可能会溢出,所以不推荐使用该方法,这里仅作为set
例题展示。
好文推荐
vector用法整理
leetcode 231题解