set集合容器
近期学习了STL中set的使用,在此写一点点总结和自己的一些体悟。
set集合容器实现了红黑树(Red-Black Tree)的平衡二叉检索树的的数据结构,在插入元素时,它会自动调整二叉树的排列,把该元素放到适当的位置,以确保每个子树根节点的键值大于左子树所有节点的键值,而小于右子树所有节点的键值;另外,还得确保根节点的左子树的高度与有字数的高度相等,这样,二叉树的高度最小,从而检索速度最快。要注意的是,它不会重复插入相同键值的元素,而采取忽略处理。
平衡二叉检索树的检索使用中序遍历算法,检索效率高于vector、deque、和list的容器。另外,采用中序遍历算法可将键值由小到大遍历出来,所以,可以理解为平衡二叉检索树在插入元素时,就会自动将元素按键值从小到大的顺序排列。
那么现在有人就要问了,为何set的插入、删除、检索效率要比其他序列容器高?
首先我们要了解set的储存方式,set内部采用的是红黑树(也称为RB树),红黑树是一种非常高效的平衡检索二叉树。基于红黑树,set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。结构图可能如下:
A
/ \
B C
/ \ / \
D E F G
因此插入的时候只需要稍做变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍做变换后把指向删除节点的指针指向其他节点也OK了。这里的一切操作就是指针换来换去,和内存移动没有关系。
那么当数据元素增多时,set的插入、删除和搜索速度变化如何呢?
如果你知道log2的关系你应该就彻底了解这个答案。在set中查找是使用二分查找,也就是说,如果有16个元素,最多需要比较4次就能找到结果,有32个元素,最多比较5次。那么有10000个呢?最多比较的次数为log10000,最多为14次,如果是20000个元素呢?最多不过15次。看见了吧,当数据量增大一倍的时候,搜索次数只不过多了1次,多了1/14的搜索时间而已。你明白这个道理后,就可以安心往里面放入元素了。
使用set前,需要在程序头文件中包含声明“#include<set>”。
下面我们列出一些set中的常用操作:(英语好的小伙伴可以进这个链接看看,包含了set的几乎所有操作)
首先是创建set集合对象:
1 #include<set> 2 3 using namespace std; 4 5 int main(){ 6 set<int> s;//定义元素类型为int的集合对象,当前没有任何元素 7 return 0; 8 }
元素的插入与中序遍历:
采用insert()方法把元素插入到集合中,插入规则在默认的比较规则下,是按元素值从小到大插入,如果自己指定了比较规则函数,则按自定义比较规则函数插入。使用前向迭代器对集合中序遍历,结果正好是元素排序后的结果。
1 #include<iostream> 2 #include<set> 3 using namespace std; 4 5 int main(){ 6 set<int> s; 7 s.insert(8);//插入了五个元素,但由于第二次插入8有重复,所以第二次插入8并没有执行 8 s.insert(1); 9 s.insert(12); 10 s.insert(6); 11 s.insert(8); 12 13 set<int>::iterator it;//定义前向迭代器 14 for(it=s.begin();it!=s.end();it++){ //中序遍历集合中的所有元素 15 cout<<*it<<" "; 16 } 17 cout<<endl; 18 return 0; 19 }
运行结果:
1 6 8 12
我们也可以使用反向迭代器reverse_iterator来反向遍历集合,输出结果正好是集合元素的反向排序结果。它需要用到rbegin()和rend()两个方法,他们分别给出了反向遍历的开始位置和结束位置。
1 #include<iostream> 2 #include<set> 3 using namespace std; 4 5 int main(){ 6 set<int> s; 7 s.insert(8);//插入了五个元素,但由于第二次插入8有重复,所以第二次插入8并没有执行 8 s.insert(1); 9 s.insert(12); 10 s.insert(6); 11 s.insert(8); 12 13 set<int>::reverse_iterator it;//定义反向迭代器 14 for(it=s.rbegin();it!=rend();it++){//反向遍历 15 cout<<*it<<" "; 16 } 17 cout<<endl; 18 return 0; 19 }
运行结果
12 8 6 1
元素的删除:
元素可以使用erase()来删除某键值的元素,也可以使用clear()来清空集合。
1 #include<iostream> 2 #include<set> 3 using namespace std; 4 5 int main(){ 6 set<int> s; 7 s.insert(8);//插入了五个元素,但由于第二次插入8有重复,所以第二次插入8并没有执行 8 s.insert(1); 9 s.insert(12); 10 s.insert(6); 11 s.insert(8); 12 13 s.erase(6);//删除键值为6的那个元素 14 set<int>::iterator it; 15 for(it=s.begin();it!=s.end();it++){ 16 cout<<*it<<" "; 17 } 18 cout<<endl; 19 s.clear();//清空集合 20 cout<<s.size()<<endl;//输出集合大小 21 return 0; 22 }
运行结果:
12 8 1
0
元素的检索:
我们可以使用find()来对集合进行搜索,如果找到查找的键值,则返回键值的迭代器位置,否则,返回集合最后一个元素后面的一个位置,即end()。
1 #include<iostream> 2 #include<set> 3 using namespace std; 4 5 int main(){ 6 set<int> s; 7 s.insert(8); 8 s.insert(1); 9 s.insert(12); 10 s.insert(6); 11 s.insert(8); 12 13 set<int>::iterator it; 14 it=s.find(6);//查找键值为6的元素 15 if(it!=s.end()){//找到 16 cout<<*it<<endl; 17 } 18 else//没找到 19 cout<<"not find it"<<endl; 20 it=find(20);//查找键值为20的元素 21 if(it!=s.end()) 22 cout<<*it<<endl; 23 else 24 cout<<"not find it"<<endl; 25 return 0; 26 }
其他操作:
其中一些我们程序中也用到过。
c++ stl容器set成员函数:begin()--返回指向第一个元素的迭代器
c++ stl容器set成员函数:clear()--清除所有元素
c++ stl容器set成员函数:count()--返回某个值元素的个数
c++ stl容器set成员函数:empty()--如果集合为空,返回true
c++ stl容器set成员函数:end()--返回指向最后一个元素的迭代器
c++ stl容器set成员函数:equal_range()--返回集合中与给定值相等的上下限的两个迭代器
c++ stl容器set成员函数:erase()--删除集合中的元素
c++ stl容器set成员函数:find()--返回一个指向被查找到元素的迭代器
c++ stl容器set成员函数:get_allocator()--返回集合的分配器
c++ stl容器set成员函数:insert()--在集合中插入元素
c++ stl容器set成员函数:lower_bound()--返回指向大于(或等于)某值的第一个元素的迭代器
c++ stl容器set成员函数:key_comp()--返回一个用于元素间值比较的函数
c++ stl容器set成员函数:max_size()--返回集合能容纳的元素的最大限值
c++ stl容器set成员函数:rbegin()--返回指向集合中最后一个元素的反向迭代器
c++ stl容器set成员函数:rend()--返回指向集合中第一个元素的反向迭代器
c++ stl容器set成员函数:size()--集合中元素的数目
c++ stl容器set成员函数:swap()--交换两个集合变量
c++ stl容器set成员函数:upper_bound()--返回大于某个值元素的迭代器
c++ stl容器set成员函数:value_comp()--返回一个用于比较元素间的值的函数