1.关于set set,map,priority_queue其实都有三个参数,只不过后两个默认,第二个是默认从小到大的排序函数
C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入、排序、删除、查找等。让用户在STL使用过程中,并不会感到陌生。
关于set,必须说明的是set关联式容器。set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序(map也会根据key的值自动排序)。默认是按照less<>排序规则来排序(下文有如何改变排序方法的技术)。应该注意的是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是否为空.
insert(key_value); 将key_value插入到set中 ,返回值是pair<set<int>::iterator,bool>,bool标志着插入是否成功,而iterator代表插入的位置,若key_value已经在set中,则iterator表示的key_value在set中的位置。
添加元素——插入
(1)pair<iterator, bool> insert(const value_type& v)
将元素v插入set容器,要求v值不与set容器的任何元素重复,否则插入失败。返回一个pair配对对象,提供所插入元素的迭代器位置和true/false插入成功标志。
(2)void insert(InputIterator first, InputIterator last)
将某迭代器区间[first,last)所指的数据作为元素,插入到set容器。
(3)iterator insert(iterator position, const value_type& v)
将元素v插入set容器,参数position只是提示可在position位置之前插入v,所返回的插入位置视实际情况而定,不一定能在position位置前插入。
inset(first,second);将定位器first到second之间的元素插入到set中,返回值是void.
示例代码:
1 #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 }
运行结果:
查找
find() ,返回给定值值得定位器,如果没找到则返回end()。
示例代码:
1 #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 }
示例代码:
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 中 1 出现的次数是 :"<<s.count(1)<<endl; 14 cout<<"set 中 4 出现的次数是 :"<<s.count(4)<<endl; 15 return 0; 16 }
运行结果:
删除
erase(iterator) ,删除定位器iterator指向的值
erase(first,second),删除定位器first和second之间的值
erase(key_value),删除键值key_value的值
看看程序吧:
1 #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中的删除操作是不进行任何的错误检查的,比如定位器的是否合法等等,所以用的时候自己一定要注意。
equal_range() ,返回一对定位器,分别表示第一个大于或等于给定关键值的元素和 第一个大于给定关键值的元素,这个返回值是一个pair类型,如果这一对定位器中哪个返回失败,就会等于end()的值。具体这个有什么用途我还没遇到过~~~
示例代码:
1 #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 }
运行结果:
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 }
运行结果:
equal_range() ,返回一对定位器,分别表示第一个大于或等于给定关键值的元素和 第一个大于给定关键值的元素,这个返回值是一个pair类型,如果这一对定位器中哪个返回失败,就会等于end()的值。具体这个有什么用途我还没遇到过~~~
示例代码:
1 #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 }
运行结果:
三 .使用系统的比较函数和自定义比较函数
3.1使用系统预定义仿函数
- set<type>:以less<>为排序法则的set
- set<type,greater<type>>使用系统自定义仿函数greater<type> ,#include<functional>
- set<type, op>:以自定义op函数为排序法则的set
- multiset<type>:以less<>为排序法则的multiset
- multiset<type, op>:以op为排序法则的multiset
从上面我们可以看到,可以从两个地方来指定排序法则:
(1)作为模板参数
例如:std::set<int, greater<int> > col1;//定义从大到小排序,但是记得加上系统预定义仿函数的头文件,#include<functional>
这种情况下,排序法则本身作为容器类型的一部分。因此类别系统确保“只有排序准则相同的容器才能合并”对于一个set或者multiset容器,只有当元素类型和排序法则类型都相同时,他们的类型才被认为相同,否则就是不同类型的容器。
(2)作为构造函数参数
例如:std::set<int> col1(greater<int>);
这种情况下指定的排序法则不作为容器类型的一部分,你可以为相同类型的容器指定不同的排序规则。这通常应用于要求相同的容器类型,但排序规则可以不同的场合。
注:c++提供的fanh仿函数如下:
仿函数 | 效果 |
negate<type>() | -param |
plus<type>() | param1+param2 |
minus<type>() | param1-param2 |
multiplies<type>() | param1*param2 |
divides<type>() | param1/param2 |
modulus<type>() | param1%param2 |
equal_to<type>() | param1==param2 |
not_equal_to<type>() | param1!=param2 |
less<type>() | param1<param2 |
greater<type>() | param1>param2 |
less_equal<type>() | param1<=param2 |
greater_equal<type>() | param1>=param2 |
logical_not<type>() | !param1 |
logical_and<type>() | param1&¶m2 |
黑底区段是为了区分三大类仿函数:算数类,相对关系类,逻辑运算类
3.2使用自定义比较函数
使用insert将元素插入到集合中去的时候,集合会根据设定的比较函数奖该元素放到该放的节点上去。在定义集合的时候,如果没有指定比较函数,那么采用默认的比较函数,即按键值从小到大的顺序插入元素。但在很多情况下,需要自己编写比较函数。
编写比较函数有两种方法。
(1)如果元素不是结构体,那么可以编写比较函数。下面的程序比较规则为按键值从大到小的顺序插入到集合中。
(2)如果元素是结构体,那么可以直接把比较函数写在结构体内。
定义一个元素为整数的集合a,可以用 set<int> a;
1. begin() 返回指向第一个元素的迭代器
2. clear() 清除所有元素
3. count() 返回某个值元素的个数
4. empty() 如果集合为空,返回true
5. end() 返回指向最后一个元素的迭代器
6. equal_range() 返回集合中与给定值相等的上下限的两个迭代器
7. erase() 删除集合中的元素
8. find() 返回一个指向被查找到元素的迭代器
9. get_allocator() 返回集合的分配器
10.insert() 在集合中插入元素
11.lower_bound() 返回指向大于(或等于)某值的第一个元素的迭代器
12.key_comp() 返回一个用于元素间值比较的函数
13.max_size() 返回集合能容纳的元素的最大限值
14.rbegin() 返回指向集合中最后一个元素的反向迭代器
15.rend() 返回指向集合中第一个元素的反向迭代器
16.size() 集合中元素的数目
17.swap() 交换两个集合变量
18.upper_bound() 返回大于某个值元素的迭代器
19.value_comp() 返回一个用于比较元素间的值的函数
函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置
举例如下:
一个数组number序列为:4,10,11,30,69,70,96,100.设要插入数字3,9,111.pos为要插入的位置的下标
则
pos = lower_bound( number, number + 8, 3) - number,pos = 0.即number数组的下标为0的位置。
pos = lower_bound( number, number + 8, 9) - number, pos = 1,即number数组的下标为1的位置(即10所在的位置)。
pos = lower_bound( number, number + 8, 111) - number, pos = 8,即number数组的下标为8的位置(但下标上限为7,所以返回最后一个元素的下一个元素)。
所以,要记住:函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置,且last的位置是越界的!!~
返回查找元素的第一个可安插位置,也就是“元素值>=查找值”的第一个元素的位置
-------------------------------------------------------------------------------------------------set_union(a.begin(),a.end(),b.begin(),b.end(),insert_iterator<set<int> >(c,c.begin()));
set_intersection(a.begin(),a.end(),b.begin(),b.end(),insert_iterator<set<int> >(c,c.begin()));
set_difference(a.begin(),a.end(),b.begin(),b.end(),insert_iterator<set<int> >(c,c.begin()));
(注意在此前要将c清为空集)。
注意:
很重要的一点,为了实现集合的快速运算,set的实现采用了平衡二叉树,因此,set中的元素必须是可排序的。如果是自定义的类型,那在定义类型的同时必须给出运算符<的定义