STL中关于map和set的四个问题?
2015-07-20 21:38
183 查看
STLmap和set的使用虽不复杂,但也有一些不易理解的地方,如: #为何map和set的插入删除效率比用其他序列容器高? #为何每次insert之后,以前保存的iterator不会失效? #为何map和set不能像vector一样有个reserve函数来预分配数据? #当数据元素增多时(10000到20000个比较),map和set的插入和搜索速度变化如何? 或许有得人能回答出来大概原因,但要彻底明白,还需要了解STL的底层数据结构。 C++STL之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector,string,list等方便的容器,更重要的是STL封 装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等,在 封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入、排序、删除、查找等。 让用户在STL使用过程中,并不会感到陌生。 C++STL中标准关联容器set,multiset,map,multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB 树(Red-BlackTree)。RB树的统计性能要好于一般的平衡二叉树(有些书籍根据作者姓名,Adelson-Velskii和Landis,将其称 为AVL-树),所以被STL选择作为了关联容器的内部结构。本文并不会介绍详细AVL树和RB树的实现以及他们的优劣,关于RB树 的详细实现参看红黑树:理论与实现(理论篇)。本文针对开始提出的几个问题的回答,来向大家简单介绍map和set的底层数据 结构。 为何map和set的插入删除效率比用其他序列容器高? 大部分人说,很简单,因为对于关联容器来说,不需要做内存拷贝和内存移动。说对了,确实如此。map和set容器内所有元素 都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。结构图可能如下: A /\ BC /\/\ DEFG 因此插入的时候只需要稍做变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍做变换后把指向删除节点的指针 指向其他节点就OK了。这里的一切操作就是指针换来换去,和内存移动没有关系。 为何每次insert之后,以前保存的iterator不会失效? 看见了上面答案的解释,你应该已经可以很容易解释这个问题。iterator这里就相当于指向节点的指针,内存没有变,指向内 存的指针怎么会失效呢(当然被删除的那个元素本身已经失效了)。相对于vector来说,每一次删除和插入,指针都有可能失效 ,调用push_back在尾部插入也是如此。因为为了保证内部数据的连续存放,iterator指向的那块内存在删除和插入过程中可 能已经被其他内存覆盖或者内存已经被释放了。即使时push_back的时候,容器内部空间可能不够,需要一块新的更大的内存 ,只有把以前的内存释放,申请新的更大的内存,复制已有的数据元素到新的内存,最后把需要插入的元素放到最后,那么以 前的内存指针自然就不可用了。特别时在和find等算法在一起使用的时候,牢记这个原则:不要使用过期的iterator。 为何map和set不能像vector一样有个reserve函数来预分配数据? 我以前也这么问,究其原理来说时,引起它的原因在于在map和set内部存储的已经不是元素本身了,而是包含元素的节点。也 就是说map内部使用的Alloc并不是map<Key,Data,Compare,Alloc>声明的时候从参数中传入的Alloc。例如: map<int,int,less<int>,Alloc<int>>intmap; 这时候在intmap中使用的allocator并不是Alloc<int>,而是通过了转换的Alloc,具体转换的方法时在内部通过 Alloc<int>::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和setWinter还要提的就是它们和一个c语言包装库的效率比较。在许多unix和linux平台下,都有一个库叫isc ,里面就提供类似于以下声明的函数: voidtree_init(void**tree); void*tree_srch(void**tree,int(*compare)(),void*data); voidtree_add(void**tree,int(*compare)(),void*data,void(*del_uar)()); inttree_delete(void**tree,int(*compare)(),void*data,void(*del_uar)()); inttree_trav(void**tree,int(*trav_uar)()); voidtree_mung(void**tree,void(*del_uar)()); 许多人认为直接使用这些函数会比STLmap速度快,因为STLmap中使用了许多模板什么的。其实不然,它们的区别并不在于算 法,而在于内存碎片。如果直接使用这些函数,你需要自ma己去new一些节点,当节点特别多,而且进行频繁的删除和插入的时 候,内存碎片就会存在,而STL采用自己的Allocator分配内存,以内存池的方式来管理这些内存,会大大减少内存碎片,从而 会提升系统的整体性能。Winter在自己的系统中做过测试,把以前所有直接用isc函数的代码替换成map,程序速度基本一致。 当时间运行很长时间后(例如后台服务程序),map的优势就会体现出来。从另外一个方面讲,使用map会大大降低你的编码难 度,同时增加程序的可读性。何乐而不为?http://blog.csdn.net/wu_lai_314/article/details/8440655
这里说一下map和set容器的区别。
对于map中的每个节点存储的是一对信息,包括一个键和一个值,各个节点之间的键值不能重复。
对于set中的每个节点存储的是一个信息,只有一个键,但是每个键值也是唯一的。set表示的是集合的概念。
相关文章推荐
- manacher算法
- Android自定义控件属性
- STL容器的效率比较
- Elevator
- vc的环境变量配置和缺少mspdb60.dll的解决方法
- RatingBar的自定义
- 关于一点HTML的东西
- C语言各种预编译宏总结
- 查看程序符号表的几个命令
- OpenWrt配置绿联的usb转Ethernet网口驱动
- 常用的不会忘记,不常用却容易忘记的(持续更新······)
- 感想
- Gym 100712L Alternating Strings II(单调队列)
- JAVA实现二分查找
- POJ 3258 River Hopscotch
- 4 和tqy4+. ---PHP的循环总结
- 数据仓库简介
- C#软件开发实例.个人定制自己的屏幕抓图工具(八)加入了截图功能键盘
- php5.6及以上版本利用curl文件上传
- Linux基础---鸟哥Linux总结