当前位置:   article > 正文

学习->C++篇十六:哈希表与unordered_map、unordered_set_c++ 哈希表unordered

c++ 哈希表unordered

目录

 

1.哈希表概念          

2.常用哈希函数:

解决哈希冲突的两个方法:

一.闭散列法(开放定址法):

二.开散列法(哈希桶):

3.封装unordered_map,unordered_set


 1.哈希表概念          

顺序结构以及平衡树中,元素键值与其存储位置之间没有对应的关系,因此在查找一个元素
时,必须要经过键值的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即
O(log2N),搜索的效率取决于搜索过程中元素的比较次数,倘若无需进行多次比较,直接从表中获得所需的元素,这就需将键值和存储位置建立映射关系,以便快速查找元素。
        该方法为 哈希法(散列法),哈希方法中使用将键值转换为存储位置的转换函数称为哈希(散列)函数,构造出来的结构称 为 哈希表(或散列表)
        哈希冲突:不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突 或哈希碰撞。

2.常用哈希函数

        1. 直接定址法--(常用) 取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
优点:简单、均匀
缺点:需要事先知道关键字的分布情况
使用场景:适合查找比较小且连续的情况
        2. 除留余数法--(常用) 设散列表中的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数, 按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。

解决哈希冲突的两个方法:

一.闭散列法(开放定址法):

1.线性探测法:
代码模拟实现:
  1. #pragma once
  2. #include<unordered_map>
  3. #include<vector>
  4. using std::vector;
  5. using std::pair;
  6. //线性探测法
  7. //
  8. //给每一个空间标记
  9. enum state{EXIST,DELETE,EMPTY};
  10. template<class K,class V>
  11. class hashtable
  12. {
  13. //表中存储elem结构,包含pair键值对和标记状态的state
  14. struct elem
  15. {
  16. pair<K, V> _val;
  17. state _state;
  18. };
  19. public:
  20. hashtable(size_t capacity=3)
  21. :_ht(capacity)
  22. ,_size(0)
  23. {
  24. for (size_t i = 0; i < capacity; i++)
  25. {
  26. _ht[i]._state = EMPTY;
  27. }
  28. }
  29. ~hashtable()
  30. {}
  31. bool Insert(const pair<K, V>& val)
  32. {
  33. checkCapacity();
  34. size_t hashi = val.first % _ht.size();
  35. while (_ht[hashi]._state != EMPTY)
  36. {
  37. if (_ht[hashi]._state == EXIST
  38. && _ht[hashi]._val.first == val.first)
  39. return false;
  40. ++hashi;
  41. if (hashi == _ht.size())
  42. hashi = 0;
  43. }
  44. _ht[hashi]._state = EXIST;
  45. _ht[hashi]._val = val;
  46. _size++;
  47. return true;
  48. }
  49. elem* Find(const K& k)
  50. {
  51. if (_ht.size() == 0)
  52. return nullptr;
  53. size_t hashi = k % _ht.size();
  54. while (_ht[hashi]._state != EMPTY)
  55. {
  56. if (_ht[hashi]._state == EXIST && _ht[hashi]._val.first == k)
  57. return &_ht[hashi];
  58. ++hashi;
  59. if (hashi == _ht.size())
  60. hashi = 0;
  61. }
  62. return nullptr;
  63. }
  64. private:
  65. void checkCapacity()
  66. {
  67. if (_size * 10 / _ht.size() >= 7)
  68. {
  69. size_t newsize = _ht.size() * 2;
  70. hashtable<K,V>newht(newsize);
  71. for (auto e : _ht)
  72. {
  73. if (e._state == EXIST)
  74. newht.Insert({ e._val.first,e._val.second });
  75. }
  76. newht._size = _size;
  77. newht._ht.swap(_ht);
  78. }
  79. }
  80. bool Erase(const K&k)
  81. {
  82. if (elem* p = Find(k))
  83. {
  84. p->_state = DELETE;
  85. --_size;
  86. return true;
  87. }
  88. return false;
  89. }
  90. size_t HashFunc(const K& k)
  91. {
  92. return k % _ht.size();
  93. }
  94. private:
  95. vector<elem>_ht;
  96. size_t _size;
  97. };
2.二次探测法:
与线性探测法类似,将插入和查找的探测元素的顺序变成hashi,hashi+1^2,hashi+2^2,hashi+3^2......即可,记得计算好的哈希地址对哈希表的总大小取模,代码如下:
  1. //二次探测法
  2. bool Insert(const pair<K, V>& val)
  3. {
  4. checkCapacity();
  5. size_t hashi = val.first % _ht.size();
  6. size_t index = hashi;
  7. int i = 0;
  8. while (_ht[hashi]._state != EMPTY)
  9. {
  10. if (_ht[hashi]._state == EXIST
  11. && _ht[hashi]._val.first == val.first)
  12. return false;
  13. ++i;
  14. hashi=index+i*i;
  15. hashi %= _ht.size();
  16. }
  17. _ht[hashi]._state = EXIST;
  18. _ht[hashi]._val = val;
  19. _size++;
  20. return true;
  21. }
  22. //二次探测法
  23. elem* Find(const K& k)
  24. {
  25. if (_ht.size() == 0)
  26. return nullptr;
  27. size_t hashi = k % _ht.size();
  28. size_t index = hashi;
  29. int i = 0;
  30. while (_ht[hashi]._state != EMPTY)
  31. {
  32. if (_ht[hashi]._state == EXIST && _ht[hashi]._val.first == k)
  33. return &_ht[hashi];
  34. i++;
  35. hashi=index+i*i;
  36. hashi %= _ht.size();
  37. }
  38. return nullptr;
  39. }

二.开散列法(哈希桶):

开散列法:首先对关键码的集合通过哈希函数映射到对应地址,具有相同地址的的关键码归于同一个子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。(即将哈希冲突的元素放在单链表中)

简单实现:

  1. namespace openhash
  2. {
  3. template<class V>
  4. struct hashBucketNode
  5. {
  6. hashBucketNode(const V& data)
  7. :_next(nullptr)
  8. , _data(data)
  9. {}
  10. hashBucketNode<V>* _next;
  11. V _data;
  12. };
  13. template<class V>
  14. class hashBucket
  15. {
  16. typedef hashBucketNode<V> Node;
  17. public:
  18. hashBucket(size_t capacity = 3)
  19. :_size(0)
  20. {
  21. _ht.resize(capacity, nullptr);
  22. }
  23. Node* insert(const V& data)
  24. {
  25. //几号桶
  26. size_t Bucketi = data % _ht.size();
  27. Node* cur = _ht[Bucketi];
  28. while (cur)
  29. {
  30. if (cur->_data == data)
  31. return cur;
  32. cur = cur->_next;
  33. }
  34. Node* newnode = new Node(data);
  35. newnode->_next = _ht[Bucketi];
  36. _ht[Bucketi] = newnode;
  37. _size++;
  38. return newnode;
  39. }
  40. Node* erase(const V& data)
  41. {
  42. size_t Bucketi = data % _ht.size();
  43. Node* cur = _ht[Bucketi], prev = nullptr;
  44. while (cur)
  45. {
  46. if (cur->_data == data)
  47. {
  48. if (prev)
  49. prev->_next = cur->_next;
  50. else
  51. _ht[Bucketi] = cur->_next;
  52. //返回删除元素的下一个节点
  53. prev = cur->_next;
  54. delete cur;
  55. --_size;
  56. return prev;
  57. }
  58. }
  59. return nullptr;
  60. }
  61. Node* Find(const V& data)
  62. {
  63. size_t Bucketi = data % _ht.size();
  64. Node* cur = _ht[Bucketi];
  65. while (cur)
  66. {
  67. if (cur->_data == data)
  68. return cur;
  69. cur = cur->_next;
  70. }
  71. return nullptr;
  72. }
  73. size_t size()const { return _size; }
  74. bool Empty()const { return _size == 0; }
  75. void clear()
  76. {
  77. for (int i = 0; i < _ht.size(); i++)
  78. {
  79. while (_ht[i])
  80. {
  81. Node* cur = _ht[i];
  82. _ht[i] = cur->_next;
  83. delete cur;
  84. }
  85. }
  86. _size = 0;
  87. }
  88. ~hashBucket() { clear(); }
  89. private:
  90. size_t hashFunc(const V& data) { return data % _ht.size(); }
  91. private:
  92. vector<Node*> _ht;
  93. size_t _size;
  94. };
  95. }

开散列法的扩容:

因为扩容时桶的总数改变了,每个元素需要重新映射地址,所以有大的性能消耗。理想情况下是每个桶一个元素,于是,可以在桶的个数等于存储的元素个数的时候进行哈希表的扩容操作。

扩容代码:(将原链表节点一个个插入到新的哈希桶)

  1. size_t BucketCount()const
  2. {
  3. size_t bc = 0;
  4. for (auto p : _ht)
  5. if (p)++bc;
  6. return bc;
  7. }
  8. private:
  9. void checkCapacity()
  10. {
  11. if (BucketCount() == _size)
  12. {
  13. vector<Node*>newht(_size == 0 ? 3:_size * 2);
  14. for (auto& p : _ht)
  15. {
  16. Node* pnode = p;
  17. while (pnode)
  18. {
  19. p = p->_next;
  20. size_t Bucketi = pnode->_data % newht.size();
  21. pnode->_next = newht[Bucketi];
  22. newht[Bucketi] = pnode;
  23. pnode = p;
  24. }
  25. }
  26. _ht.swap(newht);
  27. }
  28. }

3.封装unordered_map,unordered_set

上述实现是将key取模得到存储地址,如果要处理字符串,则需提供字符串哈希函数,如果要处理自定义类型也需提供哈希函数,可以通过传入仿函数实现,为封装unordered_map和unordered_set还需实现迭代器,与红黑树类似,还需有一个取value中key的仿函数。
简单实现封装如下:
  1. //hashtable11-3.h
  2. namespace openhash
  3. {
  4. template<class T>
  5. struct HashFunc
  6. {
  7. //默认的哈希函数
  8. size_t operator()(const T& val) { return val; }
  9. };
  10. //针对字符串哈希函数->模板特化
  11. template<>
  12. struct HashFunc<string>
  13. {
  14. size_t operator()(const string& s)
  15. {
  16. const char* str = s.c_str();
  17. size_t seed = 31;
  18. size_t hash = 0;
  19. while (*str)
  20. {
  21. hash = hash * seed + (*str++);
  22. }
  23. return hash;
  24. }
  25. };
  26. //定义节点:存储的T可能是K,也可能是pair<K,V>
  27. template<class T>
  28. struct HashNode
  29. {
  30. HashNode(const T&data)
  31. :_next(nullptr)
  32. ,_data(data)
  33. {}
  34. HashNode<T>* _next;
  35. T _data;
  36. };
  37. //迭代器需用到哈希桶,要做前置声明
  38. template<class K,class T,class KeyOfT,class HashFunc>
  39. class HashBucket;
  40. //实现迭代器,其中有一个节点指针和哈希桶的指针
  41. template<class K,class T,class KeyOfT,class HashFunc>
  42. class __HBIterator
  43. {
  44. typedef HashNode<T> Node;
  45. typedef HashBucket<K, T, KeyOfT, HashFunc>* Phb;
  46. typedef __HBIterator<K, T, KeyOfT, HashFunc> Self;
  47. public:
  48. __HBIterator(){}
  49. __HBIterator(Node*node,Phb phb)
  50. :_node(node),_phb(phb)
  51. {}
  52. //单向迭代器只需实现++
  53. Self& operator++()//前置加加
  54. {
  55. assert(_node);//当_node为空(即end,不可以++)
  56. if (_node->_next)
  57. _node = _node->_next;
  58. else
  59. {
  60. KeyOfT kot;
  61. HashFunc hf;
  62. size_t sz = _phb->_table.size();
  63. //计算下一个桶
  64. size_t hashi = hf(kot(_node->_data)) % sz + 1;
  65. //寻找下一个不为nullptr的桶
  66. while (hashi < sz && !_phb->_table[hashi])
  67. ++hashi;
  68. if (hashi == sz)
  69. _node = nullptr;//已经走完最后一个哈希桶
  70. else
  71. _node = _phb->_table[hashi];
  72. }
  73. return *this;
  74. }
  75. Self& operator++(int)//后置加加
  76. {
  77. Self ret(_node, _phb);
  78. operator++();
  79. return ret;
  80. }
  81. T& operator*() { return _node->_data; }
  82. T* operator->() { return &_node->_data; }
  83. bool operator!=(const Self& s) const { return _node != s._node; }
  84. bool operator==(const Self& s) const { return _node == s._node; }
  85. private:
  86. Node* _node;
  87. Phb _phb;
  88. };
  89. template<class K,class T,class KeyOfT,class HashFunc>
  90. class HashBucket
  91. {
  92. template<class K,class T,class KeyOfT,class hashFunc>
  93. friend class __HBIterator;//将迭代器作为哈希桶的友元类模板,以便能访问哈希表(遍历桶)
  94. typedef HashNode<T> Node;
  95. public:
  96. typedef __HBIterator<K, T, KeyOfT, HashFunc> Iterator;
  97. Iterator begin()
  98. {
  99. for (auto p : _table)
  100. if (p)return Iterator(p, this);
  101. return Iterator(nullptr, this);
  102. }
  103. Iterator end() { return Iterator(nullptr, this); }
  104. HashBucket(size_t capacity = 0)
  105. :_table(GetNextPrime(_table.size()),nullptr)
  106. ,_size(0)
  107. {}
  108. ~HashBucket() { Clear(); }
  109. void Clear()
  110. {
  111. for (size_t i = 0; i < _table.size(); i++)
  112. {
  113. while (_table[i])
  114. {
  115. Node* cur = _table[i];
  116. _table[i] = cur->_next;
  117. delete cur;
  118. }
  119. }
  120. _size = 0;
  121. }
  122. Iterator find(const K& key)
  123. {
  124. HashFunc hf;
  125. size_t hashi = hf(key) % _table.size();
  126. Node* cur = _table[hashi];
  127. while (cur)
  128. {
  129. if (KeyOfT()(cur->_data) == key)
  130. return Iterator(cur, this);
  131. cur = cur->_next;
  132. }
  133. return Iterator(nullptr, this);
  134. }
  135. pair<Iterator, bool> insert(const T& data)
  136. {
  137. Iterator it = find(KeyOfT()(data));
  138. if (it != end())
  139. return make_pair(it, false);
  140. checkCapacity();
  141. Node* newnode = new Node(data);
  142. size_t hashi = HashFunc()(KeyOfT()(data)) % _table.size();
  143. newnode->_next = _table[hashi];
  144. _table[hashi] = newnode;
  145. ++_size;
  146. return make_pair(Iterator(newnode, this), true);
  147. }
  148. size_t erase(const K& key)
  149. {
  150. KeyOfT kot;
  151. HashFunc hf;
  152. size_t hashi = hf(key)%_table.size();
  153. Node* prev = nullptr;
  154. Node* cur = _table[hashi];
  155. while (cur)
  156. {
  157. if (kot(cur->_data) == key)
  158. {
  159. if (prev)
  160. prev->_next = cur->_next;
  161. else
  162. _table[hashi] = cur->_next;
  163. delete cur;
  164. --_size;
  165. return 1;
  166. }
  167. prev = cur;
  168. cur = cur->_next;
  169. }
  170. return 0;
  171. }
  172. private:
  173. //将桶的数量设置为一个素数,据说能减少哈希冲突
  174. size_t GetNextPrime(size_t prime)
  175. {
  176. const int PRIMECOUNT = 28;
  177. //素数表
  178. static const size_t primeList[PRIMECOUNT] =
  179. {
  180. 53ul, 97ul, 193ul, 389ul, 769ul,
  181. 1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
  182. 49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
  183. 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
  184. 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
  185. 1610612741ul, 3221225473ul, 4294967291ul
  186. };
  187. // 获取比prime大那一个素数
  188. size_t i = 0;
  189. for (; i < PRIMECOUNT; ++i)
  190. {
  191. if (primeList[i] > prime)
  192. return primeList[i];
  193. }
  194. return primeList[i];
  195. }
  196. void checkCapacity()
  197. {
  198. if (_size == _table.size())
  199. {
  200. KeyOfT kot;
  201. HashFunc hf;
  202. vector<Node*>newtable(GetNextPrime(_table.size()),nullptr);
  203. for (auto p : _table)
  204. {
  205. auto cur = p;
  206. while (cur)
  207. {
  208. Node* next = cur->_next;
  209. cur->_next = newtable[hf(kot(p->_data))];
  210. newtable[hf(kot(p->_data))] = cur;
  211. cur = next;
  212. }
  213. }
  214. newtable.swap(_table);
  215. }
  216. }
  217. private:
  218. vector<Node*>_table;
  219. size_t _size;
  220. };
  221. }
封装unordered_map和unordered_set类似:
unordered_map主要要多实现一个operator[]函数,注意这个函数是先调用insert实现的。
  1. #pragma once
  2. #include"hashtable11-3.h"
  3. namespace openhash
  4. {
  5. template<class K, class V, class HashFunc = HashFunc<K> >
  6. class unordered_map
  7. {
  8. struct MapKeyOfT { const K& operator()(const pair<K, V>& p) { return p.first; } };
  9. public:
  10. typedef typename HashBucket<K, pair<K, V>, MapKeyOfT, HashFunc>::Iterator iterator;
  11. iterator begin() { return _hb.begin(); }
  12. iterator end() { return _hb.end(); }
  13. iterator find(const K& key) {
  14. return _hb.find(key);
  15. }
  16. size_t erase(const K& key) {
  17. return _hb.erase(key);
  18. }
  19. pair<iterator, bool>insert(const pair<K, V>& p) {
  20. return _hb.insert(p);
  21. }
  22. V& operator[](const K& k) {
  23. pair<iterator, bool>p = insert(make_pair(k, V()));
  24. return p.first->second;
  25. }
  26. private:
  27. HashBucket<K, pair<K, V>, MapKeyOfT, HashFunc>_hb;
  28. };
  29. }
  1. #pragma once
  2. namespace openhash
  3. {
  4. #include"hashtable11-3.h"
  5. template<class K, class HashFunc = HashFunc<K> >
  6. class unordered_set
  7. {
  8. struct SetKeyOfT { const K& operator()(const K& k) { return k; } };
  9. public:
  10. typedef typename HashBucket<K,K,SetKeyOfT, HashFunc>::Iterator iterator;
  11. iterator begin() { return _hb.begin(); }
  12. iterator end() { return _hb.end(); }
  13. iterator find(const K& key) {
  14. return _hb.find(key);
  15. }
  16. size_t erase(const K& key) {
  17. return _hb.erase(key);
  18. }
  19. pair<iterator, bool>insert(const K& k) {
  20. return _hb.insert(k);
  21. }
  22. private:
  23. HashBucket<K, K, SetKeyOfT, HashFunc>_hb;
  24. };
  25. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/555617
推荐阅读
相关标签
  

闽ICP备14008679号