STL中关于map和set的四个问题?
STL map和set的使用虽不复杂,但也有一些不易理解的地方,如:
- 为何map和set的插入删除效率比用其他序列容器高?
或许有得人能回答出来大概原因,但要彻底明白,还需要了解STL的底层数据结构。
C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构
操作。vector封装数组,list封装了链表,map和 set封装了二叉树等,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入、
排序、删除、查找等。让用户在 STL使用过程中,并不会感到陌生。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了。这里的一切操作就是指针换来换
去,和内存移动没有关系。
红黑树的节点实现:因此插入和删除只是指针的变化:
struct __rb_tree_node_base
{
typedef __rb_tree_color_type color_type;
typedef __rb_tree_node_base* base_ptr; color_type color; //节点颜色,非红即黑
base_ptr parent; //父节点
base_ptr left; //左孩子
base_ptr right; //右孩子
}
2.为何每次insert之后,以前保存的iterator不会失效?
看见了上面答案的解释,你应该已经可以很容易解释这个问题。iterator这里就相当于指向节点的指针,内存没有变,指向内存的指针怎么会失效呢(当然被删除的那个元素本身已
经失效了)。相对于vector来说,每一次删除和插入,指针都有可能失效,调用push_back在尾部插入也是如此。因为为了保证内部数据的连续存放,iterator指向的那块内存在
删除和插入过程中可能已经被其他内存覆盖或者内存已经被释放了。即使时push_back的时候,容器内部空间可能不够,需要一块新的更大的内存,只有把以前的内存释放,申请新
的更大的内存,复制已有的数据元素到新的内存,最后把需要插入的元素放到最后,那么以前的内存指针自然就不可用了。特别时在和find等算法在一起使用的时候,牢记这个原
则:不要使用过期的iterator。
3.为何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方法你就不要奢望了。
4.当数据元素增多时(10000到20000个比较),map和set的插入和搜索速度变化如何?
如果你知道log2的关系你应该就彻底了解这个答案。在map和set中查找是使用二分查找,也就是说,如果有16个元素,最多需要比较4次就能找到结果,有32个元素,最多比较5次
。那么有10000个呢?最多比较的次数为log10000,最多为14次,如果是20000个元素呢?最多不过15次。看见了吧,当数据量增大一倍的时候,搜索次数只不过多了1次,多了1/1
4的搜索时间而已。你明白这个道理后,就可以安心往里面放入元素了。最后,对于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中使用了许多模板什么的。其实不然,它们的区别并不在于算法,而在于内存碎片。如果直接使用这些函数,你需
要自ma己去new一些节点,当节点特别多,而且进行频繁的删除和插入的时候,内存碎片就会存在,而STL采用自己的Allocator分配内存,以内存池的方式来管理这些内存,会大
大减少内存碎片,从而会提升系统的整体性能。Winter在自己的系统中做过测试,把以前所有直接用isc函数的代码替换成map,程序速度基本一致。当时间运行很长时间后(例如
后台服务程序),map的优势就会体现出来。从另外一个方面讲,使用map会大大降低你的编码难度,同时增加程序的可读性。何乐而不为?
STL中关于map和set的四个问题?的更多相关文章
- STL 中的map 与 hash_map的理解
可以参考侯捷编著的<STL源码剖析> STL 中的map 与 hash_map的理解 1.STL的map底层是用红黑树存储的,查找时间复杂度是log(n)级别: 2.STL的hash_ma ...
- STL中的map和unordered_map
STL中的map和unordered_map map 头文件:#include 原理:std::map的内部实现了一颗红黑树,有对其键值进行排序的功能,所以map是一个有序的容器,map中的每一个元素 ...
- C++ STL中的map用红黑树实现,搜索效率是O(lgN),为什么不像python一样用散列表从而获得常数级搜索效率呢?
C++ STL中的标准规定: map, 有序 unordered_map,无序,这个就是用散列表实现 谈谈hashmap和map的区别,我们知道hashmap是平均O(1),map是平均O(lnN)的 ...
- STL中的map、unordered_map、hash_map
转自https://blog.csdn.net/liumou111/article/details/49252645 在之前使用STL时,经常混淆的几个数据结构,特别是做Leetcode的题目时,对于 ...
- STL中的map和hash_map
以下全部copy于:http://blog.chinaunix.net/uid-26548237-id-3800125.html 在网上看到有关STL中hash_map的文章,以及一些其他关于STL ...
- stl中的map数据类型
1.1 STL map 1.1.1 背景 关联容器使用键(key)来存储访问读取元素,而顺序容器则通过元素在容器中的位置存储和访问元素. 常见的顺序容器有:vector.list.deque.stac ...
- STL中的map/multimap小结
(1)使用map/multimap之前必须包含头文件<map>:#include<map> 并且和所有的关联式容器一样,map/multimap通常以平衡二叉树来完成 (2)n ...
- stl中的map经验
如果想使用一个map临时变量装载参数map,不需要使用new创建一个对象. 声明一个变量,直接赋值就可以.map内部自己重载了=操作符,会自己分配内存.
- STL中的map
map 容器 提供 1 对 1 的关系 定义方式: map<string,int>mp; 写在前面的是关键字. 数据插入: 1.使用 insert 插入 pair 数据 mp.insert ...
随机推荐
- Python3组合数据类型(元组、列表、集合、字典)语法
一.序列类型(字符串,元组(),列表[]) 序列类型支持in,len(),分片[],迭代,5种内置序列类型:bytearray,bytes,list,str,tuple(元组). 1.元组可以嵌套(如 ...
- [图形学] 计算机图形学 with OpenGL开篇
<计算机图形学>(第四版)正在学习中,学习目的是为了在Unity中使用shader实现不同的渲染效果. 希望在这里能把学习过程中学到的知识和遇到的问题记录下来. 工作环境是:Xcode 8 ...
- 原生JS的HTTP请求
ar xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if( xhr.readyState == 4){ if( xh ...
- 网站waf检测
WAFW00F WAFW00F识别和指纹Web应用防火墙(WAF)产品. 其工作原理是首先通过发送一个正常http请求,然后观察其返回有没有一些特征字符,若没有在通过发送一个恶意的请求触发waf拦截来 ...
- 关于TRIM的优化技巧
背景 今天在论坛中,看到有人在问一个千万级别表查询的优化.一个简单的查询几分钟.语句如下 SELECT work_date , major , style , ...
- POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)
POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...
- HDU 1814 Peaceful Commission / HIT 1917 Peaceful Commission /CJOJ 1288 和平委员会(2-sat模板题)
HDU 1814 Peaceful Commission / HIT 1917 Peaceful Commission /CJOJ 1288 和平委员会(2-sat模板题) Description T ...
- windows下安装DB2数据库以及使用Aqua Data Studio链接数据库
本文只是作为自己的心得体会,不具有一般性! 1.其实安装DB2数据库还是比较简单的,一般都是直接下一步下一步就可以了,只是有些地方需要注意.我安装的DB2数据库版本如下图所示: 2.拿到数据库的版本之 ...
- JDK动态代理源码学习
继上一篇博客设计模式之代理模式学习之后http://blog.csdn.net/u014427391/article/details/75115928,本博客介绍JDK动态代理的实现原理,学习一下JD ...
- 支持多个版本的ASP.NET Core Web API
基本配置及说明 版本控制有助于及时推出功能,而不会破坏现有系统. 它还可以帮助为选定的客户提供额外的功能. API版本可以通过不同的方式完成,例如在URL中添加版本或通过自定义标头和通过Accept- ...