关联性容器 set
引入
map和set常常用,但是不太了解,不太深入。想要深入的了解,发现网上的全是一些高级的东西,讲讲了map和set的定义,但是并没有或是很少提及用法。想看用法,但是看着冗杂,不够精简,特别是对于像我这种理解力低的渣渣,感到一脸茫然。
于是就起了自己写一篇博客的念头。
关联性容器
set和map都叫做关联性容器。
什么是关联性容器呢?
我们就需要把它和另一种容器,顺序性容器合着一起讲。
它们的区别在于:
**关联容器通过键存储和读取元素,顺序容器通过元素在容器中的位置顺序存储和访问元素。 **
也就是说,
序列式容器强调值的排序,而关联式容器则在值中选择一个值作为一个关键字,这个关键字起到对值的索引的作用,方便查找。
举个例子,穿着独一无二跑的超快鞋的小明,迟到了。
老师问:
“小明是不是又是最后一个进教室的?” 这就是顺序性容器。
“小明是不是又穿着独一无二跑的超快鞋?”这就是关联性容器。
set的定义和基本概念
来,复习高中数学必修一第一课!
(我不会告诉你我还是初二)
集合!集合!集合!
于是人们开发出了set。
set,就是集合,它主要利用了集合的互异性,也就是说,set的每个元素值唯一。
set的内部自建一颗红黑树,一种二叉平衡树。而STL相当于提供了红黑树的接口函数,让程序员的使用更加方便。
set的头文件
set需要
#include<set>
using namespace std;
这两个头文件。
准确来说,第二个不是头文件,但差不多⑧。
set的声明与构造函数
set声明的基本格式是:
set <数据类型> 名称 ;
比如:
set <int> s;
set <double> m;
set <char> w;
set的基本操作与遍历
以set <int> s;
为例。
基本操作
set的最基本的操作有下列几种:
s.begin()//返回s的迭代器
s.end()//返回s的迭代器
s.clear()//删除s中的所有的元素
s.empty()//判断s是否为空,空返回1,不空返回0
s.max_size()//返回s最多能装的元素个数
s.size()//返回s当前有多少个元素
s.find(k)//在s里找k,找到返回k的迭代器,否则返回s.end();
s.count(k)//返回s里k的个数,因为set去重,所以只会返回0或1
在以上返回迭代器的函数前加上*
,就能返回该迭代器对应的值!
//事实上,所有迭代器前面加上*
,都是它所代表的值
set与迭代器
如果想要遍历set的话,我们需要定义一个迭代器。
set <int> ::iterator iter;
因为s.begin()
和s.end()
返回的也是迭代器,所以说我们可以这样来遍历一个set:
set <int> ::iterator iter;
for(iter=s.begin();iter!=s.end();iter++)
cout<<*iter<<endl;//因为iter也是迭代器,要输出它所代表的值,需要加上‘*’号
如果你像输出set里第k个元素,那么只需要令iter=s.begin();
然后再循环k-1次iter++;
,就可以了。
举个例子:
#include<cstdio>
#include<iostream>
#include<set>
using namespace std;
int a[12]={5,4,5,8,8,3,2,9,6,1,0,7};
set<int> s;
int main()
{
for(int i=0;i<12;i++) s.insert(a[i]);
set <int> ::iterator iter;
for(iter=s.begin();iter!=s.end();iter++)
cout<<*iter<<' ';
}
我们依次往set里插入5,4,5,8,8,3,2,9,6,1,0,7
,来看看结果:
0 1 2 3 4 5 6 7 8 9
因此我们发现,set不仅自动去重,而且自动升序排序。
set的插入和删除
插入
上文已经提到过一种,除此之外,set还支持另外一种插入
s.insert(k);
在s里插入一个关键字k,自动去重,自动排序。
####s.insert(left,right);
注意:left,right是两个迭代器。
插入left和right代表的一段区间。
比如说刚才的程序就可以改为:
#include<cstdio>
#include<iostream>
#include<set>
using namespace std;
int a[12]={5,4,5,8,8,3,2,9,6,1,0,7};
set<int> s;
int main()
{
s.insert(a+0,a+12);
set <int> ::iterator iter;
cout<<"s.insert(a+0,a+12) : ";
for(iter=s.begin();iter!=s.end();iter++)
cout<<*iter<<' ';
cout<<endl<<"s.insert(a+0,a+11) : ";
s.clear();
s.insert(a+0,a+11);
for(iter=s.begin();iter!=s.end();iter++)
cout<<*iter<<' ';
}
我们插入了两次,一次s.insert(a+0,a+12)
,一次s.insert(a+0,a+11)
。来看看结果:
s.insert(a+0,a+12) : 0 1 2 3 4 5 6 7 8 9
s.insert(a+0,a+11) : 0 1 2 3 4 5 6 8 9
少了一个7!
因此我们可以知道,s.insert(left,right);
插入的区间是半开半闭区间[left,right)。
删除
set支持三种删除。
s.erase(k);
删除关键字为k的元素。
s.erase(iterator);
删除迭代器为iterator的元素。
s.erase(left,right);
删除区间**[left,right)**的元素。
####注意
注意,set的删除并不会判断删除的正确性。也就是说,就算set里没有你要删的元素,set也会去删。在删除之前一定要特判!
set的查找
介绍四种查找。
s.find(k);//返回k的迭代器,如果没有返回s.end();
s.count(k);//返回k的个数(在set中只有0或1)
s.upper_bound(k);//返回第一个大于等于k的迭代器
s.lower_bound(k);//返回最后一个大于等于k的迭代器
set的排序机制
上文提到过,set自动升序排列。这是int类,再来试试char:
#include<cstdio>
#include<iostream>
#include<set>
using namespace std;
char a[10]={'x','c','Z','P','A','l','L','k','S','S'};
set <char> s;
int main()
{
s.insert(a+0,a+10);
set <char> ::iterator iter;
for(iter=s.begin();iter!=s.end();iter++)
cout<<*iter<<" ";
}
结果:A L P S Z c k l x
。
在char里也会按照ASCII码大小升序排序。
问题来了,我如果我想用自己的方法排序呢?
不用结构体
新建一个结构体,重载 () 。
struct Comp
{
bool operator () (const another_struct &a , const another_struct &b) const //another_struct is another strcut
{
//make your own rules
}
};
要让set使用这个规则,声明时改成:set <another_struct,Comp>;
。
结构体
在结构体里重载**<**即可。
struct node
{
bool operator < (const & a) const
{
//make your own rules
}
};
multiset
STL的另一种关联性容器,它不会去重,用法与set如出一辙。