set集合容器使用一种称为红黑树(Red-Black Tree)的平衡二叉检索树的数据结构,来组织泛化的元素结构。每个节点包含一个取值红色或黑色的颜色域,以利于进行树的平衡处理。作为节点键值的元素的插入,必须确保每个子树根节点的键值大于左子树所有节点的键值,而小于右子树所有节点的键值。不会将重复的键值插入容器,也不需要指定具体的插入位置,而按元素在树中的关联关系,进行位置检索和插入,元素的删除依然。
set是一种随机存储的关联式容器,其关键词(key)也是元素(value)。set之中所有元素互不相同。set是通过二叉查找树来实现的。
关于set,必须说明的是set关联式容器。set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。应该注意的是set中数元素的值不能直接被改变。C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树,所以被STL选择作为了关联容器的内部结构。
一、为何map和set的插入删除效率比用其他序列容器高?
大部分人说,很简单,因为对于关联容器来说,不需要做内存拷贝和内存移动。说对了,确实如此。set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。结构图可能如下:
A
/ \
B C
/ \ / \
D E F G
因此插入的时候只需要稍做变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍做变换后把指向删除节点的指针指向其他节点也OK了。这里的一切操作就是指针换来换去,和内存移动没有关系。
二、 Set是一个有序的集合,所以有3点要注意:1 唯一的元素值。如果需要有多个相同的元素,请用multiset
2 元素值本身就是它的键。如果需要键值对,请用map
3 元素时刻遵循有序的规则,非排序的请用unordered_set
set与multiset:
set/multiset会根据待定的排序准则,自动将元素排序。两者不同在于前者不允许元素重复,而后者允许。
1) 不能直接改变元素值,因为那样会打乱原本正确的顺序,要改变元素值必须先删除旧元素,则插入新元素
2) 不提供直接存取元素的任何操作函数,只能通过迭代器进行间接存取,而且从迭代器角度来看,元素值是常数
3) 元素比较动作只能用于型别相同的容器(即元素和排序准则必须相同)
模板原型:
set模板原型://Key为元素(键值)类型
template <class Key, class Compare=less<Key>, class Alloc=STL_DEFAULT_ALLOCATOR(Key) >
从原型可以看出,可以看出比较函数对象及内存分配器采用的是默认参数,因此如果未指定,它们将采用系统默认方式
三、创建set对象
创建set对象,共6种方式,提示如果比较函数对象及内存分配器未出现,即表示采用的是系统默认方式。
1.创建一个空的set
set<int> s0 ;
2.创建一个带大于比较器的set, 默认是小于比较器less<int>
set<int, greater<int>> s1 ;
3.用数组初始化一个set
int a[3] = {1, 2, 3} ;
set<int> s2(a, a + 3) ;
4.用拷贝构造函数初始化set
set<int> s1 ;
set<int> s2(s1) ;
5.区间初始化
set<int> s1 ;
set<int> s2(s1.begin(), s1.end()) ;
6.创建空的set对象,元素类型是char*,
比较函数对象(即排序准则)为自定义的stress
struct strLess
{
bool operator() (const char *s1, const char *s2) const
{
return strcmp(s1, s2) < 0;
}
};
set<const char*,strLess> s2(strLess());
7.用迭代区间[&first, &last)所指的元素,及比较函数对象strLess,创建一个set对象
const char* szArray[] = {"hello", "dog", "bird" };
set<const char*, strLess> s5(szArray, szArray + 3, strLess() );
8.创建自定义类型的set:
怎样在set中放入自定义类型?两种方法:1、定义一个函数对象并在定义set的时候将其作为第二个模板参数。2、为自定义类型定义<运算符。如:
class Edge
{
public:
Edge(int u, int v): u(u), v(v){}
bool operator < (const Edge& edge) const
{
return this->u < edge.u;
} //为了方便起见设为public int u; int v;
};
class EdgeComp
{
public:
bool operator()(const Edge& left, const Edge& right)
{
return left.u < right.u;
}
};
std::set<Edge> edge_set;
Edge edge_a(0, 1);
Edge edge_b(0, 2);
edge_set.insert(edge_a);
edge_set.insert(edge_b);
std::set<Edge, EdgeComp> edge_set2;
edge_set2.insert(edge_a);
edge_set2.insert(edge_b);
四、set里元素遍历
正向遍历:
使用while
int a[3] = {1, 2, 3} ;
set<int> s(a, a + 3) ;
set<int>::const_iterator itor ;
itor = s.begin() ;
while (itor != s.end())
{
cout << *itor << endl ;
++itor ;
}
使用for
int a[3] = {1, 2, 3} ;
set<int> s(a, a + 3) ;
set<int>::const_iterator itor ;
for (itor = s.begin(); itor != s.end(); ++itor)
{
cout << *itor << endl ;
}
反向遍历:
使用while
int a[3] = {1, 2, 3} ;set<int> s(a, a + 3) ;set<int>::const_reverse_iterator ritor ;ritor = s.rbegin() ;while (ritor != s.rend()){cout << *ritor << endl ;++ritor ;}使用for
int a[3] = {1, 2, 3} ;set<int> s(a, a + 3) ;set<int>::const_reverse_iterator ritor ;for (ritor = s.rbegin(); ritor != s.rend(); ++ritor){cout << *ritor << endl ;}五、元素插入
1,插入value,返回pair配对对象,可以根据.second判断是否插入成功。(提示:value不能与set容器内元素重复)
pair<iterator, bool> insert(value)
如:
s1.insert(20);cout<<"s1.insert(20).second = "<<endl;; if (s1.insert(20).second) cout<<"Insert OK!"<<endl; else cout<<"Insert Failed!"<<endl;
2,在pos位置之前插入value,返回新元素位置,但不一定能插入成功
iterator insert(&pos, value)
3,将迭代区间[&first, &last)内所有的元素,插入到set容器
void insert[&first, &last)
如:批量插入实例
插入整个数组
int a[3] = {1, 2, 3} ;
set<int> s ;
s.insert(a, a + 3) ;
插入其他set值
int a[3]={1,2,3};
set<int> s(a,a+3);
set<int> s1;
s1.insert(s.begin(),s.end());
六、元素删除
1. size_type erase(value)
删除set容器内元素值为value的所有元素,返回移除的元素个数.对于set容器来说,此函数总是返回1,因为set容器不会出现重复的元素值(键值)
2.void erase(&pos)
移除pos位置上的元素,无返回值
3.void erase(&first, &last)
移除迭代区间[&first, &last)内的元素,无返回值
4,void clear(), 移除set容器内所有元素
例子:
set<int> s ;
for (int i = 1; i <= 5; ++i)
s.insert(i) ;
set<int>::const_iterator citor ;
citor = s.begin() ;
++citor ; // citor now point to 2
// 删除单个元素
s.erase(citor) ; // erase 2 ;
//批量删除
citor = s.find(3) ; // itor now point to 3
s.erase(citor, s.end()) ; // erase 3, 4, 5
//删除所有元素
s.erase(s.begin(), s.end()) ;// erase all elements, same as s.clear()
七、元素查找
1.count(value) 返回set对象内元素值为value的元素个数
set<int> s ;
for (int i = 1; i <= 5; ++i)
s.insert(i) ;
set<int>::iterator itor ;
if(s.count(4) == 1) // return 1 if s contains 4, else 0
cout << "s contains 4" ;
else
cout << "s does not contains 4" ;
2.iterator find(value) 返回value所在位置,找不到value将返回end()
set<int> s ;
for (int i = 1; i <= 5; ++i)
s.insert(i) ;
set<int>::iterator itor ;
itor = s.find(4) ;
if(itor != s.end()) // itor point to s.end() if not found
cout << "found" ;
else
cout << "not found" ;
3.lower_bound(value),upper_bound(value), equal_range(value) 略,参见map里的用法
八、其他常用函数
元素判空:s1.empty()
元素个数:s1.size()
交换:s1.swap(s2)九、小结
set 集合容器是一个有序关联容器,所包含的元素必须是唯一的,不能插入重复的元素(如果重复插入了,程序会自动舍弃重复的元素)。由于使用了红黑树这种特殊的平衡二叉检索树管理元素数据,具有高效的元素插入、删除和检索。元素检索的算法时间复杂度 【log2 N】,也即 2 的 N 次方个元素的检索,只需要 N 此比较操作。
pair ,Functor 是比较高级的用法,必须掌握。
set 缺点:不能重复插入相同元素,删除、插入 操作频繁的时候,set 就不适合。
set 优点:对于元素的检索,非常快