hashtable 前言: hashtable
实现一个关联容器, 其在插入, 删除等操作都可以做到 O(1)
实现
哈希表概念 哈希方法
直接定址法 :取关键字或关键字的某个线性函数值为散列地址。(这种散列函数叫做自身函数)
数字分析法 :假设关键字是以r为基的数,并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
平方取中法 :取关键字平方后的中间几位为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,取其中的哪几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取的位数由表长决定。
折叠法 :将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。
随机数法
除留余数法 :取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。不仅可以对关键字直接取模,也可在折叠法、平方取中法等运算之后取模。对p的选择很重要,一般取素数或m,若p选择不好,容易产生冲突。
hashtable解决冲突的办法就是开链.
冲突处理 哈希表的冲突处理也有很多种.
开放定址法
线性探测 : 本来的位置被占有(冲突), 重新再往后找到第一个有空的位置插入进去
二次探测 : 本来的位置被占有(冲突), 每次有冲突就平方一次重新查找
开链 : 本来的位置被占有(冲突), 形成一个链表插入到链表中
装载因子 : 装入表中的元素 / 表的实际大小. 装载因子越大说明冲突的可能性就越大.
hashtable 分析 hashtable 前置声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <class Value , class Key , class HashFcn , class ExtractKey , class EqualKey , class Alloc = alloc>class hashtable ;template <class Value , class Key , class HashFcn , class ExtractKey , class EqualKey , class Alloc > struct __hashtable_iterator ;template <class Value , class Key , class HashFcn , class ExtractKey , class EqualKey , class Alloc > struct __hashtable_const_iterator ;template <class Value , class Key , class HashFcn , class ExtractKey , class EqualKey , class Alloc >
hashtable 基础定义 hashtable
采用开链法来处理 hash
冲突, 所以需要有一个桶 bucket
以及每一个节点 node
桶 bucket: 定义的哈希表的小大, 采用 vector
为桶的容器
关于桶的, 通常采用质数来作为桶的大小.
1 2 3 4 5 6 7 8 9 10 static const int __stl_num_primes = 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 , 3221225473ul , 4294967291ul };
在后续的 sgi_STL
版本后, 会根据编译器版本以及平台, 选择不同的质数数量以更好的适应不同平台.
由于涉及到 hashtable 后续的扩容问题, 所以提供了 unsigned long __stl_next_prime(unsigned long n)
函数来找到一个满足条件的质数.
1 2 3 4 5 6 7 inline unsigned long __stl_next_prime(unsigned long n){ const unsigned long * first = __stl_prime_list; const unsigned long * last = __stl_prime_list + __stl_num_primes; const unsigned long * pos = lower_bound (first, last, n); return pos == last ? *(last - 1 ) : *pos; }
关于扩容: 在这里设计者依旧采用了一个通过经验得到的判断, 当桶 bucket
中元素容量 > 当前桶的大小时, 就要在上述的质数数组中寻找第一个不小于元素容量的质数重新作为桶的大小
节点 node: 在桶的每一个位置采用链表的结构进行存储, 所以节点为链表节点
1 2 3 4 5 6 template <class Value >struct __hashtable_node { __hashtable_node* next; Value val; };
hashtable 迭代器 hashtable
迭代器是 forward_iterator_tag
类型, 正向迭代器, 所以不提供回退的功能, 也就是说它没有重载 operator--
__hashtable_const_iterator
和 __hashtable_iterator
除了内部数据设置为 const 外, 其他结构都是相同的, 这里只对后者进行分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> iterator; typedef __hashtable_const_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> const_iterator; 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_iterator(node* n, hashtable* tab) : cur (n), ht (tab) {} __hashtable_iterator() {} reference operator *() const { return cur->val; } #ifndef __SGI_STL_NO_ARROW_OPERATOR pointer operator ->() const { return &(operator *()); } #endif 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; } };
由于 hashtable
采用的是开链法来处理哈希冲突, 所以迭代器在找完一条链之后, 如何再进行 operator++
, 这时就需要回到 hashtable
, 所以在 hashtable 迭代器中需要有一个指向 hashtable
的指针
hashtable hashtable 类型定义
模板参数含义:
Value: 节点的实值类型
Key: 节点的键值类型
HashFcn: hash function 的类型
ExtractKey: 从节点中取出键值的方法 (函数或者仿函数)
EqualKey: 判断键值是否相同的方法 (函数或者仿函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 template <class Value , class Key , class HashFcn , class ExtractKey , class EqualKey , class Alloc >class hashtable {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; typedef value_type* pointer; typedef const value_type* const_pointer; typedef value_type& reference; typedef const value_type& const_reference; hasher hash_funct () const { return hash; } key_equal key_eq () const { return equals; } 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; size_type num_elements; public : typedef __hashtable_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> iterator; typedef __hashtable_const_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> const_iterator; friend struct __hashtable_iterator < Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>; friend struct __hashtable_const_iterator < Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>; ... };
构造与析构函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 template <class Value , class Key , class HashFcn , class ExtractKey , class EqualKey , class Alloc >class hashtable { ... public : hashtable (size_type n, const HashFcn& hf,const EqualKey& eql,const ExtractKey& ext) : hash (hf), equals (eql), get_key (ext), num_elements (0 ) { initialize_buckets (n); } hashtable (size_type n, const HashFcn& hf, const EqualKey& eql) : hash (hf), equals (eql), get_key (ExtractKey ()), num_elements (0 ) { initialize_buckets (n); } hashtable (const hashtable& ht) : hash (ht.hash), equals (ht.equals), get_key (ht.get_key), num_elements (0 ) { copy_from (ht); } ~hashtable () { clear (); } ... };
基本属性获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 template <class Value , class Key , class HashFcn , class ExtractKey , class EqualKey , class Alloc >class hashtable { ... public : size_type size () const { return num_elements; } size_type max_size () const { return size_type (-1 ); } bool empty () const { return size () == 0 ; } void swap (hashtable& ht) { __STD::swap (hash, ht.hash); __STD::swap (equals, ht.equals); __STD::swap (get_key, ht.get_key); buckets.swap (ht.buckets); __STD::swap (num_elements, ht.num_elements); } iterator begin () { for (size_type n = 0 ; n < buckets.size (); ++n) if (buckets[n]) return iterator (buckets[n], this ); return end (); } iterator end () { return iterator (0 , this ); } const_iterator begin () const { for (size_type n = 0 ; n < buckets.size (); ++n) if (buckets[n]) return const_iterator (buckets[n], this ); return end (); } const_iterator end () const { return const_iterator (0 , this ); } size_type bucket_count () const { return buckets.size (); } size_type max_bucket_count () const { return __stl_prime_list[__stl_num_primes - 1 ]; } size_type elems_in_bucket (size_type bucket) const { size_type result = 0 ; for (node* cur = buckets[bucket]; cur; cur = cur->next) result += 1 ; return result; } ... };
hashtable 具体方法 重载操作符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 template <class Value , class Key , class HashFcn , class ExtractKey , class EqualKey , class Alloc >class hashtable { ... public : hashtable& operator = (const hashtable& ht) { if (&ht != this ) { clear (); hash = ht.hash; equals = ht.equals; get_key = ht.get_key; copy_from (ht); } return *this ; } friend bool operator == __STL_NULL_TMPL_ARGS (const hashtable&, const hashtable&); ... }; 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; if (!cur) { size_type bucket = ht->bkt_num (old->val); while (!cur && ++bucket < ht->buckets.size ()) cur = ht->buckets[bucket]; } return *this ; } template <class V , class K , class HF , class ExK , class EqK , class A >inline __hashtable_iterator<V, K, HF, ExK, EqK, A>__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator ++(int ) { iterator tmp = *this ; ++*this ; return tmp; } template <class V , class K , class HF , class Ex , class Eq , class A >bool operator ==(const hashtable<V, K, HF, Ex, Eq, A>& ht1, const hashtable<V, K, HF, Ex, Eq, A>& ht2) { typedef typename hashtable<V, K, HF, Ex, Eq, A>::node node; if (ht1.buckets.size () != ht2.buckets.size ()) return false ; for (int n = 0 ; n < ht1.buckets.size (); ++n) { node* cur1 = ht1.buckets[n]; node* cur2 = ht2.buckets[n]; for ( ; cur1 && cur2 && cur1->val == cur2->val; cur1 = cur1->next, cur2 = cur2->next) {} if (cur1 || cur2) return false ; } return true ; }
重新分配 resize(int)
由于上文中说到, 当 hashtable 中的元素个数超过 桶 bucket 大小时, 会重新给 hashtable 分配更大的容量并将所有元素重新分配到新的 bucket 里, 对于设计者来说, 这是一个经验之谈.
但是扩容也带来了性能的不稳定性, 一旦出现大量重复插入的数据, 将导致对于内存和时间的浪费, 如果打过 codeforces
的同学可以知道, unordered_map
会被 hack 掉. 所以对于特定的数据或者场景, 还是慎用 hashtable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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); if (n > old_n) { vector<node*, A> tmp (n, (node*) 0 ) ; __STL_TRY { for (size_type bucket = 0 ; bucket < old_n; ++bucket) { node* first = buckets[bucket]; while (first) { size_type new_bucket = bkt_num (first->val, n); buckets[bucket] = first->next; first->next = tmp[new_bucket]; tmp[new_bucket] = first; first = buckets[bucket]; } } buckets.swap (tmp); } # ifdef __STL_USE_EXCEPTIONS catch (...) { for (size_type bucket = 0 ; bucket < tmp.size (); ++bucket) { while (tmp[bucket]) { node* next = tmp[bucket]->next; delete_node (tmp[bucket]); tmp[bucket] = next; } } throw ; } # endif } } }
插入
hashtable
也有 insert_equal
和 insert_unique
两种插入方式
insert_equal: 插入返回的是插入位置的迭代器, 且插入时, 会将所有相同值的节点全部放在一起
insert_unique: 插入返回的是一个 pair
first 为插入后新元素的迭代器, second 为 bool 表示是否插入成功
同时提供了 ForwardIterator
和 InputIterator
的类型迭代器相对应的不同插入方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 template <class Value , class Key , class HashFcn , class ExtractKey , class EqualKey , class Alloc >class hashtable { ... public : pair<iterator, bool > insert_unique (const value_type& obj) { resize (num_elements + 1 ); return insert_unique_noresize (obj); } iterator insert_equal (const value_type& obj) { resize (num_elements + 1 ); return insert_equal_noresize (obj); } pair<iterator, bool > insert_unique_noresize (const value_type& obj) ; iterator insert_equal_noresize (const value_type& obj) ; #ifdef __STL_MEMBER_TEMPLATES template <class InputIterator > void insert_unique (InputIterator f, InputIterator l) { insert_unique (f, l, iterator_category (f)); } template <class InputIterator > void insert_equal (InputIterator f, InputIterator l) { insert_equal (f, l, iterator_category (f)); } template <class InputIterator > void insert_unique (InputIterator f, InputIterator l,input_iterator_tag) { for ( ; f != l; ++f) insert_unique (*f); } template <class InputIterator > void insert_equal (InputIterator f, InputIterator l,input_iterator_tag) { for ( ; f != l; ++f) insert_equal (*f); } template <class ForwardIterator > void insert_unique (ForwardIterator f, ForwardIterator l,forward_iterator_tag) { size_type n = 0 ; distance (f, l, n); resize (num_elements + n); for ( ; n > 0 ; --n, ++f) insert_unique_noresize (*f); } template <class ForwardIterator > void insert_equal (ForwardIterator f, ForwardIterator l,forward_iterator_tag) { size_type n = 0 ; distance (f, l, n); resize (num_elements + n); for ( ; n > 0 ; --n, ++f) insert_equal_noresize (*f); } #else void insert_unique (const value_type* f, const value_type* l) { size_type n = l - f; resize (num_elements + n); for ( ; n > 0 ; --n, ++f) insert_unique_noresize (*f); } void insert_equal (const value_type* f, const value_type* l) { size_type n = l - f; resize (num_elements + n); for ( ; n > 0 ; --n, ++f) insert_equal_noresize (*f); } void insert_unique (const_iterator f, const_iterator l) { size_type n = 0 ; distance (f, l, n); resize (num_elements + n); for ( ; n > 0 ; --n, ++f) insert_unique_noresize (*f); } void insert_equal (const_iterator f, const_iterator l) { size_type n = 0 ; distance (f, l, n); resize (num_elements + n); for ( ; n > 0 ; --n, ++f) insert_equal_noresize (*f); } #endif ... };
可以发现, 上面所有的方法都调用了 insert_***_noresize
insert_unique_noresize
不可重复插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 template <class V , class K , class HF , class Ex , class Eq , class A >pair<typename hashtable<V, K, HF, Ex, Eq, A>::iterator, bool > hashtable<V, K, HF, Ex, Eq, A>::insert_unique_noresize (const value_type& obj) { const size_type n = bkt_num (obj); node* first = buckets[n]; for (node* cur = first; cur; cur = cur->next) if (equals (get_key (cur->val), get_key (obj))) return pair<iterator, bool >(iterator (cur, this ), false ); node* tmp = new_node (obj); tmp->next = first; buckets[n] = tmp; ++num_elements; return pair<iterator, bool >(iterator (tmp, this ), true ); }
insert_equal_noresize
可重复插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 template <class V , class K , class HF , class Ex , class Eq , class A >typename hashtable<V, K, HF, Ex, Eq, A>::iterator hashtable<V, K, HF, Ex, Eq, A>::insert_equal_noresize (const value_type& obj) { const size_type n = bkt_num (obj); node* first = buckets[n]; for (node* cur = first; cur; cur = cur->next) if (equals (get_key (cur->val), get_key (obj))) { node* tmp = new_node (obj); tmp->next = cur->next; cur->next = tmp; ++num_elements; return iterator (tmp, this ); } node* tmp = new_node (obj); tmp->next = first; buckets[n] = tmp; ++num_elements; return iterator (tmp, this ); }
查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 template <class Value , class Key , class HashFcn , class ExtractKey , class EqualKey , class Alloc >class hashtable { ... public : iterator find (const key_type& key) { size_type n = bkt_num_key (key); node* first; for ( first = buckets[n];first && !equals (get_key (first->val), key); first = first->next) {} return iterator (first, this ); } ... };
find_or_insert
: 如果找到了, 就返回该元素数据, 反之就将指定元素插入到链表的头部, 再返回元素默认数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 template <class V , class K , class HF , class Ex , class Eq , class A >typename hashtable<V, K, HF, Ex, Eq, A>::reference hashtable<V, K, HF, Ex, Eq, A>::find_or_insert (const value_type& obj) { resize (num_elements + 1 ); size_type n = bkt_num (obj); node* first = buckets[n]; for (node* cur = first; cur; cur = cur->next) if (equals (get_key (cur->val), get_key (obj))) return cur->val; node* tmp = new_node (obj); tmp->next = first; buckets[n] = tmp; ++num_elements; return tmp->val; }
删除
erase有很多个重载函数, 这里就具体分析一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 template <class Value , class Key , class HashFcn , class ExtractKey , class EqualKey , class Alloc >class hashtable { ... public : size_type erase (const key_type& key) ; void erase (const iterator& it) ; void erase (iterator first, iterator last) ; void erase (const const_iterator& it) ; void erase (const_iterator first, const_iterator last) ; void resize (size_type num_elements_hint) ; void clear () ; ... };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 template <class V , class K , class HF , class Ex , class Eq , class A >typename hashtable<V, K, HF, Ex, Eq, A>::size_type hashtable<V, K, HF, Ex, Eq, A>::erase (const key_type& key) { const size_type n = bkt_num_key (key); node* first = buckets[n]; size_type erased = 0 ; if (first) { node* cur = first; node* next = cur->next; while (next) { if (equals (get_key (next->val), key)) { cur->next = next->next; delete_node (next); next = cur->next; ++erased; --num_elements; } else { cur = next; next = cur->next; } } if (equals (get_key (first->val), key)) { buckets[n] = first->next; delete_node (first); ++erased; --num_elements; } } return erased; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 template <class V , class K , class HF , class Ex , class Eq , class A >void hashtable<V, K, HF, Ex, Eq, A>::erase_bucket (const size_type n, node* last) { node* cur = buckets[n]; while (cur != last) { node* next = cur->next; delete_node (cur); cur = next; buckets[n] = cur; --num_elements; } } template <class V , class K , class HF , class Ex , class Eq , class A >void hashtable<V, K, HF, Ex, Eq, A>::clear (){ for (size_type i = 0 ; i < buckets.size (); ++i) { node* cur = buckets[i]; while (cur != 0 ) { node* next = cur->next; delete_node (cur); cur = next; } buckets[i] = 0 ; } num_elements = 0 ; }
可以发现, 其操作本质还是找到对应的桶后, 进行链表操作.
复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 template <class V , class K , class HF , class Ex , class Eq , class A >void hashtable<V, K, HF, Ex, Eq, A>::copy_from (const hashtable& ht){ buckets.clear (); buckets.reserve (ht.buckets.size ()); buckets.insert (buckets.end (), ht.buckets.size (), (node*) 0 ); __STL_TRY { for (size_type i = 0 ; i < ht.buckets.size (); ++i) { if (const node* cur = ht.buckets[i]) { node* copy = new_node (cur->val); buckets[i] = copy; for (node* next = cur->next; next; cur = next, next = cur->next) { copy->next = new_node (next->val); copy = copy->next; } } } num_elements = ht.num_elements; } __STL_UNWIND(clear ()); }
总结
hashtbale
选用开链法来处理 hash 冲突
采用质数作为桶的容量, 并在元素个数大于桶大小时扩容并进行 rehash
与 RB-tree
:
再插入过程中, 前者平均是 O(1), 后者为 O(nlogn)
前者插入是无序的, 后者是有序的
前者桶满后效率很低, 后者不会考虑到满
两者都实现了可重复和不可重复
前者正向迭代器, 没有 operator--
, 后者迭代器实现了 operator--
至于 hash function , 又其他头文件定义, hashtable 只是进行调用, 并不属于 hashtable 容器, 所以不再进行
分析, 且对于一些类型, 标准库并没有提供 hash function
(标准库不可能提前知道你要建立的类型是什么) 需要用户自己去定义 hash function