STL中的set容器的一点总结2

http://blog.csdn.net/sunshinewave/article/details/8068326

1.关于set

C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入、排序、删除、查找等。让用户在STL使用过程中,并不会感到陌生。

关于set,必须说明的是set关联式容器。set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。应该注意的是set中数元素的值不能直接被改变。C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树,所以被STL选择作为了关联容器的内部结构。

 关于set有下面几个问题:

(1)为何map和set的插入删除效率比用其他序列容器高?

大部分人说,很简单,因为对于关联容器来说,不需要做内存拷贝和内存移动。说对了,确实如此。set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。结构图可能如下:

  A
   /
  B C
 / /
  D E F G

因此插入的时候只需要稍做变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍做变换后把指向删除节点的指针指向其他节点也OK了。这里的一切操作就是指针换来换去,和内存移动没有关系。

(2)为何每次insert之后,以前保存的iterator不会失效?

iterator这里就相当于指向节点的指针,内存没有变,指向内存的指针怎么会失效呢(当然被删除的那个元素本身已经失效了)。相对于vector来说,每一次删除和插入,指针都有可能失效,调用push_back在尾部插入也是如此。因为为了保证内部数据的连续存放,iterator指向的那块内存在删除和插入过程中可能已经被其他内存覆盖或者内存已经被释放了。即使时push_back的时候,容器内部空间可能不够,需要一块新的更大的内存,只有把以前的内存释放,申请新的更大的内存,复制已有的数据元素到新的内存,最后把需要插入的元素放到最后,那么以前的内存指针自然就不可用了。特别时在和find等算法在一起使用的时候,牢记这个原则:不要使用过期的iterator。

(3)当数据元素增多时,set的插入和搜索速度变化如何?

如果你知道log2的关系你应该就彻底了解这个答案。在set中查找是使用二分查找,也就是说,如果有16个元素,最多需要比较4次就能找到结果,有32个元素,最多比较5次。那么有10000个呢?最多比较的次数为log10000,最多为14次,如果是20000个元素呢?最多不过15次。看见了吧,当数据量增大一倍的时候,搜索次数只不过多了1次,多了1/14的搜索时间而已。你明白这个道理后,就可以安心往里面放入元素了。

2.set中常用的方法


begin()        ,返回set容器的第一个元素

end()      ,返回set容器的最后一个元素

clear()          ,删除set容器中的所有的元素

empty()    ,判断set容器是否为空

max_size()   ,返回set容器可能包含的元素最大个数

size()      ,返回当前set容器中的元素个数

rbegin     ,返回的值和end()相同

rend()     ,返回的值和rbegin()相同

写一个程序练一练这几个简单操作吧:

1 #include <iostream>
 2 #include <set>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     set<int> s;
 9     s.insert(1);
10     s.insert(2);
11     s.insert(3);
12     s.insert(1);
13     cout<<"set 的 size 值为 :"<<s.size()<<endl;
14     cout<<"set 的 maxsize的值为 :"<<s.max_size()<<endl;
15     cout<<"set 中的第一个元素是 :"<<*s.begin()<<endl;
16     cout<<"set 中的最后一个元素是:"<<*s.end()<<endl;
17     s.clear();
18     if(s.empty())
19     {
20         cout<<"set 为空 !!!"<<endl;
21     }
22     cout<<"set 的 size 值为 :"<<s.size()<<endl;
23     cout<<"set 的 maxsize的值为 :"<<s.max_size()<<endl;
24     return 0;
25 }
复制代码

运行结果:

小结:插入3之后虽然插入了一个1,但是我们发现set中最后一个值仍然是3哈,这就是set 。还要注意begin() 和 end()函数是不检查set是否为空的,使用前最好使用empty()检验一下set是否为空.


count() 用来查找set中某个某个键值出现的次数。这个函数在set并不是很实用,因为一个键值在set只可能出现0或1次,这样就变成了判断某一键值是否在set出现过了。

示例代码:View Code

 #include <iostream>
 2 #include <set>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     set<int> s;
 9     s.insert(1);
10     s.insert(2);
11     s.insert(3);
12     s.insert(1);
13     cout<<"set 中 1 出现的次数是 :"<<s.count(1)<<endl;
14     cout<<"set 中 4 出现的次数是 :"<<s.count(4)<<endl;
15     return 0;
16 }
复制代码

运行结果:

equal_range() ,返回一对定位器,分别表示第一个大于或等于给定关键值的元素和 第一个大于给定关键值的元素,这个返回值是一个pair类型,如果这一对定位器中哪个返回失败,就会等于end()的值。具体这个有什么用途我还没遇到过~~~

示例代码:

 #include <iostream>
 2 #include <set>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     set<int> s;
 9     set<int>::iterator iter;
10     for(int i = 1 ; i <= 5; ++i)
11     {
12         s.insert(i);
13     }
14     for(iter = s.begin() ; iter != s.end() ; ++iter)
15     {
16         cout<<*iter<<" ";
17     }
18     cout<<endl;
19     pair<set<int>::const_iterator,set<int>::const_iterator> pr;
20     pr = s.equal_range(3);
21     cout<<"第一个大于等于 3 的数是 :"<<*pr.first<<endl;
22     cout<<"第一个大于 3的数是 : "<<*pr.second<<endl;
23     return 0;
24 }
复制代码

运行结果:


erase(iterator)  ,删除定位器iterator指向的值

erase(first,second),删除定位器first和second之间的值

erase(key_value),删除键值key_value的值

看看程序吧:

#include <iostream>
 2 #include <set>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     set<int> s;
 9     set<int>::const_iterator iter;
10     set<int>::iterator first;
11     set<int>::iterator second;
12     for(int i = 1 ; i <= 10 ; ++i)
13     {
14         s.insert(i);
15     }
16     //第一种删除
17     s.erase(s.begin());
18     //第二种删除
19     first = s.begin();
20     second = s.begin();
21     second++;
22     second++;
23     s.erase(first,second);
24     //第三种删除
25     s.erase(8);
26     cout<<"删除后 set 中元素是 :";
27     for(iter = s.begin() ; iter != s.end() ; ++iter)
28     {
29         cout<<*iter<<" ";
30     }
31     cout<<endl;
32     return 0;
33 }
复制代码

运行结果:

小结:set中的删除操作是不进行任何的错误检查的,比如定位器的是否合法等等,所以用的时候自己一定要注意。


find()  ,返回给定值值得定位器,如果没找到则返回end()。

示例代码:

 #include <iostream>
 2 #include <set>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     int a[] = {1,2,3};
 9     set<int> s(a,a+3);
10     set<int>::iterator iter;
11     if((iter = s.find(2)) != s.end())
12     {
13         cout<<*iter<<endl;
14     }
15     return 0;
16 }
复制代码

insert(key_value); 将key_value插入到set中 ,返回值是pair<set<int>::iterator,bool>,bool标志着插入是否成功,而iterator代表插入的位置,若key_value已经在set中,则iterator表示的key_value在set中的位置。

inset(first,second);将定位器first到second之间的元素插入到set中,返回值是void.

示例代码:

#include <iostream>
 2 #include <set>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     int a[] = {1,2,3};
 9     set<int> s;
10     set<int>::iterator iter;
11     s.insert(a,a+3);
12     for(iter = s.begin() ; iter != s.end() ; ++iter)
13     {
14         cout<<*iter<<" ";
15     }
16     cout<<endl;
17     pair<set<int>::iterator,bool> pr;
18     pr = s.insert(5);
19     if(pr.second)
20     {
21         cout<<*pr.first<<endl;
22     }
23     return 0;
24 }
复制代码

运行结果:


lower_bound(key_value) ,返回第一个大于等于key_value的定位器

upper_bound(key_value),返回最后一个大于等于key_value的定位器

示例代码:

 #include <iostream>
 2 #include <set>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     set<int> s;
 9     s.insert(1);
10     s.insert(3);
11     s.insert(4);
12     cout<<*s.lower_bound(2)<<endl;
13     cout<<*s.lower_bound(3)<<endl;
14     cout<<*s.upper_bound(3)<<endl;
15     return 0;
16 }
 #include <iostream>
 2 #include <set>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     set<int> s;
 9     s.insert(1);
10     s.insert(3);
11     s.insert(4);
12     cout<<*s.lower_bound(2)<<endl;
13     cout<<*s.lower_bound(3)<<endl;
14     cout<<*s.upper_bound(3)<<endl;
15     return 0;
16 }
复制代码

运行结果:

http://www.cnblogs.com/wonderKK/archive/2012/04/10/2441379.html

set是STL中一种标准关联容器(vector,list,string,deque都是序列容器,而set,multiset,map,multimap是标准关联容器),它底层使用平衡的搜索树——红黑树实现,插入删除操作时仅仅需要指针操作节点即可完成,不涉及到内存移动和拷贝,所以效率比较高。set,顾名思义是“集合”的意思,在set中元素都是唯一的,而且默认情况下会对元素自动进行升序排列,支持集合的交(set_intersection),差(set_difference) 并(set_union),对称差(set_symmetric_difference) 等一些集合上的操作,如果需要集合中的元素允许重复那么可以使用multiset
#include<set>
#include<iterator>
#include<iostream>
using namespace std;
int main()
{
set<int>eg1;
//插入
eg1.insert(1);
eg1.insert(100);
eg1.insert(5);
eg1.insert(1);//元素1因为已经存在所以set中不会再次插入1
eg1.insert(10);
eg1.insert(9);
//遍历set,可以发现元素是有序的
set<int>::iterator set_iter=eg1.begin();
cout<<"Set named eg1:"<<endl;
for(;set_iter!=eg1.end();set_iter++) cout<<*set_iter<<" ";
cout<<endl;
//使用size()函数可以获得当前元素个数
cout<<"Now there are "<<eg1.size()<<" elements in the set eg1"<<endl;
if(eg1.find(200)==eg1.end())//find()函数可以查找元素是否存在
   cout<<"200 isn't in the set eg1"<<endl;

set<int>eg2;
for(int i=6;i<15;i++)
eg2.insert(i);
cout<<"Set named eg2:"<<endl;
for(set_iter=eg2.begin();set_iter!=eg2.end();set_iter++)
   cout<<*set_iter<<" ";
cout<<endl;
//获得两个set的并
set<int>eg3;
cout<<"Union:";
set_union(eg1.begin(),eg1.end(),eg2.begin(),eg2.end(),insert_iterator<set<int> >(eg3,eg3.begin()));//注意第五个参数的形式
copy(eg3.begin(),eg3.end(),ostream_iterator<int>(cout," "));
cout<<endl;
//获得两个set的交,注意进行集合操作之前接收结果的set要调用clear()函数清空一下
eg3.clear();
set_intersection(eg1.begin(),eg1.end(),eg2.begin(),eg2.end(),insert_iterator<set<int> >(eg3,eg3.begin()));
cout<<"Intersection:";
copy(eg3.begin(),eg3.end(),ostream_iterator<int>(cout," "));
cout<<endl;
//获得两个set的差
eg3.clear();
set_difference(eg1.begin(),eg1.end(),eg2.begin(),eg2.end(),insert_iterator<set<int> >(eg3,eg3.begin()));
cout<<"Difference:";
copy(eg3.begin(),eg3.end(),ostream_iterator<int>(cout," "));
cout<<endl;
//获得两个set的对称差,也就是假设两个集合分别为A和B那么对称差为AUB-A∩B
   eg3.clear();
   set_symmetric_difference(eg1.begin(),eg1.end(),eg2.begin(),eg2.end(),insert_iterator<set<int> >(eg3,eg3.begin()));
   copy(eg3.begin(),eg3.end(),ostream_iterator<int>(cout," "));
   cout<<endl;

return 0;
}

set会对元素进行排序,那么问题也就出现了排序的规则是怎样的呢?上面的示例代码我们发现对int型的元素可以自动判断大小顺序,但是对char*就不会自动用strcmp进行判断了,更别说是用户自定义的类型了,事实上set的标准形式是set<Key, Compare, Alloc>,

参数描述默认值
Key 集合的关键字和值的类型  
Compare 关键字比较函数,它的参数类型key参数指定的类型,如果第一个参数小于第二个参数则返回true,否则返回false less<Key>
Alloc set的分配器,用于内部内存管理 alloc

下面给出一个关键字类型为char*的示例代码

#include<iostream>
#include<iterator>
#include<set>
using namespace std;
struct ltstr
{
bool operator() (const char* s1, const char* s2) const
{
   return strcmp(s1, s2) < 0;
}
};

int main()
{
const int N = 6;
const char* a[N] = {"isomer", "ephemeral", "prosaic", 
   "nugatory", "artichoke", "serif"};
const char* b[N] = {"flat", "this", "artichoke",
   "frigate", "prosaic", "isomer"};

set<const char*,ltstr> A(a, a + N);
set<const char*,ltstr> B(b, b + N);
set<const char*,ltstr> C;

cout << "Set A: ";
//copy(A.begin(), A.end(), ostream_iterator<const char*>(cout, " "));
set<const char*,ltstr>::iterator itr;
for(itr=A.begin();itr!=A.end();itr++) cout<<*itr<<" ";
cout << endl;
cout << "Set B: ";
copy(B.begin(), B.end(), ostream_iterator<const char*>(cout, " "));   
cout << endl;

cout << "Union: ";
set_union(A.begin(), A.end(), B.begin(), B.end(),
    ostream_iterator<const char*>(cout, " "),
    ltstr());   
cout << endl;

cout << "Intersection: ";
set_intersection(A.begin(), A.end(), B.begin(),B.end(),ostream_iterator<const char*>(cout," "),ltstr());
cout<<endl;
set_difference(A.begin(), A.end(), B.begin(), B.end(),inserter(C, C.begin()),ltstr());
cout << "Set C (difference of A and B): ";
copy(C.begin(), C.end(), ostream_iterator<const char*>(cout, " "));
cout <<endl;
return 0;
}

其中的ltstr也可以这样定义
class ltstr
{
        public:
        bool operator() (const char* s1,const char*s2)const
        {
                return strcmp(s1,s2)<0;
        }
};

更加通用的应用方式那就是数据类型也是由用户自定义的类来替代,比较的函数自定义,甚至可以加上二级比较,比如首先按照总分数排序,对于分数相同的按照id排序,下面是示例代码

#include<set>
#include<iostream>
using namespace std;
struct
{
                int id;
                int score;
                string name;
};
struct compare
{
        bool operator()(const Entity& e1,const Entity& e2)const   {
                if(e1.score<e2.score) return true;
                else
                        if(e1.score==e2.score)
                                if(e1.id<e2.id) return true;

                return false;
        }
};

int main()
{
        set<Entity,compare>s_test;
        Entity a,b,c;
        a.id=123;a.score=90;a.name="bill";
        b.id=121;b.score=85;b.name="mary";
        c.id=130;c.score=85;c.name="jerry";
        s_test.insert(a);s_test.insert(b);s_test.insert(c);
        set<Entity,compare>::iterator itr;
        cout<<"Score List(ordered by score): ";
        for(itr=s_test.begin();itr!=s_test.end();itr++)
                cout<<itr->id<<"---"<<itr->name<<"---"<<itr->score<<endl;
        return 0;
}

如何判断重复呢,比如id相等的不对,名字相同的不可以??

struct compare
{
bool operator()(const Entity& e1,const Entity& e2)const {
if(strcmp(e1.name.c_str(), e2.name.c_str())== 0) return false;
if(e1.id == e2.id) return false;
if(e1.score<e2.score) return true;
else
if(e1.score==e2.score)
if(e1.id<e2.id) return true;

return false;
}
};

int _tmain(int argc, _TCHAR* argv[])
{
set<Entity,compare>s_test;
Entity a,b,c, d, e;
a.id=123;a.score=90;a.name="bill";
b.id=121;b.score=85;b.name="mary";
c.id=130;c.score=85;c.name="jerry";
d.id=130;c.score=90;d.name="jerryf";
d.id=132;c.score=90;d.name="mary";
s_test.insert(a);s_test.insert(b);s_test.insert(c);s_test.insert(d);
set<Entity,compare>::iterator itr;
cout<<"Score List(ordered by score): ";
for(itr=s_test.begin();itr!=s_test.end();itr++)
cout<<itr->id<<"---"<<itr->name<<"---"<<itr->score<<endl;
return 0;
}

http://www.cnblogs.com/music-liang/archive/2013/04/05/3000845.html

http://www.cnblogs.com/music-liang/archive/2013/04/05/3000845.html

set集合容器

set集合容器使用一种称为红黑树(Red-Black Tree) 的平衡二叉检索树的数据结构,来组织泛化的元素数据。每个节点包含一个取值红色或黑色的颜色域,以利于进行树的平衡处理。作为节点键值的元素的插入,必须确保每个子树根节点的键值大于左子树所有节点的键值,而小于右子树所有节点的键值。不会将重复的键值插入容器,也不需要指定具体的插入位置,而按元素在树中的关联关系,进行位置检索和插入,元素的删除亦然。
    元素数据的检索,使用的是二叉检索树的中序遍历算法,检索的效率高于 vector 、 deque 和 lsit 等容器。由于采用中序遍历算法可将二叉检索树的键值,由小到大排列遍历出来,因此 set 集合容器蕴含了元素间的有序性。
    作为一种关联容器,set 集合容器实现了 Unique Sorted Associative Container 和 Simple Associative Container 概念的函数定义要求。以熟悉的函数形式提供了元素插入、删除 和检索的功能,封装了二叉树的复杂操作。
    set 集合容器的C++标准头文件为 set ,需要先用宏语句 "#include <set>" 包含进来方可使用


创建 set 对象
为了管理 set 的二叉树的链表数据,先要利用 set 容器的构造函数,创建一个 set 对象。
1.    set()
    用默认的 less<T> 函数对象和内存分配器,创建一个没有任何数据元素的 set 对象,例如,下面一行代码创建了一个空的 set 对象 s ,元素类型为 int
    set<int> s;

2.    set(const key_compare& comp)
    指定一个比较函数对象comp 来创建 set 对象,内存分配器为默认值。
    例如,下面的的代码使用自定义的函数对象 strLess ,创建一个 set 容器对象 s 。
    // 定义字符串比较函数对象 strLess
    struct  strLess
    {
        bool  operator()(const char* s1, const char* s2)  const
        {
            return  strcmp(s1, s2) < 0;
        }
    };
    // 创建 set 容器对象 s    
    set<const char*, strLess>  s(strLess());  
    // 将1和2对比起来看。    1.是说,创建的set容器对象,默认是从小到大排序的。。。2.是说,可以自己写一个结构体,在创建的时候传入这个结构体,让set 容器元素的排序,按照我们定义的方式来进行。。 。


3.    set(const set&)
    set拷贝构造函数,通过红黑树的拷贝构造函数,实现两个set容器的元素、头节点和节点个数的拷贝。
    下面的代码,利用 set 容器对象 s1 ,拷贝生成 set 容器对象 s2。
    // set<int>  s1;
    set<int>  s2(s1);

4.    set(InputIterator first, InputIterator last)
    用迭代器区间 [first, last) 所指的元素,创建一个 set 对象。
    例如,下面代码将数组 iArray 的元素插入到 set 容器对象 s 的红黑树中。
    int iArray[] = {13, 32, 19};
    set<int>  s(iArray, iArray + 3);

5.    set(InputIterator first, InputIterator last, const key_compare& comp)
    用迭代器区间 [first, last) 所指的元素和 comp 函数对象,创建一个 set 对象。
    例如,下面的代码利用上面定义的 strLess 函数对象和数组 szArray ,创建 set 对象 s 。
    const  char*  szArray = {"Hello", "dog", "bird"};
    set<const char*, strLess >   s(szArray, szArray + 3, strLess());




元素的插入
set 并没有固定的所谓尾部插入 push_back 函数,元素的插入一般使用 insert 进行动态检索插入。
如下的 3 个 insert 函数,或将一个元素插入 set 容器,或将一个迭代器区间的元素加以插入。
1.    pair<iterator, bool>  insert(const value_type& v)
    将元素 v 插入 set 容器,要求 v 值不与 set 容器的任何元素重复,否则插入失败。返回一个 pair 配对对象,提供所插入元素的迭代器位置和 true/false 插入成功标志
    
2.    iterator insert(iteraotr position, const value_type& v)
    将元素 v 插入 set 容器,参数 position 只是提示可在 position 位置之前插入 v ,所返回的插入位置视实际情况而定,不一定能在 position 位置前插入。
    
3.    void insert(InputIterator first, InputIterator last)
    将某迭代器区间 [first, last) 所指的数据作为元素,插入到 set 容器。
    如果希望提供一个是否插入成功的信息,可以使用返回 pair 对象的 insert 函数进行插入,如下面的代码所示。
    set<int>  sInt;
    sInt.insert(10);
    pair<set<int>::iterator, bool>  p = sInt.insert(19);
    if(p.second)
        cout<<"插入新元素"<<*(p.first) << endl;
    else
        cout<<"已存在该元素,不重复插入"<<endl;




        
        
元素的删除
与插入一样,set 容器也具有高效的红黑树元素的删除处理,并自动重新调整内部的红黑树平衡。
如下的删除函数,可删除某个迭代器位置上的元素、等于某键值的元素、一个区间上的元素和容器的所有元素。
1.    void erase(iterator position)
    删除 position 所指的元素

2.    size_type erase(const key_type& k)
    删除等于键值 k 的那个元素,对于 set 容器来说,此函数总是返回值1,因为 set 容器不会出现重复的元素值(键值)
    
3.    void erase(iterator first, iterator last)
    删除 set 迭代器区间 [first, last) 上的所有元素
    
4.    void clear()
    删除所有元素,但不会删除内部红黑树的头节点

 1 /*        解释:
 2     利用【前向】迭代器进行访问,自动将这些整数【由小到大】打印出来
 3 */
 4 
 5 -------------------------------------------------------- 遍历 set 容器元素
 6 #include <set>
 7 #include <iostream>
 8 using namespace std;
 9 int main()
10 {
11     // 注意,我插入的元素是乱序、有重复的
12     set<int> sInt;
13     sInt.insert(10);
14     sInt.insert(15);
15     sInt.insert(11);
16     sInt.insert(17);
17     sInt.insert(13);
18     sInt.insert(19);
19     sInt.insert(19);  // 重复插入,被 set 容器舍弃
20     // 打印二叉树的数据
21     set<int>::iterator i, iend;
22     iend = sInt.end();
23     for (i=sInt.begin(); i!=iend; ++i)
24         cout << *i << ' ';
25     cout << endl;
26 
27     return 0;
28 }
复制代码
*        解释:
 2     利用 set 容器定义的【反向迭代器】 reverse_iterator 和 const_reverse_iterator ,可实现红黑树的【逆中序遍历】,从而将元素【由大到小】遍历出来。反向遍历,通常需要用到 rebegin 和 rend 函数,给出反向遍历的开始位置和结束位置。
 3 */
 4 
 5 -------------------------------------------------------- 反向遍历 set 容器元素
 6 #include <set>
 7 #include <iostream>
 8 using namespace std;
 9 int main()
10 {
11     // 注意,我插入的元素是乱序、有重复的
12     set<int> sInt;
13     sInt.insert(10);
14     sInt.insert(15);
15     sInt.insert(11);
16     sInt.insert(17);
17     sInt.insert(12);
18     sInt.insert(18);
19     sInt.insert(18);
20     sInt.insert(18);
21     // 反向 打印二叉树的数据
22     set<int>::reverse_iterator ri, riend;
23     riend = sInt.rend();
24     for (ri=sInt.rbegin(); ri!=riend; ++ri)
25         cout << *ri << ' ';
26     cout << endl;
27 
28     return 0;
29 }
复制代码
/*        解释:
 2     set容器提供了一个应用红黑树进行搜索的函数 find ,返回的迭代器值位搜索到的元素位置,如果元素不存在,则返回一个 end 结束元素的位置。
 3 */
 4 
 5 -------------------------------------------------------- set 容器元素的搜索
 6 #include <set>
 7 #include <iostream>
 8 using namespace std;
 9 int main()
10 {
11     set<int> sInt;
12     sInt.insert(10);
13     sInt.insert(15);
14     sInt.insert(11);
15     sInt.insert(17);
16     sInt.insert(13);
17     sInt.insert(19);
18     sInt.insert(1);
19 
20     // 搜索元素 13
21     int v = 13;
22     set<int>::iterator i_v = sInt.find(v);
23     cout << *i_v << endl;
24 
25     // 搜索不存在的元素 100
26     v = 100;
27     i_v = sInt.find(v);
28     if (i_v != sInt.end())
29         cout << *i_v << endl;
30 
31     return 0;
32 }
复制代码
/*        其他常用函数
 2 empty()    
 3 size()
 4 swap()
 5 lower_bound()    下确界函数,返回第一个 > elem 元素的迭代器
 6 upper_bound()    上确界函数,返回第一个 > elem 元素的迭代器
 7 equal_range()  返回容器中与elem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。以上函数返回两个迭代器,而这两个迭代器被封装在pair中。以下讲解pair的含义与使用方法。
 8 */
 9 
10 -------------------------------------------------------- set 的 empty 和 size 函数
11 #include <set>
12 #include <iostream>
13 using namespace std;
14 int main()
15 {
16     set<int> sInt;
17     sInt.insert(5);
18     sInt.insert(6);
19     sInt.insert(7);
20     sInt.insert(8);
21     sInt.insert(4);
22 
23     // 打印元素个数
24     if (sInt.empty())
25         cout << "set 容器为空" << endl;
26     else
27         cout << "set 容器元素个数为:" << sInt.size() << endl;
28 
29     return 0;
30 }
复制代码
 ------------------------------   浅谈一下 pair
 2 (在关于 map 容器的笔记中,会更多地用到 pair. pair 很强大的,这里只是小试牛刀而已)
 3 
 4 pair译为对组,可以将两个值视为一个单元。
 5 pair<T1,T2>存放的两个值的类型,可以不一样,如T1为int,T2为float。T1,T2也可以是自定义类型。
 6 pair.first是pair里面的第一个值,是T1类型。
 7 pair.second是pair里面的第二个值,是T2类型
 8 
 9 // pair 和 equal_range 的配合使用 
10 例如 set<int> setInt;
11 ...  //往setInt容器插入元素1,3,5,7,9
12 pair< set<int>::iterator , set<int>::iterator > pairIt = setInt.equal_range(5);
13 set<int>::iterator itBeg = pairIt.first;
14 set<int>::iterator itEnd = pairIt.second;
15 //此时 *itBeg==5  而  *itEnd == 7
复制代码
 -------------------------  浅谈一下 functor
2 尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。
3 functor,翻译成函数对象,伪函数,算符,是重载了“()”操作符的普通类对象。从语法上讲,它与普通函数行为类似。

// 题目:学生包含学号,姓名属性,现要求任意插入几个学生对象到set容器中,使得容器中的学生按学号的升序排序。
 2 
 3 #pragma warning(disable:4786)
 4 #include <set>
 5 #include <iostream>
 6 using namespace std;
 7 // 学生类
 8 class CStudent
 9 {
10 public:
11     CStudent(int nID, string strName)
12     {
13         m_nID = nID;
14         m_strName = strName; 
15     }
16 
17 public:
18     int m_nID;
19     string m_strName;
20 };
21 
22 // 函数对象 , 多看几遍,找一些相关的例子,就能理解了
23 struct studentFunctor
24 {
25     bool operator() (const CStudent &stu1, const CStudent &stu2)
26     {
27         return (stu1.m_nID < stu2.m_nID);
28     }
29 };
30 
31 int main()
32 {
33     set<CStudent, studentFunctor> setStu;
34     setStu.insert(CStudent(3, "小张"));
35     setStu.insert(CStudent(1, "小李"));
36     setStu.insert(CStudent(5, "小王"));
37     setStu.insert(CStudent(7, "小军"));
38 
39     set<CStudent, studentFunctor>::iterator it,itend;
40     itend = setStu.end();
41     // 按 ID 顺序,从小到大排列
42     for (it=setStu.begin(); it!=itend; ++it)
43     {
44         // 字符串,用 c_str() 函数来显示(string容器会讨论)
45         cout << it->m_strName.c_str() << "   ";
46     }
47 
48     return 0;
49 }

STL学习笔记-- set

set集合容器

    set集合容器使用一种称为红黑树(Red-Black Tree) 的平衡二叉检索树的数据结构,来组织泛化的元素数据。每个节点包含一个取值红色或黑色的颜色域,以利于进行树的平衡处理。作为节点键值的元素的插入,必须确保每个子树根节点的键值大于左子树所有节点的键值,而小于右子树所有节点的键值。不会将重复的键值插入容器,也不需要指定具体的插入位置,而按元素在树中的关联关系,进行位置检索和插入,元素的删除亦然。
    元素数据的检索,使用的是二叉检索树的中序遍历算法,检索的效率高于 vector 、 deque 和 lsit 等容器。由于采用中序遍历算法可将二叉检索树的键值,由小到大排列遍历出来,因此 set 集合容器蕴含了元素间的有序性。
    作为一种关联容器,set 集合容器实现了 Unique Sorted Associative Container 和 Simple Associative Container 概念的函数定义要求。以熟悉的函数形式提供了元素插入、删除 和检索的功能,封装了二叉树的复杂操作。
    set 集合容器的C++标准头文件为 set ,需要先用宏语句 "#include <set>" 包含进来方可使用


创建 set 对象
为了管理 set 的二叉树的链表数据,先要利用 set 容器的构造函数,创建一个 set 对象。
1.    set()
    用默认的 less<T> 函数对象和内存分配器,创建一个没有任何数据元素的 set 对象,例如,下面一行代码创建了一个空的 set 对象 s ,元素类型为 int
    set<int> s;

2.    set(const key_compare& comp)
    指定一个比较函数对象comp 来创建 set 对象,内存分配器为默认值。
    例如,下面的的代码使用自定义的函数对象 strLess ,创建一个 set 容器对象 s 。
    // 定义字符串比较函数对象 strLess
    struct  strLess
    {
        bool  operator()(const char* s1, const char* s2)  const
        {
            return  strcmp(s1, s2) < 0;
        }
    };
    // 创建 set 容器对象 s    
    set<const char*, strLess>  s(strLess());  
    // 将1和2对比起来看。    1.是说,创建的set容器对象,默认是从小到大排序的。。。2.是说,可以自己写一个结构体,在创建的时候传入这个结构体,让set 容器元素的排序,按照我们定义的方式来进行。。 。


3.    set(const set&)
    set拷贝构造函数,通过红黑树的拷贝构造函数,实现两个set容器的元素、头节点和节点个数的拷贝。
    下面的代码,利用 set 容器对象 s1 ,拷贝生成 set 容器对象 s2。
    // set<int>  s1;
    set<int>  s2(s1);

4.    set(InputIterator first, InputIterator last)
    用迭代器区间 [first, last) 所指的元素,创建一个 set 对象。
    例如,下面代码将数组 iArray 的元素插入到 set 容器对象 s 的红黑树中。
    int iArray[] = {13, 32, 19};
    set<int>  s(iArray, iArray + 3);

5.    set(InputIterator first, InputIterator last, const key_compare& comp)
    用迭代器区间 [first, last) 所指的元素和 comp 函数对象,创建一个 set 对象。
    例如,下面的代码利用上面定义的 strLess 函数对象和数组 szArray ,创建 set 对象 s 。
    const  char*  szArray = {"Hello", "dog", "bird"};
    set<const char*, strLess >   s(szArray, szArray + 3, strLess());




元素的插入
set 并没有固定的所谓尾部插入 push_back 函数,元素的插入一般使用 insert 进行动态检索插入。
如下的 3 个 insert 函数,或将一个元素插入 set 容器,或将一个迭代器区间的元素加以插入。
1.    pair<iterator, bool>  insert(const value_type& v)
    将元素 v 插入 set 容器,要求 v 值不与 set 容器的任何元素重复,否则插入失败。返回一个 pair 配对对象,提供所插入元素的迭代器位置和 true/false 插入成功标志
    
2.    iterator insert(iteraotr position, const value_type& v)
    将元素 v 插入 set 容器,参数 position 只是提示可在 position 位置之前插入 v ,所返回的插入位置视实际情况而定,不一定能在 position 位置前插入。
    
3.    void insert(InputIterator first, InputIterator last)
    将某迭代器区间 [first, last) 所指的数据作为元素,插入到 set 容器。
    如果希望提供一个是否插入成功的信息,可以使用返回 pair 对象的 insert 函数进行插入,如下面的代码所示。
    set<int>  sInt;
    sInt.insert(10);
    pair<set<int>::iterator, bool>  p = sInt.insert(19);
    if(p.second)
        cout<<"插入新元素"<<*(p.first) << endl;
    else
        cout<<"已存在该元素,不重复插入"<<endl;




        
        
元素的删除
与插入一样,set 容器也具有高效的红黑树元素的删除处理,并自动重新调整内部的红黑树平衡。
如下的删除函数,可删除某个迭代器位置上的元素、等于某键值的元素、一个区间上的元素和容器的所有元素。
1.    void erase(iterator position)
    删除 position 所指的元素

2.    size_type erase(const key_type& k)
    删除等于键值 k 的那个元素,对于 set 容器来说,此函数总是返回值1,因为 set 容器不会出现重复的元素值(键值)
    
3.    void erase(iterator first, iterator last)
    删除 set 迭代器区间 [first, last) 上的所有元素
    
4.    void clear()
    删除所有元素,但不会删除内部红黑树的头节点

复制代码
 1 /*        解释:
 2     利用【前向】迭代器进行访问,自动将这些整数【由小到大】打印出来
 3 */
 4 
 5 -------------------------------------------------------- 遍历 set 容器元素
 6 #include <set>
 7 #include <iostream>
 8 using namespace std;
 9 int main()
10 {
11     // 注意,我插入的元素是乱序、有重复的
12     set<int> sInt;
13     sInt.insert(10);
14     sInt.insert(15);
15     sInt.insert(11);
16     sInt.insert(17);
17     sInt.insert(13);
18     sInt.insert(19);
19     sInt.insert(19);  // 重复插入,被 set 容器舍弃
20     // 打印二叉树的数据
21     set<int>::iterator i, iend;
22     iend = sInt.end();
23     for (i=sInt.begin(); i!=iend; ++i)
24         cout << *i << ' ';
25     cout << endl;
26 
27     return 0;
28 }
复制代码
复制代码
 1 /*        解释:
 2     利用 set 容器定义的【反向迭代器】 reverse_iterator 和 const_reverse_iterator ,可实现红黑树的【逆中序遍历】,从而将元素【由大到小】遍历出来。反向遍历,通常需要用到 rebegin 和 rend 函数,给出反向遍历的开始位置和结束位置。
 3 */
 4 
 5 -------------------------------------------------------- 反向遍历 set 容器元素
 6 #include <set>
 7 #include <iostream>
 8 using namespace std;
 9 int main()
10 {
11     // 注意,我插入的元素是乱序、有重复的
12     set<int> sInt;
13     sInt.insert(10);
14     sInt.insert(15);
15     sInt.insert(11);
16     sInt.insert(17);
17     sInt.insert(12);
18     sInt.insert(18);
19     sInt.insert(18);
20     sInt.insert(18);
21     // 反向 打印二叉树的数据
22     set<int>::reverse_iterator ri, riend;
23     riend = sInt.rend();
24     for (ri=sInt.rbegin(); ri!=riend; ++ri)
25         cout << *ri << ' ';
26     cout << endl;
27 
28     return 0;
29 }
复制代码
复制代码
 1 /*        解释:
 2     set容器提供了一个应用红黑树进行搜索的函数 find ,返回的迭代器值位搜索到的元素位置,如果元素不存在,则返回一个 end 结束元素的位置。
 3 */
 4 
 5 -------------------------------------------------------- set 容器元素的搜索
 6 #include <set>
 7 #include <iostream>
 8 using namespace std;
 9 int main()
10 {
11     set<int> sInt;
12     sInt.insert(10);
13     sInt.insert(15);
14     sInt.insert(11);
15     sInt.insert(17);
16     sInt.insert(13);
17     sInt.insert(19);
18     sInt.insert(1);
19 
20     // 搜索元素 13
21     int v = 13;
22     set<int>::iterator i_v = sInt.find(v);
23     cout << *i_v << endl;
24 
25     // 搜索不存在的元素 100
26     v = 100;
27     i_v = sInt.find(v);
28     if (i_v != sInt.end())
29         cout << *i_v << endl;
30 
31     return 0;
32 }
复制代码
复制代码
 1 /*        其他常用函数
 2 empty()    
 3 size()
 4 swap()
 5 lower_bound()    下确界函数,返回第一个 > elem 元素的迭代器
 6 upper_bound()    上确界函数,返回第一个 > elem 元素的迭代器
 7 equal_range()  返回容器中与elem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。以上函数返回两个迭代器,而这两个迭代器被封装在pair中。以下讲解pair的含义与使用方法。
 8 */
 9 
10 -------------------------------------------------------- set 的 empty 和 size 函数
11 #include <set>
12 #include <iostream>
13 using namespace std;
14 int main()
15 {
16     set<int> sInt;
17     sInt.insert(5);
18     sInt.insert(6);
19     sInt.insert(7);
20     sInt.insert(8);
21     sInt.insert(4);
22 
23     // 打印元素个数
24     if (sInt.empty())
25         cout << "set 容器为空" << endl;
26     else
27         cout << "set 容器元素个数为:" << sInt.size() << endl;
28 
29     return 0;
30 }
复制代码
复制代码
 1 ------------------------------   浅谈一下 pair
 2 (在关于 map 容器的笔记中,会更多地用到 pair. pair 很强大的,这里只是小试牛刀而已)
 3 
 4 pair译为对组,可以将两个值视为一个单元。
 5 pair<T1,T2>存放的两个值的类型,可以不一样,如T1为int,T2为float。T1,T2也可以是自定义类型。
 6 pair.first是pair里面的第一个值,是T1类型。
 7 pair.second是pair里面的第二个值,是T2类型
 8 
 9 // pair 和 equal_range 的配合使用 
10 例如 set<int> setInt;
11 ...  //往setInt容器插入元素1,3,5,7,9
12 pair< set<int>::iterator , set<int>::iterator > pairIt = setInt.equal_range(5);
13 set<int>::iterator itBeg = pairIt.first;
14 set<int>::iterator itEnd = pairIt.second;
15 //此时 *itBeg==5  而  *itEnd == 7
复制代码
1 -------------------------  浅谈一下 functor
2 尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。
3 functor,翻译成函数对象,伪函数,算符,是重载了“()”操作符的普通类对象。从语法上讲,它与普通函数行为类似。
复制代码
 1 // 题目:学生包含学号,姓名属性,现要求任意插入几个学生对象到set容器中,使得容器中的学生按学号的升序排序。
 2 
 3 #pragma warning(disable:4786)
 4 #include <set>
 5 #include <iostream>
 6 using namespace std;
 7 // 学生类
 8 class CStudent
 9 {
10 public:
11     CStudent(int nID, string strName)
12     {
13         m_nID = nID;
14         m_strName = strName; 
15     }
16 
17 public:
18     int m_nID;
19     string m_strName;
20 };
21 
22 // 函数对象 , 多看几遍,找一些相关的例子,就能理解了
23 struct studentFunctor
24 {
25     bool operator() (const CStudent &stu1, const CStudent &stu2)
26     {
27         return (stu1.m_nID < stu2.m_nID);
28     }
29 };
30 
31 int main()
32 {
33     set<CStudent, studentFunctor> setStu;
34     setStu.insert(CStudent(3, "小张"));
35     setStu.insert(CStudent(1, "小李"));
36     setStu.insert(CStudent(5, "小王"));
37     setStu.insert(CStudent(7, "小军"));
38 
39     set<CStudent, studentFunctor>::iterator it,itend;
40     itend = setStu.end();
41     // 按 ID 顺序,从小到大排列
42     for (it=setStu.begin(); it!=itend; ++it)
43     {
44         // 字符串,用 c_str() 函数来显示(string容器会讨论)
45         cout << it->m_strName.c_str() << "   ";
46     }
47 
48     return 0;
49 }
复制代码



------------------------ set 小结
    set 集合容器是一个有序关联容器,所包含的元素必须是唯一的,不能插入重复的元素(如果重复插入了,程序会自动舍弃重复的元素)。由于使用了红黑树这种特殊的平衡二叉检索树管理元素数据,具有高效的元素插入、删除和检索。元素检索的算法时间复杂度位 【log2 N】,也即 2 的 N 次方个元素的检索,只需要 N 此比较操作。。。。厉害吧。
    pair ,Functor 是比较高级的用法,必须掌握。多看例子,慢慢就能理解了。
    
    set 缺点:不能重复插入相同元素,删除、插入 操作频繁的时候,set 就不适合。
    set 优点:对于元素的检索,非常快

原文地址:https://www.cnblogs.com/virusolf/p/4912644.html