【STL】关联容器 — hashtable
来源:互联网 发布:sublime text 3js插件 编辑:程序博客网 时间:2024/06/05 18:44
C++ 11已将哈希表纳入了标准之列。hashtable是hash_set、hash_map、hash_multiset、hash_multimap的底层机制,即这四种容器中都包含一个hashtable。
解决碰撞问题的办法有许多,线性探测、二次探测、开链等等。SGI STL的hashtable采用的开链方法,每个hash table中的元素用vector承载,每个元素称为桶(bucket),一个桶指向一个存储了实际元素的链表(list),链表节点(node)结构如下:
template <class Value>struct __hashtable_node{ __hashtable_node* next; Value val; // 存储实际值};
再来看看hash table的迭代器定义:
template <class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>struct __hashtable_iterator { // 迭代器 typedef hashtable<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> hashtable; .... typedef __hashtable_node<Value> node; // 定义迭代器相应类型 typedef forward_iterator_tag iterator_category; // 前向迭代器 typedef Value value_type; typedef ptrdiff_t difference_type; typedef size_t size_type; typedef Value& reference; typedef Value* pointer; node* cur; // 迭代器目前所指节点 hashtable* ht; // 和hashtable之间的纽带 __hashtable_iterator(node* n, hashtable* tab) : cur(n), ht(tab) {} __hashtable_iterator() {} reference operator*() const { return cur->val; } pointer operator->() const { return &(operator*()); } iterator& operator++(); iterator operator++(int); bool operator==(const iterator& it) const { return cur == it.cur; } bool operator!=(const iterator& it) const { return cur != it.cur; }};
hash table的迭代器不能后退,这里关注迭代器的自增操作,代码如下:
template <class V, class K, class HF, class ExK, class EqK, class A>__hashtable_iterator<V, K, HF, ExK, EqK, A>&__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++() // 注意类模板成员函数的定义{ const node* old = cur; cur = cur->next; // 移动到下一个node if (!cur) { // 到了list结尾 size_type bucket = ht->bkt_num(old->val); // 根据节点值定位旧节点所在桶号 while (!cur && ++bucket < ht->buckets.size()) // 计算下一个可用桶号 cur = ht->buckets[bucket]; // 找到,另cur指向新桶的第一个node } return *this;}
hashtable数据结构内容很多,这里只列出少量代码:
template <class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>class hashtable { // hash table数据结构public: typedef Key key_type; typedef Value value_type; typedef HashFcn hasher; // 散列函数类型 typedef EqualKey key_equal; typedef size_t size_type; typedef ptrdiff_t difference_type; .... private: hasher hash; // 散列函数 key_equal equals; // 判断键值是否相等 ExtractKey get_key; // 从节点取出键值 typedef __hashtable_node<Value> node; typedef simple_alloc<node, Alloc> node_allocator; // 空间配置器 vector<node*,Alloc> buckets; // 桶的集合,可以看出一个桶实值上是一个node* size_type num_elements; // node个数 ....}
SGI STL将hash table的大小,也就是vector的大小设计为28个质数,并存放在一个数组中:
static const int __stl_num_primes = 28; // 28个质数static const unsigned long __stl_prime_list[__stl_num_primes] ={ 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741, 3221225473, 4294967291};
当vector容量不足时,会以两倍的容量进行扩充。
下面介绍插入操作,以insert_unique为例:
// 插入新元素,键值不能重复 pair<iterator, bool> insert_unique(const value_type& obj) { resize(num_elements + 1); // 判断vector是否需要扩充 return insert_unique_noresize(obj); // 直接插入obj }
insert操作大致分两步:第一步是扩充(如果需要的话),第二步是插入。
resize代码如下:
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) // 判断是否需要扩充vector{ const size_type old_n = buckets.size(); if (num_elements_hint > old_n) { // 元素个数大于vector容量,则需要扩充vector const size_type n = next_size(num_elements_hint); if (n > old_n) { vector<node*, A> tmp(n, (node*) 0); // 建立一个临时的vector作为转移目的地 for (size_type bucket = 0; bucket < old_n; ++bucket) { // 一个桶一个桶进行转移 node* first = buckets[bucket]; while (first) { // 一个节点一个节点进行转移 size_type new_bucket = bkt_num(first->val, n); // 散列过程,对n取模 buckets[bucket] = first->next; first->next = tmp[new_bucket]; // 这一句和下一句表示从链表前端插入 tmp[new_bucket] = first; first = buckets[bucket]; // first指向旧vector的下一个node } buckets.swap(tmp); // 两个vector的内容互换,使buckets彻底改变 } } }}
上述代码基本思路就是:先扩充,再移动,最后交换。
- 扩充利用next_size函数。next_size的作用就是从质数表中选取最接近并且不小于num_elements_hint的质数并返回,利用这个较大值开辟一个新vector。
- 移动实质上就是指针的移动。重新对每个节点进行散列,然后从前链入到新的vector中。
- 交换过程就是上面代码红色部分。这里使用了vector内部的swap成员函数,将*this和tmp的内容进行了互换,这是copy-and-swap技术,《Effective C++》条款11有说明这个技术。扩充完vector后,就可以顺利插入需要插入的元素了。
insert_unique_noresize代码如下:
template <class V, class K, class HF, class Ex, class Eq, class A>pair<typename hashtable<V, K, HF, Ex, Eq, A>::iterator, bool> // 注意,返回一个pairhashtable<V, K, HF, Ex, Eq, A>::insert_unique_noresize(const value_type& obj) // 直接插入节点,无需扩充{ const size_type n = bkt_num(obj); // 对obj进行散列,然后模上vector大小,从而确定桶号 node* first = buckets[n]; // first指向对应桶的第一个node for (node* cur = first; cur; cur = cur->next) if (equals(get_key(cur->val), get_key(obj))) // 遇到相同node,则直接返回这个node return pair<iterator, bool>(iterator(cur, this), false); // 没有遇到相同node,则在list开头插入 node* tmp = new_node(obj); tmp->next = first; buckets[n] = tmp; ++num_elements; return pair<iterator, bool>(iterator(tmp, this), true);}
这里也是将新节点插入list的开头,详细过程已在注释中说明。
参考:
《STL源码剖析》 P253.
0 0
- 【STL】关联容器 — hashtable
- stl之关联容器——set,map,hashtable
- STL关联式容器之散列表——hashtable
- STL — 关联容器
- STL源码剖析 - 第5章 关联式容器 - hashtable
- STL源码剖析-关联式容器之hashtable
- STL学习——STL中的关联式容器总结(RB-tree、set、map、hashtable、hash_set、hash_map)
- STL—— 关联容器
- STL关联容器—multimap
- STL关联容器—map
- STL关联容器—set
- 【STL】关联容器 — 红黑树
- 【STL】关联容器 — set
- 【STL】关联容器 — map
- 【STL】关联容器 — hash_set
- 【STL】关联容器 — hash_map
- STL组件——容器(关联容器)
- STL关联容器
- AS3代码播放GIF动画
- TC-SRM617div1-800
- C# apsx ListControl根据值选定列表项
- 【从0开始Tornado建站】总体设计
- 黑马程序员_API_包装类&set&list集合
- 【STL】关联容器 — hashtable
- VariableDeclaratorId expected after this token
- Android fill_parent、wrap_content和match_parent的区别
- uva 301
- 1000桶酒中寻找一桶毒酒算法
- Git命令解析
- CentOS离线安装gcc4.8.2
- 财务中要生存以个表的SQL语句
- 寻找和为定值的两个数