当前位置:   article > 正文

C++哈希表模拟实现unordered_map 与unordered_set

C++哈希表模拟实现unordered_map 与unordered_set

哈希概念

unordered系列的关联式容器(如unordered_map unordered_set) 之所以效率比较高,是因为其底层使用了哈希结构

顺序结构以及平衡树 中,元素关键码与其存储位置之间没有对应的关系,因此在 查找一个元素
时,必须要经过关键码的多次比较
理想的搜索方法:可以 不经过任何比较 一次 直接从表中得到要搜索的元素
哈希/散列:关键值与另一个值,建立一个关联关系 是一种思想
哈希表/散列表:关键字和存储位置建立一个关联关系
哈希 ( 散列 ) 方法:通过某种函数(hashFunc) 使元素的存储位置与它的关键值之间能够建立
一一映射的关系,那么在查找时 通过该函数 可以 很快找到该元素
哈希方法中:使用的转换函数称作 哈希(散列)函数 ,构造出来的结构 哈希表 (Hash Table)(or 散列表 )
向该结构中:
插入元素: 根据待插入元素的 关键码 ,以此 函数计算出 该元素的 存储位置 并按此位置进行存放
搜索元素: 根据待插入元素的 关键码 ,以此 函数计算出 该元素的 存储位置, 在结构中按此位置取元素比较,若关键码相等,则搜索成功

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快

如若再插入99,则会发生哈希冲突

哈希冲突 

不同关键字 通过相同哈希函数计算出 相同的哈希地址 ,该种现象称为哈希冲突 或哈希碰撞

引起哈希冲突的一个原因可能是: 哈希函数设计不够合理

常见哈希函数 

1. 直接定址法--(常用)  
Hash Key = A*Key + B
使用场景:关键字范围集中且数据量不大的情况
缺点:若是值很分散,那么此方法会导致空间开很大,浪费
关键字与存储位置是一对一的关系,不存在哈希冲突
2. 除留余数法--(常用)
设散列表中允许的 地址数为 m ,取一个不大于 m ,但最接近或者等于 m 的质数 p 作为除数
Hash(key) = key% p(p<=m)
关键字可以分散,数据量可以很大
关键字与存储位置是多对一的关系,存在哈希冲突
3. 平方取中法 --( 了解 )
假设关键字为 1234 ,对它平方就是 1522756 ,抽取中间的 3 227 作为哈希地址;
再比如关键字为 4321 ,对它平方就是 18671041 ,抽取中间的 3 671( 710) 作为哈希地址
平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况
4. 折叠法 --( 了解 )
折叠法是将关键字从左到右分割成位数相等的几部分 ( 最后一部分位数可以短些 ) ,然后将这
几部分叠加求和,并按散列表表长,取后几位作为散列地址。
折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况
5. 随机数法 --( 了解 )
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 H(key) = random(key), 其中
random为随机数函数 
通常应用于关键字长度不等时采用此法
总之,哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

哈希冲突解决

两种方法:闭散列开散列

闭散列(开放定址法)

当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有 空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去

寻找下一个空位置:hashi为元素使用哈希函数映射出的地址

1. 线性探测 hashi+i (i>=0)

2. 二次探测 hashi+i^2 (i>=0)

线性探测:

从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止

插入:通过哈希函数获取待插入元素在哈希表中的位置

           如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突

           则使用线性探测找到下一个空位置,插入新元素

删除 :采用伪删除法

采用闭散列处理哈希冲突时, 不能随便物理删除 哈希表中已有的元素,若直接删除元素
会影响其他元素的搜索 。比如删除元素 4 ,如果直接删除掉,则下标为4的空间为空,当查找44时,直接就是找到了一个空位置,查找结束,找不到,但实际上元素44是应该要被搜索成功的
响。因此 线性探测采用标记的 伪删除法 来删除一个元素
删除状态的意义:1 再插入,这个位置可以覆盖值
                             2 防止后面冲突的值,出现找不到的情况,遇到删除状态,还是继续往后找
  1. enum Status//标记存储值的状态
  2. {
  3. EMPTY,//空
  4. EXIST,//存在
  5. DELETE//删除
  6. };

哈希表设计代码(开放定址法/闭散列解决哈希冲突)

  1. #pragma once
  2. #include<string>
  3. #include<vector>
  4. #include<iostream>
  5. using namespace std;
  6. template<class K>
  7. struct HashFunc
  8. {
  9. size_t operator()(const K& key)
  10. {
  11. return (size_t)key;
  12. }
  13. };
  14. template<>
  15. struct HashFunc<string>//将字符串映射成一个整型
  16. {
  17. size_t operator()(const string& key)
  18. {
  19. size_t hash = 0;
  20. for (auto e : key)
  21. {
  22. hash *= 31;
  23. hash += e;
  24. }
  25. return hash;
  26. }
  27. };
  28. namespace open_address//开放定址法/闭散列
  29. {
  30. enum Status//标记存储值的状态
  31. {
  32. EMPTY,//空
  33. EXIST,//存在
  34. DELETE//删除
  35. };
  36. template<class K,class V>
  37. struct HashData
  38. {
  39. pair<K, V> _kv;
  40. Status _s;//状态
  41. };
  42. template<class K,class V,class Hash = HashFunc<K>>
  43. class HashTable
  44. {
  45. public:
  46. HashTable()
  47. {
  48. _tables.resize(10);
  49. }
  50. bool Insert(const pair<K, V>& kv)
  51. {
  52. //检查是否已经存在
  53. if (Find(kv.first))
  54. return false;
  55. //检查是否需要扩容
  56. // 负载因子0.7就扩容
  57. if (_n * 10 / _tables.size() == 7)
  58. {
  59. size_t newSize = _tables.size() * 2;
  60. HashTable<K, V, Hash> newHT;
  61. newHT._tables.resize(newSize);
  62. //遍历旧表
  63. for (size_t i = 0; i < _tables.size(); i++)
  64. {
  65. if (_tables[i]._s == EXIST)
  66. newHT.Insert(_tables[i]._kv);
  67. }
  68. _tables.swap(newHT._tables);
  69. }
  70. //插入
  71. Hash hf;
  72. size_t hashi = hf(kv.first) % _tables.size();
  73. while (_tables[hashi]._s == EXIST)
  74. {
  75. ++hashi;
  76. hashi %= _tables.size();
  77. }
  78. _tables[hashi]._kv = kv;
  79. _tables[hashi]._s = EXIST;
  80. ++_n;
  81. return true;
  82. }
  83. HashData<K, V>* Find(const K& key)//若是一次性找不到,则一直找到空就结束
  84. {
  85. Hash hf;
  86. size_t hashi = hf(key) % _tables.size();
  87. while (_tables[hashi]._s !=EMPTY)
  88. {
  89. if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key)
  90. {
  91. return &_tables[hashi];
  92. }
  93. hashi++;
  94. hashi %= _tables.size();
  95. }
  96. return nullptr;
  97. }
  98. // 伪删除法
  99. bool Erase(const K& key)
  100. {
  101. HashData<K, V>* ret = Find(key);
  102. if (ret)
  103. {
  104. ret->_s = DELETE;
  105. --_n;
  106. return true;
  107. }
  108. else
  109. {
  110. return false;
  111. }
  112. }
  113. void Print()
  114. {
  115. for (size_t i = 0; i < _tables.size(); i++)
  116. {
  117. if (_tables[i]._s == EXIST)
  118. {
  119. cout << "[" << i << "]->" << _tables[i]._kv.first << ":" << _tables[i]._kv.second << endl;
  120. }
  121. else if (_tables[i]._s == EMPTY)
  122. {
  123. printf("[%d]->\n", i);
  124. }
  125. else
  126. {
  127. printf("[%d]->D\n", i);
  128. }
  129. }
  130. cout << endl;
  131. }
  132. private:
  133. vector<HashData<K,V>> _tables;
  134. size_t _n = 0;// 存储的关键字的个数
  135. };
  136. }

哈希表的扩容问题:

负载因子:存储关键字个数/空间大小,当负载因子为0.7时就扩容

开散列(链地址法/开链法/拉链法)

首先对关键码集合用散列函数 计算散列地址 ,具有 相同地址的关键码归于 同一子集合,每一个子集合称为 一个桶 ,各个桶中的元素通过一个 单链表链接 起来

开散列中每个桶中放的都是发生哈希冲突的元素

哈希表(开散列解决哈希冲突)模拟实现unordered_map 与unordered_set

HashTable.h

  1. #pragma once
  2. #include<string>
  3. #include<vector>
  4. #include<iostream>
  5. using namespace std;
  6. template<class K>
  7. struct HashFunc // 哈希函数采用除留余数法,被模的key必须要为整形才可以处理
  8. {
  9. size_t operator()(const K& key)
  10. {
  11. return (size_t)key;
  12. }
  13. };
  14. template<>
  15. struct HashFunc<string>//将字符串映射成一个整型 (若key为字符串)
  16. {
  17. size_t operator()(const string& key)
  18. {
  19. size_t hash = 0;
  20. for (auto e : key)
  21. {
  22. hash *= 31;
  23. hash += e;
  24. }
  25. return hash;
  26. }
  27. };
  28. namespace hash_bucket//开散列/拉链法
  29. {
  30. template<class T>
  31. struct HashNode
  32. {
  33. T _data;
  34. HashNode<T>* _next;
  35. HashNode(const T& data)
  36. :_data(data)
  37. ,_next(nullptr)
  38. {}
  39. };
  40. // 前置声明
  41. template<class K, class T, class Hash, class KeyOfT>
  42. class HashTable;
  43. template<class K, class T, class Ref,class Ptr,class Hash, class KeyOfT>
  44. struct __HTIterator
  45. {
  46. typedef HashNode<T> Node;
  47. typedef __HTIterator<K, T, Ref, Ptr, Hash, KeyOfT> Self;
  48. Node* _node;
  49. const HashTable<K, T, Hash, KeyOfT>* _pht;//权限可以平移/缩小,不能放大
  50. size_t _hashi;
  51. __HTIterator(Node*node, HashTable<K, T, Hash, KeyOfT>* pht, size_t hashi)
  52. :_node(node)
  53. ,_pht(pht)
  54. ,_hashi(hashi)
  55. {}
  56. //有普通的this( HashTable的this)指针与const修饰的this指针,走更适配的
  57. __HTIterator(Node* node, const HashTable<K, T, Hash, KeyOfT>* pht, size_t hashi)
  58. :_node(node)
  59. , _pht(pht)
  60. , _hashi(hashi)
  61. {}
  62. Self& operator++()
  63. {
  64. if (_node->_next)//当前桶没走完,还有节点,走到下一个节点
  65. {
  66. _node = _node->_next;
  67. }
  68. else//当前桶已走完,找下一个桶的起始节点
  69. {
  70. ++_hashi;
  71. while (_hashi < _pht->_tables.size())
  72. {
  73. if (_pht->_tables[_hashi])
  74. {
  75. _node = _pht->_tables[_hashi];
  76. break;
  77. }
  78. ++_hashi;
  79. }
  80. if (_hashi == _pht->_tables.size())
  81. {
  82. _node = nullptr;
  83. }
  84. }
  85. return *this;
  86. }
  87. Ref operator*()
  88. {
  89. return _node->_data;
  90. }
  91. Ptr operator->()
  92. {
  93. return &_node->_data;
  94. }
  95. bool operator!=(const Self& s)
  96. {
  97. return _node != s._node;
  98. }
  99. };
  100. // unordered_set -> Hashtable<K, K>
  101. // unordered_map -> Hashtable<K, pair<K, V>>
  102. template<class K,class T,class Hash,class KeyOfT>
  103. class HashTable
  104. {
  105. typedef HashNode<T> Node;
  106. template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>
  107. friend struct __HTIterator;
  108. public:
  109. typedef __HTIterator<K, T, T&, T*, Hash, KeyOfT> iterator;
  110. typedef __HTIterator<K, T, const T&, const T*, Hash, KeyOfT> const_iterator;
  111. iterator begin()
  112. {
  113. for (size_t i = 0; i < _tables.size(); i++)
  114. {
  115. if (_tables[i])
  116. {
  117. return iterator(_tables[i], this, i);
  118. }
  119. }
  120. return end();
  121. }
  122. iterator end()
  123. {
  124. return iterator(nullptr,this,-1);
  125. }
  126. const_iterator begin()const
  127. {
  128. for (size_t i = 0; i < _tables.size(); i++)
  129. {
  130. if (_tables[i])
  131. {
  132. return const_iterator(_tables[i], this, i);
  133. }
  134. }
  135. return end();
  136. }
  137. const_iterator end()const
  138. {
  139. return const_iterator(nullptr,this,-1);
  140. }
  141. HashTable()
  142. {
  143. _tables.resize(10);
  144. }
  145. ~HashTable()
  146. {
  147. for (size_t i = 0; i < _tables.size(); i++)
  148. {
  149. Node* cur = _tables[i];
  150. while (cur)
  151. {
  152. Node* next = cur->_next;
  153. delete cur;
  154. cur = next;
  155. }
  156. _tables[i] = nullptr;
  157. }
  158. }
  159. pair<iterator, bool> Insert(const T& data)
  160. {
  161. //检查是否已经存在
  162. Hash hf;//将关键字转成/映射成一个整型 (data可以为int,可以为string)
  163. KeyOfT kot;//取出data中的关键字,data若为Key则就是它本身,data若为pair,则取得pair的第一个元素
  164. iterator it = Find(kot(data));
  165. if (it != end())
  166. return make_pair(it, false);
  167. //检查是否需要扩容
  168. // 负载因子最大到1
  169. if (_n ==_tables.size())
  170. {
  171. vector<Node*> newTables;
  172. newTables.resize(_tables.size() * 2, nullptr);
  173. //遍历旧表
  174. for (size_t i = 0; i < _tables.size(); i++)
  175. {
  176. //挪动映射至新表
  177. Node* cur = _tables[i];
  178. while (cur)
  179. {
  180. Node* next = cur->_next;
  181. size_t hashi = hf(kot(cur->_data)) % newTables.size();
  182. cur->_next = newTables[hashi];
  183. newTables[hashi] = cur;
  184. cur = next;
  185. }
  186. _tables[i] = nullptr;
  187. }
  188. _tables.swap(newTables);
  189. }
  190. size_t hashi = hf(kot(data)) % _tables.size();
  191. Node* newnode = new Node(data);
  192. //头插
  193. newnode->_next = _tables[hashi];
  194. _tables[hashi] = newnode;
  195. ++_n;
  196. return make_pair(iterator(newnode,this,hashi), true);
  197. }
  198. iterator Find(const K& key)
  199. {
  200. Hash hf;
  201. KeyOfT kot;
  202. size_t hashi = hf(key) % _tables.size();
  203. Node* cur = _tables[hashi];
  204. while (cur)
  205. {
  206. if (kot(cur->_data) == key)
  207. {
  208. return iterator(cur,this,hashi);
  209. }
  210. cur = cur->_next;
  211. }
  212. return end();
  213. }
  214. bool Erase(const K& key)
  215. {
  216. Hash hf;
  217. KeyOfT kot;
  218. size_t hashi = hf(key) % _tables.size();
  219. Node* prev = nullptr;
  220. Node* cur = _tables[hashi];
  221. while (cur)
  222. {
  223. if (kot(cur->_data) == key)
  224. {
  225. if (prev == nullptr)//第一个就是要找的
  226. {
  227. _tables[hashi] = cur->_next;
  228. }
  229. else
  230. {
  231. prev->_next = cur->_next;
  232. }
  233. delete cur;
  234. return true;
  235. }
  236. prev = cur;
  237. cur = cur->_next;
  238. }
  239. return false;
  240. }
  241. private:
  242. vector<Node*> _tables;
  243. size_t _n = 0;
  244. };
  245. }

MyUnorderedMap.h

  1. #pragma once
  2. #include"HashTable.h"
  3. namespace djx
  4. {
  5. template<class K,class V,class Hash = HashFunc<K>>
  6. class unordered_map
  7. {
  8. struct MapKeyOfT
  9. {
  10. const K& operator()(const pair<K, V>& kv)//获取关键字
  11. {
  12. return kv.first;
  13. }
  14. };
  15. public:
  16. typedef typename hash_bucket::HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::iterator iterator;
  17. iterator begin()
  18. {
  19. return _ht.begin();
  20. }
  21. iterator end()
  22. {
  23. return _ht.end();
  24. }
  25. pair<iterator, bool> insert(const pair<K, V>& kv)
  26. {
  27. return _ht.Insert(kv);
  28. }
  29. V& operator[](const K& key)
  30. {
  31. pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
  32. return ret.first->second;
  33. }
  34. const V& operator[](const K& key) const
  35. {
  36. pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
  37. return ret.first->second;
  38. }
  39. iterator find(const K& key)
  40. {
  41. return _ht.Find(key);
  42. }
  43. bool erase(const K& key)
  44. {
  45. return _ht.Erase(key);
  46. }
  47. private:
  48. hash_bucket::HashTable<K, pair<const K, V>, Hash, MapKeyOfT> _ht;
  49. };
  50. }

MyUnorderedSet.h

  1. #pragma once
  2. #include"HashTable.h"
  3. namespace djx
  4. {
  5. template<class K,class Hash = HashFunc<K>>
  6. class unordered_set
  7. {
  8. struct SetKeyOfT
  9. {
  10. const K& operator()(const K& key)
  11. {
  12. return key;
  13. }
  14. };
  15. public:
  16. // 对类模板取内嵌类型,加typename告诉编译器这里是类型
  17. typedef typename hash_bucket::HashTable<K, K, Hash, SetKeyOfT>::const_iterator iterator;
  18. typedef typename hash_bucket::HashTable<K, K, Hash, SetKeyOfT>::const_iterator const_iterator;
  19. const_iterator begin() const
  20. {
  21. return _ht.begin();
  22. }
  23. const_iterator end() const
  24. {
  25. return _ht.end();
  26. }
  27. pair<const_iterator, bool> insert(const K& key)
  28. {
  29. auto ret = _ht.Insert(key);
  30. return make_pair(const_iterator(ret.first._node, ret.first._pht, ret.first._hashi), ret.second);
  31. }
  32. iterator find(const K& key)
  33. {
  34. auto ret = _ht.Find(key);
  35. return const_iterator(ret._node, ret._pht, ret._hashi);
  36. }
  37. bool erase(const K& key)
  38. {
  39. return _ht.Erase(key);
  40. }
  41. private:
  42. hash_bucket::HashTable<K, K, Hash, SetKeyOfT> _ht;
  43. };
  44. }

测试:

  1. void test_set()
  2. {
  3. djx::unordered_set<int> us;
  4. us.insert(4);
  5. us.insert(19);
  6. us.insert(62);
  7. us.insert(3);
  8. djx::unordered_set<int>::iterator it = us.begin();
  9. while (it != us.end())
  10. {
  11. cout << *it << " ";
  12. ++it;
  13. }
  14. cout << endl;
  15. auto e = us.find(19);
  16. cout << *e << endl;
  17. us.erase(19);
  18. for (auto e : us)
  19. {
  20. cout << e << " ";
  21. }
  22. cout << endl;
  23. }

  1. void test_map()
  2. {
  3. djx::unordered_map<string, string> dict;
  4. dict.insert(make_pair("sort", ""));
  5. dict.insert(make_pair("string", ""));
  6. dict.insert(make_pair("insert", ""));
  7. for (auto& kv : dict)
  8. {
  9. //kv.first += 'x';不可以修改K
  10. kv.second += 'x';
  11. cout << kv.first << ":" << kv.second << endl;
  12. }
  13. cout << endl;
  14. string arr[] = { "苹果","葡萄","葡萄","甜瓜"};
  15. djx::unordered_map<string, int> count_map;
  16. for (auto& e : arr)
  17. {
  18. count_map[e]++;
  19. }
  20. for (auto& kv : count_map)
  21. {
  22. cout << kv.first << ":" << kv.second << endl;
  23. }
  24. cout << endl;
  25. count_map.erase("苹果");
  26. }

一些代码设计的细节:

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

闽ICP备14008679号