关联容器
1.1标准化
1)map:STL中的标准容器
2)hash_map:非c++标准的容器
3)unordered_map:unordered_map原来属于boost分支和std::tr1中,现加入c++11中,是hash_map在STL中的实现
2.底层实现
2.1map
1)基于红黑树实现
2.2unordered_map
1)基于哈希表实现
2)使用一个下标范围比较大的数组(用vector完成,以便有动态扩充能力,大小默认为28个质数中的一个),利用hash函数将key映射到数组的不同区域进行保存
3)vector中的元素称为桶,允许将不同的key映射到同一个桶中(哈希冲突),桶中用链表(并不是容器list,只是原始的链表,不含头节点)保存多个元素(节点),每次插入新节点时,插入到链表的头部而不是尾部。当桶中保存多个元素时,就要使用比较函数(默认为==运算符)来搜索这些元素来找到想要的那个
4)元素的直接地址用哈希函数生成,冲突用链地址法和比较函数解决,unordered_map的性能依赖于哈希函数的质量和桶的数量和大小
整个过程可以描述为:
- 首先分配一大片内存,形成许多桶
- 插入操作:得到key -> 通过hash函数得到hash值 -> 得到桶号(一般是hash值对桶数求模) -> 存放key和value在桶内
- 取值过程:得到key -> 通过hash函数得到hash值 -> 得到桶号(hash值对桶数求模) -> 比较桶内元素与key是否相等 -> 取出相等纪录的value
- 用户可以指定自己的hash函数与比较函数
2.3hash_map的扩容(resize函数)
1)如果总元素个数(现有元素+新增元素)大于bucket vector的大小,则进行扩容
2)由第1点可知,桶链表的最大长度和bucket vector的大小相等
3)扩容过程:
- 创建一个新vector tmp ,大小为满足总元素个数的质数
- 将旧vector buckets中桶链表上的节点一个一个地移动至 tmp对应的桶链表上
- 最后使用swap函数将buckets和tmp对调,函数结束后tmp作为局部变量被销毁
template <class V, class K, class HF, class Ex, class Eq, class A> void hashtable<V, K, HF, Ex, Eq, A>::resize(size_type num_elements_hint) { const size_type old_n = buckets.size(); if (num_elements_hint > old_n) { //确定真的需要重新配置 const size_type n = next_size(num_elements_hint); // 去28个质数的质数池里找出下一个质数 if (n > old_n) { vector<node*, A> tmp(n, (node*)0); // 设立新的 buckets __STL_TRY { // 以下处理每一个旧的bucket for (size_type bucket = 0; bucket < old_n; ++bucket) { node* first = buckets[bucket]; // 指向节点所对应之串列的起始节点 // 以下处理每一个旧bucket 所含(串列)的每一个节点 while (first) { // 串列还没结束时 // 以下找出节点落在哪一个新bucket 内 size_type new_bucket = bkt_num(first->val, n); // 以下四个动作颇为微妙 // (1) 令旧 bucket 指向其所对应之串列的下一个节点(以便迭代处理) buckets[bucket] = first->next; // (2)(3) 将当前节点安插到新bucket 内,成为其对应串列的第一个节点。 first->next = tmp[new_bucket]; tmp[new_bucket] = first; // (4) 回到旧bucket 所指的待处理串列,准备处理下一个节点 first = buckets[bucket]; } } buckets.swap(tmp); // vector::swap。新旧 buckets 对调。 // 注意,对调两方如果大小不同,大的会变小,小的会变大。 // 离开时释还local tmp的內存。 } } } }
3.key是否有序
1)map:红黑树是一棵二叉搜索树,具有排序功能,存放在map中的key-value对按照key排序
2)unordered_map:无序
4.默认支持的key的类型
1)map:内置类型或者类对象均可
2)unordered_map:内置类型或者类对象均可
3)hash_map:char、char *(会被默认当成地址,而不是字符串,小心)、const char *、unsigned char、signed char、short、unsigned short、int、unsigned int、long、unsignd long等内置类型
5.一般操作的时间复杂度
1)map:插入、查找、删除的时间复杂度为O(logn)
2)unordered_map:插入、查找、删除的时间复杂度为O(1)
6.需要定义的函数
1)map:比较函数,默认为<运算符,也可以自定义,但必须保证严格弱序:
- 两个关键字不能同时“严格弱于”对方:如果key1“严格弱于”key2,那么key2绝不能“严格弱于”key1
- 传递性:如果key1“严格弱于”key2,key2“严格弱于”key3,那么key1必须“严格弱于”key3
- 唯一性:如果key1和key2都不如果“严格弱于”对方,则认为key1和key2“等价”
2)unordered_map:哈希函数和比较函数,均可自定义,比较函数默认为==运算符
7.应用场景对比
1)选择时需要权衡三个因素:顺序性,效率,数据量,内存使用
2)如果需要排序则选择map
2)在元素数量达到一定数量级时如果要求效率优先,则采用unordered_map。但是注意:虽然unordered_map操作速度比map的速度快,但是unordered_map构造速度慢于map。其次,unordered_map由于基于哈希表,对内存的消耗高于map,是空间换时间
8.set
set与map的不同在于,元素的键值(key)就是实值(value),,实值就是键值,其余一样
9.multimap
multimap中的关键字允许重复,底层插入操作采用红黑树中的insert_equal()而非insert_unique(),红黑树的插入规则为要插入的pair的key如果比节点的key小,则向左走,如果大于等于则向右走,insert_equal()和insert_unique()都会按照此规则定位到合适的插入位置,与insert_unique()不同的是,insert_equal()会通过迭代器回溯到key可能相等的那个节点,再进行一次比较,如果相等,则不插入。具体过程可看源码
10.unordered_multimap
multimap中的关键字允许重复,底层插入操作采用哈希表的insert_equal()而非insert_unique(),插入时,定位到桶,两个函数都会遍历桶中的链表,遍历过程中如发现相同key,insert_unique()将不插入,直接返回,而insert_equal()会插入到相同key节点的后面
posted on 2019-03-22 08:18 JoeChenzzz 阅读(214) 评论(0) 编辑 收藏 举报