set跟vector差不多,它跟vector的区别就是,set里面的元素是有序的且唯一
的,只要你往set里添加元素,它就会自动排序,而且,如果你添加的元素set里面本来就存在,那么这次添加操作就不执行。set类包含在头文件#included
中。
而multiset
允许元素重复,即
与multiset、map和multimap等关联容器一样,底层通过 红黑树 实现,所以通常情况下,其复杂度都可以看成是一个比较完美的 平衡二叉树看待,近似于 O ( l o g n ) O(logn) O(logn)。
复杂度
考虑到底层用红黑树实现,所以各个操作的复杂度
操作 | 复杂度 |
---|---|
插入insert() | O(logn) |
删除erase() | O(logn) |
查找find() | O(logn) |
简单示例:
#include
#include
#include
using namespace std;
template <typename T>
void showset(set<T> v) {
for (set<T>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << " "; //集合set不支持用下标直接访问元素,所以此处用迭代器
}
cout << endl;
}
int main() {
set<int> s1 = { 9,8,1,2,3,4,5,5,5,6,7,7 }; 自动排序,从小到大,剔除相同项
showset(s1);
set<string> s2{ "hello","welcome","c++","to","hello" }; //字典序排序
showset(s2);
s1.insert(9); //有这个值了,do nothing
showset(s1);
s2.insert("everyone"); //没有这个字符串,添加并且排序
showset(s2);
system("pause");
return 0;
}
输出结果:
1 2 3 4 5 6 7 8 9
c++ hello to welcome
1 2 3 4 5 6 7 8 9
c++ everyone hello to welcome
请按任意键继续. . .
set注意事项:
1)不能直接改变元素值,因为那样会打乱原本正确的顺序,要改变元素值必须先删除旧元素,则插入新元素
2) 不提供直接存取元素的任何操作函数,只能通过迭代器进行间接存取,而且从迭代器角度来看,元素值是常数
3) 元素比较动作只能用于型别相同的容器(即元素和排序准则必须相同)
操作 | 含义 |
---|---|
begin() | 返回指向第一个元素的迭代器 |
clear() | 清除所有元素 |
count() | 返回某个值元素的个数 |
empty() | 判断集合是否为空 |
end() | 返回指向最后一个元素的迭代器 |
equal_range() | 返回集合中与给定值相等的上下限的两个迭代器 |
c.erase(num) | 删除元素num或者迭代器,返回下一个元素的迭代器 |
find() | 返回一个指向被查找到元素的迭代器,若失败则返回end() |
get_allocator() | 返回集合的分配器 |
insert() | 在集合中插入元素 |
lower_bound() | 返回指向大于(或等于)某值的第一个元素的迭代器 |
key_comp() | 返回一个用于元素间值比较的函数 |
rbegin() | 返回指向集合中最后一个元素的反向迭代器 |
rend() | 返回指向集合中第一个元素的反向迭代器 |
size() | 集合中元素的数目 |
swap() | 交换两个集合变量 |
upper_bound() | 返回大于某个值元素的迭代器 |
value_comp() | 返回一个用于比较元素间的值的函数 |
示例代码:
#include
#include
#include
using namespace std;
template <typename T>
//集合的遍历
void showset(set<T> v) {
for (set<T>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << " "; //集合set不支持用下标直接访问元素,所以此处用迭代器
}
cout << endl;
}
int main() {
set<int> s1 = { 9,8,1,2,3,4,5,5,5,6,7,7 }; 自动排序,从小到大,剔除相同项
cout << "print1 s1:";
showset(s1);
set<int> s2;
//插入操作
s2.insert(5);
s2.insert(8); //第一次插入8,可以插入
s2.insert(7);
s2.insert(10);
s2.insert(9);
s2.insert(6);
s2.insert(8); //第二次插入8,重复元素,不会插入
cout << "print2 s2:";
showset(s2);
cout << "print3 s2 max num:" << *(-- s2.end()) << endl; //取出最大元素
//返回元素个数
cout << "print4 s2.size():" << s2.size() << endl;
//查找元素
printf("%d 在s2中的地址 %p:\n", *s2.find(8), s2.find(8));
//若元素不在集合中,则迭代器会越界
if (s2.find(1) == s2.end()) cout << "1 isn't in s2\n";
else cout << "1 is in s2\n";
//删除元素,若成功则返回1
if(s2.erase(6)) cout << "erase num 6 success" << endl;
else cout << "erase failed,erase num 6 not in set" << endl;
//交换集合
swap(s1, s2);
cout << "交换s1和s2后,s2:";
showset(s2);
system("pause");
return 0;
}
输出结果:
print1 s1:1 2 3 4 5 6 7 8 9
print2 s2:5 6 7 8 9 10
print3 s2 max num:10
print4 s2.size():6
8 在s2中的地址 003D30F8:
1 isn't in s2
erase num 6 success
交换s1和s2后,s2:1 2 3 4 5 6 7 8 9
请按任意键继续. . .
如果set的类型是个结构体,我们需要定义重载函数:
set 容器模版需要3个泛型参数,如下:
template ,
class Alloc = alloc>
class set {
...
};
其中,
第一个是元素类型,必选;
第二个指定元素比较方式,缺省为 Less, 即使用 < 符号比较;
第三个指定空间分配对象,一般使用默认类型。
因此:
(1) 如果第2个泛型参数你使用默认值的话,你的自定义元素类型需要重载 < 运算操作;
(2) 如果你第2个泛型参数不使用默认值的话,则比较对象必须具有 operator() 操作,即:
bool operator()(const T &a, const T &b)*
示例代码:
我们定义一个学生的结构体,包括姓名name,年龄age,性别sex等数据,且按照name为主关键字,age为次关键字进行升序排序。
#include
#include
#include
using namespace std;
struct student {
string name;
int age;
string sex;
student() {}
student(string n, int a, string s) : name(n), age(a), sex(s) {}
};
//使用结构体自定义比较规则,当然也可以使用全局函数,
//或者在结构体student中重载比较运算符
struct cmp{
bool operator()(const student& a, const student& b) const {
/*先比较名字;若名字相同,则比较年龄。小的返回true*/
return a.name < b.name || (a.name == b.name && a.age < b.age);
}
};
int main() {
set<student, cmp> stuSet;
student stu1, stu2, stu3;
stu1.name = "zhangsan";
stu1.age = 13;
stu1.sex = "male";
stu2.name = "lisi";
stu2.age = 23;
stu2.sex = "female";
stu3.name = "zhangsan";
stu3.age = 20;
stu3.sex = "female";
stuSet.insert(stu1);
stuSet.insert(stu2);
stuSet.insert(stu3);
/*构造一个测试的Student,可以看到,即使stuTemp与stu1实际上并不是同一个对象,
*但当在set中查找时,仍会查找成功。这是因为已定义的studentSortCriterion的缘故。
*/
student stuTemp;
stuTemp.name = "zhangsan";
stuTemp.age = 13;
set<student, cmp>::iterator iter;
iter = stuSet.find(stuTemp);
if (iter != stuSet.end()) {
cout << (*iter).name << endl; //输出zhangsan
}
else {
cout << "Cannot fine the student!" << endl;
}
/*我们已经定义了一个比较函数
*那么lower_bound()或是upper_bound()会根据我们写入的重载比较函数
*或是比较结构体去比较寻找
*/
iter = stuSet.upper_bound(student("zhangsan",13,"male")); //输出zhangsan 20 female
cout << iter->name << " " << iter->age << " " << iter->sex << endl;
system("pause");
return 0;
}
在数学上,集合包含交、并、补的操作,在STL中也实现了相应的操作,包含下列几个函数
操作 | 函数 |
---|---|
集合交 | set_intersection() |
集合并 | set_uinion() |
集合补 | set_difference() |
这几个函数的参数都一样,以集合的交为例:
set_union(A.begin(), A.end(), B.begin(), B.end(), inserter(C,C.begin()));
// 前四个参数依次是第一个集合A的头尾。第二个集合B的头尾
// 第五个参数是将A和B操作后结果存入C中
set_union()函数的原型如下:
template<class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator set_union(
InputIterator1_First1 ,
InputIterator1_Last1 ,
InputIterator2_First2 ,
InputIterator2_Last2 ,
OutputIterator_Result
);
实现源码如下:
template<class InputIterator1,class InputIterator2,class OutputIterator>
OutputIterator set_intersection(InputIterator1 first1,InputIterator1 last1,InputIterator2 first2,InputIterator2 last2,OutputIterator result)
{
while (first1!=last1 && first2!=last2) //若均未到达尾端,则进行以下操作
{
//在两个区间内分别移动迭代器。若二者值相同用result记录该值,移动first1,first2和result
//若first1较小,则移动first1,其他不动
//若first2较小,则移动first2,其他不动
if (*first1<*first2)
first1++;
else if (*first2<*first1)
first2++;
else
{
*result=*first1;
first1++;
first2++;
result++;
}
}
return result;
}
例题示例
UVA 12096 The SetStack Computer
题目大意
本题的集合是集合的集合,为了方便,为每一个不同的集合分配一个ID,并将其存储在一个向量中,压栈出栈用其ID代替
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ALL(x) x.begin(),x.end() // 集合的全体
#define INS(x) inserter(x,x.begin()) // 插入到集合x,返回一个迭代器,需要添加#include
typedef struct set<int> Set; // 定义一个集合类,空集表示空向量
map<Set, int> IDcache; // 将集合映射成ID
vector<Set> Setcache; // 将集合存储起来,即ID->集合
stack<int> s; // 题目中的栈,压入集合的编号
void init() {
IDcache.clear();
Setcache.clear();
while (s.size()) s.pop();
}
// 查找给定集合的ID,若不存在,则压入
int getID(Set x) {
if (IDcache.count(x)) return IDcache[x]; // 返回集合的ID
// 将新集合压入
Setcache.push_back(x);
IDcache[x] = Setcache.size() - 1;
return IDcache[x];
}
int main() {
int t; cin >> t;
int q;
string op;
while (t--) {
init();
cin >> q;
string op;
while (q--) {
cin >> op; // 读入操作
if (op[0] == 'P') s.push(getID(Set())); // 压入空集
else if (op[0] == 'D') s.push(s.top()); // 复制一份
else { // 并、交以及加和
Set ans;
Set x1 = Setcache[s.top()]; s.pop();
Set x2 = Setcache[s.top()]; s.pop();
if (op[0] == 'U') set_union(ALL(x1),ALL(x2),INS(ans));
else if(op[0] == 'I') set_intersection(ALL(x1), ALL(x2), INS(ans));
else if (op[0] == 'A') { // 加
ans = x2;
ans.insert(getID(x1));
}
s.push(getID(ans));
}
cout << Setcache[s.top()].size() << endl;
}
cout << "***\n";
}
return 0;
}
/*
2
9
PUSH
DUP
ADD
PUSH
ADD
DUP
ADD
DUP
UNION
5
PUSH
PUSH
ADD
PUSH
INTERSECT
*/
代码中用到的其他STL中的容器可以翻阅之前的博客查看,不过第一次应该也能看得懂。