STL之map和set的异同
改了一小部分,原文转自介个博主也是转的节选一:较为表面的比较1.MAP的节点是一对数据.SET的节点是一个数据.2.都属于stl中的关联容器3.map的形式map<type1,type2>mymap;set的形式set<type>myset;4.set(集合)——包含了经过排序了的数据,这些数据的值(value)
改了一小部分,原文转自介个博主也是转的
节选一:
较为表面的比较
1.MAP的节点是一对数据.
SET的节点是一个数据.
2.都属于stl中的关联容器
3.map的形式 map<type1, type2> mymap;
set的形式 set<type> myset;
4.set(集合)——包含了经过排序了的数据,这些数据的值(value)必须是唯一的。
map(映射)——经过排序了的二元组的集合,map中的每个元素都是由两个值组成,其中的key(键值,一个map中的键值必须是唯一的)是在排序或搜索时使用,它的值可以在容器中重新获取;而另一个值是该元素关联的数值。比如,除了可以ar[43] = “overripe”这样找到一个数据,map还可以通过ar[“banana”] = “overripe”这样的方法找到一个数据。如果你想获得其中的元素信息,通过输入元素的全名就可以轻松实现。
5.map是映射集合中的元素不能重复,set可以进行集合的各种操作(交并补等),当然你也可以用list或vector实现set,但是效率会很低。
set一般是用平衡树或哈西表实现的。
映射是一种一一对应的关系,哈西表也可以看作是映射的一种。映射通常可用来实现字典结构(dictionary)
节选二:
深层次探索一下
C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般的平衡二叉树(有些书籍根据作者姓名,Adelson-Velskii和Landis,将其称为AVL-树),所以被STL选择作为了关联容器的内部结构。本文并不会介绍详细AVL树和RB树的实现以及他们的优劣,关于RB树的详细实现参看红黑树: 理论与实现(理论篇)。本文针对开始提出的几个问题的回答,来向大家简单介绍map和set的底层数据结构。
为何map和set的插入删除效率比用其他序列容器高?
大部分人说,很简单,因为对于关联容器来说,不需要做内存拷贝和内存移动。说对了,确实如此。map和set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。结构图可能如下:
A
/ /
B C
/ / / /
D E F G
因此插入的时候只需要稍做变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍做变换后把指向删除节点的指针指向其他节点就OK了。这里的一切操作就是指针换来换去,和内存移动没有关系。
为何每次insert之后,以前保存的iterator不会失效?
看见了上面答案的解释,你应该已经可以很容易解释这个问题。iterator这里就相当于指向节点的指针,内存没有变,指向内存的指针怎么会失效呢(当然被删除的那个元素本身已经失效了)。相对于vector来说,每一次删除和插入,指针都有可能失效,调用push_back在尾部插入也是如此。因为为了保证内部数据的连续存放,iterator指向的那块内存在删除和插入过程中可能已经被其他内存覆盖或者内存已经被释放了。即使时push_back的时候,容器内部空间可能不够,需要一块新的更大的内存,只有把以前的内存释放,申请新的更大的内存,复制已有的数据元素到新的内存,最后把需要插入的元素放到最后,那么以前的内存指针自然就不可用了。特别时在和find等算法在一起使用的时候,牢记这个原则:不要使用过期的iterator。
为何map和set不能像vector一样有个reserve函数来预分配数据?
我以前也这么问,究其原理来说时,引起它的原因在于在map和set内部存储的已经不是元素本身了,而是包含元素的节点。也就是说map内部使用的Alloc并不是map
map<int, int, less<int>, Alloc<int> > intmap;
这时候在intmap中使用的allocator并不是Alloc, 而是通过了转换的Alloc,具体转换的方法时在内部通过Alloc::rebind重新定义了新的节点分配器,详细的实现参看彻底学习STL中的Allocator。其实你就记住一点,在map和set内面的分配器已经发生了变化,reserve方法你就不要奢望了。
当数据元素增多时(10000和20000个比较),map和set的插入和搜索速度变化如何?
如果你知道log2的关系你应该就彻底了解这个答案。在map和set中查找是使用二分查找,也就是说,如果有16个元素,最多需要比较4次就能找到结果,有32个元素,最多比较5次。那么有10000个呢?最多比较的次数为log10000,最多为14次,如果是20000个元素呢?最多不过15次。看见了吧,当数据量增大一倍的时候,搜索次数只不过多了1次,多了1/14的搜索时间而已。你明白这个道理后,就可以安心往里面放入元素了。
最后,对于map和set Winter还要提的就是它们和一个C语言包装库的效率比较。在许多unix和Linux平台下,都有一个库叫isc,里面就提供类似于以下声明的函数:
void tree_init(void **tree);
void *tree_srch(void **tree, int (*compare)(), void *data);
void tree_add(void **tree, int (*compare)(), void *data, void (*del_uar)());
int tree_delete(void **tree, int (*compare)(), void *data,void (*del_uar)());
int tree_trav(void **tree, int (*trav_uar)());
void tree_mung(void **tree, void (*del_uar)());
许多人认为直接使用这些函数会比STL map速度快,因为STL map中使用了许多模板什么的。其实不然,它们的区别并不在于算法,而在于内存碎片。如果直接使用这些函数,你需要自己去new一些节点,当节点特别多,而且进行频繁的删除和插入的时候,内存碎片就会存在,而STL采用自己的Allocator分配内存,以内存池的方式来管理这些内存,会大大减少内存碎片,从而会提升系统的整体性能。Winter在自己的系统中做过测试,把以前所有直接用isc函数的代码替换成map,程序速度基本一致。当时间运行很长时间后(例如后台服务程序),map的优势就会体现出来。从另外一个方面讲,使用map会大大降低你的编码难度,同时增加程序的可读性。何乐而不为?
例题 找众数
Description
给出n个数,求这n个数的众数。
众数的定义是:是一组数据中出现次数最多的数值,叫众数,有时众数在一组数中有好几个。
Input
第一行有个整数T,表示测试组数T<=10.
接下来有T个测试组,每个测试组的第一行有整数n,表示有n个数。
接下来1行有n个数,第i个数ai ,0<=ai<1000
n<=1000
Output
对于每个测试组,请把答案在一行中输出,如果有多个众数,则按照从小到大的顺序把他们输出。
Sample Input
1
5
3 3 2 2 4
Sample Output
2 3
想法:用map来一对一的找出数出现的次数,用set来存储排序输出。
#include<cstdio>
#include<map>
#include<set>
#include<iostream>
using namespace std;
int main()
{
int T;
int n,i;
int max;
while(scanf("%d",&T)!=EOF)
{
int a[1010]= {0};
int d[1010];
while(T--)
{
int num=0;
set<int> c;
map<int,int> b;
scanf("%d",&n);
for(i=0; i<n; i++)
{
scanf("%d",&a[i]);
}
for(i=0; i<n; i++)
{
b[a[i]]++;
}
map<int,int>::iterator p,q,m;
q=p=b.begin();
for(p=b.begin(); p!=b.end(); p++)
{
if(p->second>q->second)
{
q=p;
}
}
for(m=b.begin(); m!=b.end(); m++)
{
if(m->second==q->second)
{
c.insert(m->first);
num++;
}
}
set<int>::iterator n;
for(n=c.begin(),i=0; n!=c.end(); n++,i++)
{
d[i]=(*n);
}
for(i=0; i<num; i++)
{
if(i<(num-1))
printf("%d ",d[i]);
else printf("%d",d[i]);
}
printf("\n");
}
}
return 0;
}
这个题其实可以不用map和set,这里只是为了找道题理解两个关联容器的用法XD
更多推荐
所有评论(0)