当前位置:   article > 正文

用哈希表封装unordered_map(以及unordered_set)【C++】

用哈希表封装unordered_map(以及unordered_set)【C++】

目录

一,前言

二,封装层框架(哈希底层以哈希桶为例)

三,迭代器

1. operator++

2. operator[]

3. 仿函数优化

3. 解决unordered_set中Key可以修改的Bug

代码区

Hash_map_set.h

HashTable.h

总结:用红黑树还是哈希表??

哈希表优势:

缺点:

红黑树优势:

下节预告:哈希应用!!

结语


一,前言

在学习封装unordered_map与unordered_set前,建议先学习如何封装map & set该篇文章用红黑树封装map&set【C++】-CSDN博客

这样更能理解其中的封装思想(两种封装方式,主题思路大体相似)

同时用哈希表封装 unordered_map和undordered_set,在封装之前,我们首先是要学会哈希表基本的精华,哈希实现建议大家先学习从底层认识哈希表【C++】-CSDN博客

二,封装层框架(哈希底层以哈希桶为例)

由于我们前面已经学习过map与set的封装,在学习unordered_map(set) 封装这里我们直接给出基本的框架代码啦! 

本质上:还是对哈希表进行封装,在unordered_map层面调用哈希底层。

用哈希表封装 unordered_map &  unordered_set,思想核心:就是让unordered_set如何适应unordered_map,看到这里的都是已经学习过红黑树封装map与set的童鞋了吧

  1. namespace hash_map_set
  2. {
  3. template <class K>
  4. class unordered_set
  5. {
  6. public:
  7. struct SetkeyofT // 从T类型中提取Key
  8. {
  9. const K& operator()(const K& key)
  10. {
  11. return key;
  12. }
  13. };
  14. bool insert(const K& data)
  15. {
  16. return table.insert(data);
  17. }
  18. private:
  19. hash_barrel::HashTable<K, K, SetkeyofT> table;
  20. };
  21. template <class K, class V>
  22. class unordered_map
  23. {
  24. public:
  25. struct MapkeyofT
  26. {
  27. const K& operator()(const pair<const K, V>& kv)
  28. {
  29. return kv.first;
  30. }
  31. };
  32. bool insert(const pair<const K, V>& kv)
  33. {
  34. return table.insert(kv);
  35. }
  36. private:
  37. hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT> table;
  38. };
  39. }
  40. // 哈希表底层部分变化,为了避免冗余,就只展现部分代码
  41. template <class T>
  42. struct Node_Data
  43. {
  44. typedef Node_Data<T> Node_data;
  45. T _data;
  46. Node_data* _downstars = nullptr;
  47. Node_Data(const T& pa = T())
  48. :_data(pa)
  49. {}
  50. };
  51. template <class K, class T, class KeyofT ,class Hashstr = Hashstr<K>>
  52. class HashTable
  53. {
  54. public:
  55. typedef Node_Data<T> Node_Data;
  56. .......

三,迭代器

首先我们先完成 迭代器基本框架 和 基本“ * ”, “ & ”,“ ->”, " != "实现,代码其实比较简单,重要的是逻辑如何行云流水的形成,多写多看多思考吧!!

  1. template <class T>
  2. struct Node_Data
  3. {
  4. typedef Node_Data<T> Node_data;
  5. T _data;
  6. Node_data* _downstars = nullptr;
  7. Node_Data(const T& pa = T())
  8. :_data(pa)
  9. {}
  10. };
  11. template <class type>
  12. struct Hashstr
  13. {
  14. int operator()(const type& sd)
  15. {
  16. return sd;
  17. }
  18. };
  19. template<>
  20. struct Hashstr<string>
  21. {
  22. size_t operator()(const string& str)
  23. {
  24. size_t sum = 0;
  25. for (auto e : str)
  26. {
  27. sum += e;
  28. sum *= 31; // 别问,问就是实验的结果
  29. }
  30. return sum;
  31. }
  32. };
  33. // 由于迭代器需要使用HashTable作为成员变量,因此需要前置声明
  34. template <class K, class T, class KeyofT, class Hashstr = Hashstr<K>>
  35. class HashTable;
  36. template <class K, class T, class KeyofT, class Ref, class Ptr, class Hash>
  37. struct HT_iterator
  38. {
  39. typedef Node_Data<T> Node_Data;
  40. typedef HashTable<K, T, KeyofT, Hash> HashTable;
  41. typedef HT_iterator<K, T, KeyofT, Ref, Ptr, Hash> HT_iter; // 正常迭代器
  42. Node_Data* _node; // 当前位置
  43. HashTable* _ht; // 当前哈希表
  44. size_t _bucket = -1; // 当前桶
  45. HT_iterator(Node_Data* node, HashTable* ht)
  46. :_node(node)
  47. , _ht(ht)
  48. {
  49. Hash hashstr;
  50. KeyofT keyofT;
  51. if (node) // node为空的时候,我们会对nullptr进行访问,所以规避一下
  52. _bucket = hashstr(keyofT(_node->_data)) % _ht->Get_tables().size();
  53. }
  54. // HT_iterator(const HT_iterator)
  55. Ref operator*()
  56. {
  57. return _node->_data;
  58. }
  59. Ptr operator->()
  60. {
  61. return &(_node->_data);
  62. }
  63. bool operator!=(const HT_iter& it)
  64. {
  65. return _node != it._node;
  66. }
  67. };

1. operator++

思路:从第一个桶开始访问数据,访问完一个数据后,接着下一个桶,直至结束

思路比较简单,但迭代器设计,逻辑比较难处理。

  1. // 哈希迭代器只往前单向
  2. HT_iter operator++()
  3. {
  4. // 三种情况:
  5. // 1. 下一个为空,下一个桶存在
  6. // 2. 下一个为空,下一个不存在
  7. // 3. 下一个存在
  8. if (_node->_downstars)
  9. { // 3.
  10. _node = _node->_downstars;
  11. return *this;
  12. }
  13. else
  14. { // 1 , 2
  15. 寻找下一个不为空的桶
  16. ++_bucket;
  17. while (_bucket < _ht->Get_tables().size())
  18. {
  19. _node = (_ht->Get_tables())[_bucket];
  20. if (_node) // 找到后中断寻找
  21. break;
  22. ++_bucket;
  23. }
  24. return *this;
  25. }
  26. }

2. operator[]

在实现operator[]时,我们需要先将insert,find函数进行优化:

让insert返回值从bool ——> 返回pair<iterator,  bool>;

find()从返回数据指针——>  返回迭代器;

  1. // Hashtable——哈希类中
  2. T& operator[](const T& pa)
  3. {
  4. pair<HT_iterator, bool> it = insert(pa);
  5. return *(it.first);
  6. }
  7. // 封装层 unordered_map
  8. V& operator[](const K& pa)
  9. {
  10. pair<HT_iterator, bool> ret = insert(make_pair(pa, V()));
  11. return ret.first->second;
  12. }
  13. // unordered_set
  14. K& operator[](const K& pa)
  15. {
  16. return table[pa];
  17. }

3. 仿函数优化

在哈希实现过程中,我们实现了该仿函数,目的就是为了解决当 K不是int的时候(比如string类)无法转化为size_t类型,来寻找哈希地址。 

那么问题来了!!  我如果想将Day类型(处理年,月,日的类)作为K呢?? 请问这怎么将这个日期类转化为size_t???

为了方便分析,我将这个将其他数据转化为size_t的类叫做转化类 

我们会发现,封装在底层的中转化类(int与string类)在目前看来,应该将其放在unordered_map与unordered_set的封装层;同时在两个封装层添加一个新的参数,作为转化类的行参

这样做的好处是,在我们不知道K类型时,用户可以导入其自己设定的转化类,来实现各种类(各种类型)转化为size_t类型。

3. 解决unordered_set中Key可以修改的Bug

在前面的文章中(用红黑树封装map&set【C++】-CSDN博客),我们已经修复过一次了,这里就只展现修改位置了。

代码区

Hash_map_set.h

  1. namespace hash_map_set
  2. {
  3. template <class type>
  4. struct Hashstr
  5. {
  6. int operator()(const type& sd)
  7. {
  8. return sd;
  9. }
  10. };
  11. template<>
  12. struct Hashstr<string>
  13. {
  14. size_t operator()(const string& str)
  15. {
  16. size_t sum = 0;
  17. for (auto e : str)
  18. {
  19. sum += e;
  20. sum *= 31; // sum为啥都要乘31这个数??1.减少数据聚集在一个桶中。2.31这个数是大量实验的最佳数
  21. }
  22. return sum;
  23. }
  24. };
  25. template <class K, class Hash = Hashstr<K>>
  26. class unordered_set
  27. {
  28. public:
  29. struct SetkeyofT // 从T类型中提取Key
  30. {
  31. const K& operator()(const K& key)
  32. {
  33. return key;
  34. }
  35. };
  36. // 关于为什么要这样操作?原因:底层(HT_iterator)实例化后,再取得模板,而不是自己设定
  37. typedef typename hash_barrel::HashTable<K, K, SetkeyofT, Hash>::const_HT_iterator HT_iterator;
  38. typedef typename hash_barrel::HashTable<K, K, SetkeyofT, Hash>::const_HT_iterator const_HT_iterator;
  39. const_HT_iterator begin() const
  40. {
  41. return table.begin();
  42. }
  43. const_HT_iterator end() const
  44. {
  45. return table.end();
  46. }
  47. HT_iterator begin()
  48. {
  49. return table.begin();
  50. }
  51. HT_iterator end()
  52. {
  53. return table.end();
  54. }
  55. pair<HT_iterator, bool> insert(const K& data)
  56. {
  57. return table.insert(data);
  58. }
  59. K& operator[](const K& pa)
  60. {
  61. return table[pa];
  62. }
  63. HT_iterator find(const K& pa)
  64. {
  65. return table.find(pa);
  66. }
  67. bool erase(const K& pa)
  68. {
  69. return table.erase(pa);
  70. }
  71. private:
  72. hash_barrel::HashTable<K, K, SetkeyofT, Hash> table;
  73. };
  74. template <class K, class V, class Hash = Hashstr<K>>
  75. class unordered_map
  76. {
  77. public:
  78. struct MapkeyofT
  79. {
  80. const K& operator()(const pair<const K, V>& kv)
  81. {
  82. return kv.first;
  83. }
  84. };
  85. typedef typename hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT, Hash>::HT_iterator HT_iterator;
  86. typedef typename hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT, Hash>::const_HT_iterator const_HT_iterator;
  87. const_HT_iterator begin() const
  88. {
  89. return table.begin();
  90. }
  91. const_HT_iterator end() const
  92. {
  93. return table.end();
  94. }
  95. HT_iterator begin()
  96. {
  97. return table.begin();
  98. }
  99. HT_iterator end()
  100. {
  101. return table.end();
  102. }
  103. pair<HT_iterator, bool> insert(const pair<const K, V>& kv)
  104. {
  105. return table.insert(kv);
  106. }
  107. V& operator[](const K& pa)
  108. {
  109. pair<HT_iterator, bool> ret = insert(make_pair(pa, V()));
  110. return ret.first->second;
  111. }
  112. HT_iterator find(const K& pa)
  113. {
  114. return table.find(pa);
  115. }
  116. bool erase(const K& pa)
  117. {
  118. return table.erase(pa);
  119. }
  120. private:
  121. hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT, Hash> table;
  122. };
  123. }

HashTable.h

  1. namespace hash_barrel
  2. {
  3. template <class T>
  4. struct Node_Data
  5. {
  6. typedef Node_Data<T> Node_data;
  7. T _data;
  8. Node_data* _downstars = nullptr;
  9. Node_Data(const T& pa = T())
  10. :_data(pa)
  11. {}
  12. };
  13. // 由于迭代器需要使用HashTable作为成员变量,因此需要前置声明
  14. template <class K, class T, class KeyofT, class Hashstr>
  15. class HashTable;
  16. template <class K, class T, class KeyofT, class Ref, class Ptr, class Hash>
  17. struct HT_iterator
  18. {
  19. typedef Node_Data<T> Node_Data;
  20. typedef HashTable<K, T, KeyofT, Hash> HashTable;
  21. typedef HT_iterator<K, T, KeyofT, Ref, Ptr, Hash> HT_iter; // 正常迭代器
  22. typedef HT_iterator<K, T, KeyofT, T&, T*, Hash> iterator;
  23. Node_Data* _node; // 当前位置
  24. const HashTable* _ht; // 当前哈希表,添加const原因:1.迭代器不会修改哈希表 2.const迭代器传入的是const的哈希表,所以需要适配
  25. size_t _bucket = -1; // 当前桶
  26. HT_iterator(Node_Data* node, const HashTable* ht)
  27. :_node(node)
  28. , _ht(ht)
  29. {
  30. Hash hashstr;
  31. KeyofT keyofT;
  32. if (node) // node为空的时候,我们会对nullptr进行访问,所以规避一下
  33. _bucket = hashstr(keyofT(_node->_data)) % _ht->Get_tables().size();
  34. }
  35. // 1. 当对象是普通迭代器时,这是拷贝构造
  36. // 2. 当对象是const迭代器时,是利用普通迭代器构造const迭代器的构造函数
  37. HT_iterator(const iterator& it)
  38. :_node(it._node)
  39. ,_ht(it._ht)
  40. ,_bucket(it._bucket)
  41. {}
  42. // HT_iterator(const HT_iterator)
  43. Ref operator*()
  44. {
  45. return _node->_data;
  46. }
  47. Ptr operator->()
  48. {
  49. return &(_node->_data);
  50. }
  51. bool operator!=(const HT_iter& it)
  52. {
  53. return _node != it._node;
  54. }
  55. // 哈希迭代器只往前单向
  56. HT_iter operator++()
  57. {
  58. // 三种情况:
  59. // 1. 下一个为空,下一个桶存在
  60. // 2. 下一个为空,下一个不存在
  61. // 3. 下一个存在
  62. if (_node->_downstars)
  63. { // 3.
  64. _node = _node->_downstars;
  65. return *this;
  66. }
  67. else
  68. { // 1 , 2
  69. 寻找下一个不为空的桶
  70. ++_bucket;
  71. while (_bucket < _ht->Get_tables().size())
  72. {
  73. _node = (_ht->Get_tables())[_bucket];
  74. if (_node)
  75. break;
  76. ++_bucket;
  77. }
  78. return *this;
  79. }
  80. }
  81. };
  82. template <class K, class T, class KeyofT ,class Hash>
  83. class HashTable
  84. {
  85. public:
  86. typedef Node_Data<T> Node_Data;
  87. typedef HT_iterator<K, T, KeyofT, const T&, const T*, Hash> const_HT_iterator; // const迭代器
  88. typedef HT_iterator<K, T, KeyofT, T&, T*, Hash> HT_iterator; // 普通迭代器
  89. ~HashTable()
  90. {
  91. for (auto bucket : _tables)
  92. {
  93. Node_Data* cur = bucket;
  94. while (cur)
  95. {
  96. Node_Data* next = cur->_downstars;
  97. delete (cur);
  98. cur = next;
  99. }
  100. }
  101. }
  102. const_HT_iterator begin() const
  103. {
  104. size_t i = 0;
  105. Node_Data* cur = nullptr;
  106. for (; i < _tables.size(); i++)
  107. {
  108. cur = _tables[i];
  109. if (cur)
  110. break;
  111. }
  112. return const_HT_iterator(cur, this); // this就是哈希表对象
  113. }
  114. const_HT_iterator end() const
  115. {
  116. return const_HT_iterator(nullptr, this);
  117. }
  118. HT_iterator begin()
  119. {
  120. size_t i = 0;
  121. Node_Data* cur = nullptr;
  122. for (; i < _tables.size(); i++)
  123. {
  124. cur = _tables[i];
  125. if (cur)
  126. break;
  127. }
  128. return HT_iterator(cur, this); // this就是哈希表对象
  129. }
  130. HT_iterator end()
  131. {
  132. return HT_iterator(nullptr, this);
  133. }
  134. // 在实现 operator[]时,底层用到insert(),没找到,插入新值返回迭代器;找到直接返回迭代器
  135. pair<HT_iterator,bool> insert(const T& pa)
  136. {
  137. KeyofT keyofT;
  138. HT_iterator ret = _find(keyofT(pa));
  139. if (ret != end())
  140. return make_pair(ret, true);
  141. // 考虑扩容:负载因子为1,2,3都可以
  142. Hash hashstr;
  143. if (_tables.size() == 0 || _n * 10 / _tables.size() >= 10)
  144. {
  145. size_t new_size = _tables.size() == 0 ? 10 : _tables.size() * 2;
  146. // 开始扩容
  147. vector<Node_Data*> new_tables;
  148. new_tables.resize(new_size);
  149. for (auto& data : _tables)
  150. {
  151. // 处理桶内的数据,重新插入新节点
  152. Node_Data* cur = data;
  153. while (cur)
  154. {
  155. Node_Data* room = cur->_downstars;
  156. size_t new_hashi = hashstr(keyofT(cur->_data)) % new_tables.size();
  157. // 处理结点关系
  158. Node_Data* tmp = new_tables[new_hashi];
  159. new_tables[new_hashi] = cur;
  160. cur->_downstars = tmp;
  161. cur = room;
  162. }
  163. }
  164. _tables.swap(new_tables);
  165. }
  166. size_t hashi = hashstr(keyofT(pa)) % _tables.size();
  167. Node_Data* new_node = new Node_Data(pa);
  168. new_node->_downstars = _tables[hashi];
  169. _tables[hashi] = new_node;
  170. _n++;
  171. return make_pair(HT_iterator(new_node, this), true);
  172. }
  173. HT_iterator find(const K& order)
  174. {
  175. return _find(order);
  176. }
  177. bool erase(const K& order)
  178. {
  179. Hash hashstr;
  180. HT_iterator cur = find(order);
  181. if (!(cur._node))
  182. {
  183. cout << "擦除失败: 不存在" << endl;
  184. return false;
  185. }
  186. size_t index = hashstr(order) % _tables.size();
  187. Node_Data* tmp = _tables[index];
  188. while (tmp)
  189. {
  190. if (cur._node == tmp)
  191. {
  192. break;
  193. }
  194. else if (cur._node == tmp->_downstars)
  195. {
  196. break;
  197. }
  198. tmp = tmp->_downstars;
  199. }
  200. // 开始处理节点
  201. if (tmp == _tables[index]) // 如果擦除的是头,那要置空的包括指针数组
  202. {
  203. _tables[index] = cur._node->_downstars;
  204. }
  205. else
  206. {
  207. tmp->_downstars = cur._node->_downstars;
  208. }
  209. delete (cur._node);
  210. cout << "擦除成功" << endl;
  211. return true;
  212. }
  213. T& operator[](const T& pa)
  214. {
  215. pair<HT_iterator, bool> it = insert(pa);
  216. return *(it.first);
  217. }
  218. size_t Get_n()
  219. {return _n; }
  220. vector<Node_Data*>& Get_tables()
  221. {
  222. return _tables;
  223. }
  224. // 需要为const 对象进行重载
  225. const vector<Node_Data*>& Get_tables() const
  226. {
  227. return _tables;
  228. }
  229. private:
  230. // Node_Data* _find(const K& order)
  231. HT_iterator _find(const K& order)
  232. {
  233. if (!_tables.size())
  234. return end();
  235. Hash hashstr;
  236. KeyofT keyofT;
  237. size_t hashi = hashstr(order) % _tables.size();
  238. auto cur = _tables[hashi];
  239. while (cur)
  240. {
  241. if (keyofT(cur->_data) == order)
  242. return HT_iterator(cur, this);
  243. cur = cur->_downstars;
  244. }
  245. return end();
  246. }
  247. vector<Node_Data*> _tables; // 存放各个哈希地址的第一个结点地址的指针数组
  248. size_t _n = 0; // 哈希桶中,数据个数
  249. };
  250. }

总结:用红黑树还是哈希表??

哈希表优势:

哈希表的查找,插入,删除操作平均为O(1);红黑树的查找,插入,删除平均O(logn)。因此哈希表会有更高的效率。

缺点:

1. 哈希表存在,哈希冲突,而解决方案有 哈希桶法(链表法),开放定址法。如果超过负载值,将对全部数据进行重新分配,这会导致性能下降。

2. 占用更多的内存

红黑树优势:

1. 相比于哈希表红黑树占用内存比较少。

2. 相比于哈希表插入可能需要重新分配哈希桶,红黑树的插入效率不差,也更稳定。

因此:选择哈希表还是红黑树作为map的底层数据结构取决于具体的应用场景。如果对性能要求较高且不需要有序性,可以选择哈希表。如果需要有序性或者对性能要求不是特别高,可以选择红黑树。

 

下节预告:哈希应用!!

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

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

闽ICP备14008679号