当前位置:   article > 正文

C++ 使用哈希表封装模拟实现unordered_map unordered_set_c++ unordered_set使用什么数据结构

c++ unordered_set使用什么数据结构

一、unordered_map unordered_set 和 map set的区别

1. map set底层采取的红黑树的结构,unordered_xxx 底层数据结构是哈希表。unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。

2. Java中对应的容器名为 HashMap HashSet TreeMap TreeSet,命名方面比C++好了很多。主要是早期C++并没有实现哈希结构的容器(C++11之前),也就是unordered系列,在C++11中新增了unordered_map,unordered_set,unordered_multimap,unordered_multiset,故因为历史命名问题,取了这样的名字。

3. 它们的使用大体上几乎一致。显著的差别是:
a、map和set为双向迭代器,unordered_xxx和是单向迭代器。
b、map和set存储为有序存储(红黑树结构,中序遍历有序),unordered_xxx为无序存储(哈希表的结构致使)

4. 性能差异:采取哈希表的unordered系列容器在大量数据的增删查改效率更优,尤其是查(搜索)

二、实现代码

HashTable.h

  1. //
  2. // Created by yangzilong on 2022/11/15.
  3. //
  4. #pragma once
  5. #include <utility>
  6. #include <vector>
  7. #include <iostream>
  8. using namespace std;
  9. // 开散列(链地址法)解决哈希表中的哈希冲突
  10. // 哈希表中的结点并不知道自己存储的数据类型,unordered_map为pair,unordered_set为key
  11. template <class T>
  12. struct HashNode
  13. {
  14. HashNode<T>(const T& data)
  15. :_data(data)
  16. { }
  17. T _data;
  18. HashNode* _next = nullptr;
  19. };
  20. // 哈希表的前置声明,因为迭代器中要用到
  21. template <class , class , class , class , class >
  22. class HashTable;
  23. // 哈希表迭代器,因为数据成员中有哈希表指针,所以这些模板参数都需要
  24. template <class K, class T, class KeyOfT, class Hash, class Equal>
  25. struct __HashTable_Iterator
  26. {
  27. typedef HashNode<T> Node;
  28. typedef HashTable<K, T, KeyOfT, Hash, Equal> HT;
  29. typedef __HashTable_Iterator<K, T, KeyOfT, Hash, Equal> Self;
  30. __HashTable_Iterator(Node* node, HT* ptr)
  31. : _node(node), _tablePtr(ptr)
  32. { }
  33. bool operator==(const Self& it) const {
  34. return _node == it._node;
  35. }
  36. bool operator!=(const Self& it) const {
  37. return _node != it._node;
  38. }
  39. T& operator*() const {
  40. return _node->_data;
  41. }
  42. T* operator->() const {
  43. return &_node->_data;
  44. }
  45. // unordered_map unordered_set为单向迭代器
  46. Self& operator++() {
  47. // 前置++
  48. if(_node->_next)
  49. _node = _node->_next;
  50. else {
  51. KeyOfT kot;
  52. Hash hash;
  53. size_t hashAddress = hash(kot(_node->_data)) % _tablePtr->_table.size();
  54. ++hashAddress;
  55. _node = nullptr;
  56. while(hashAddress < _tablePtr->_table.size() && (_node = _tablePtr->_table[hashAddress]) == nullptr)
  57. ++hashAddress;
  58. // while(hashAddress < _tablePtr->_table.size() && _tablePtr->_table[hashAddress] == nullptr)
  59. // ++hashAddress;
  60. // if(hashAddress == _tablePtr->_table.size()) //
  61. // _node = nullptr;
  62. // else
  63. // _node = _tablePtr->_table[hashAddress];
  64. }
  65. return *this;
  66. }
  67. Self operator++(int) {
  68. Self ret = *this;
  69. ++*this;
  70. return ret;
  71. }
  72. // 每个迭代器中的数据成员
  73. Node* _node;
  74. HashTable<K, T, KeyOfT, Hash, Equal>* _tablePtr; // 存储对应哈希表的指针
  75. };
  76. // 第一个参数为关键字,用于Find。第二个参数为开散列哈希表中每个结点存储的数据类型
  77. template <class K, class T, class KeyOfT, class Hash, class Equal>
  78. class HashTable
  79. {
  80. // 迭代器中要用到哈希表的私有数据成员,即那个哈希表(vector)的长度。
  81. template <typename A, class B, class C, class D, class E>
  82. friend struct __HashTable_Iterator;
  83. typedef HashNode<T> Node;
  84. public:
  85. typedef __HashTable_Iterator<K, T, KeyOfT, Hash, Equal> iterator;
  86. iterator begin() {
  87. for(size_t i = 0; i < _table.size(); ++i) {
  88. if(_table[i])
  89. return iterator(_table[i], this);
  90. }
  91. return end();
  92. }
  93. iterator end() {
  94. return iterator(nullptr, this);
  95. }
  96. ~HashTable() {
  97. for(auto& ptr : _table) {
  98. Node* cur = ptr;
  99. while(cur) {
  100. Node* next = cur->_next;
  101. delete cur;
  102. cur = next;
  103. }
  104. ptr = nullptr;
  105. }
  106. }
  107. pair<iterator, bool> Insert(const T& data) {
  108. KeyOfT kot;
  109. iterator it = Find(kot(data));
  110. if(it != end())
  111. return make_pair(it, false);
  112. Hash convert;
  113. // 负载因子到1就扩容
  114. if(_table.size() == 0 || 10 * _size / _table.size() >= 10) {
  115. // 开散列法哈希表扩容
  116. vector<Node*> newTable;
  117. size_t newSize = _table.size() == 0 ? 10 : _table.size()*2;
  118. newTable.resize(newSize);
  119. for(size_t i = 0; i < _table.size(); ++i) {
  120. Node* cur = _table[i];
  121. while(cur) {
  122. Node* next = cur->_next;
  123. size_t hashAddress = convert(kot(cur->_data)) % newTable.size();
  124. cur->_next = newTable[hashAddress];
  125. newTable[hashAddress] = cur;
  126. cur = next;
  127. }
  128. _table[i] = nullptr;
  129. // if(_table[i]) {
  130. // Node* cur = _table[i];
  131. // Node* next = cur->_next;
  132. // while(cur) {
  133. // size_t hashAddress = convert(cur->_kv.first) % newTable.size();
  134. // cur->_next = newTable[hashAddress];
  135. // newTable[hashAddress] = cur;
  136. // cur = next;
  137. // if(cur)
  138. // next = cur->_next;
  139. // }
  140. // // 也没必要其实
  141. // _table[i] = nullptr;
  142. // }
  143. }
  144. _table.swap(newTable);
  145. }
  146. // 通过哈希函数求哈希地址
  147. size_t hashAddress = convert(kot(data)) % _table.size();
  148. Node* ptr = _table[hashAddress];
  149. Node* newNode = new Node(data);
  150. // 每个哈希桶中进行头插
  151. newNode->_next = ptr;
  152. _table[hashAddress] = newNode;
  153. ++_size;
  154. return make_pair(iterator(newNode, this), true);
  155. }
  156. // 用到了第一个模板参数
  157. iterator Find(const K& key) {
  158. Equal equal;
  159. if(_table.size() == 0) {
  160. return end();
  161. }
  162. KeyOfT kot;
  163. Hash convert;
  164. size_t hashAddress = convert(key) % _table.size();
  165. Node* cur = _table[hashAddress];
  166. while(cur) {
  167. if(equal(kot(cur->_data), key)) {
  168. return iterator(cur, this);
  169. }
  170. cur = cur->_next;
  171. }
  172. return end();
  173. }
  174. bool Erase(const K& key) {
  175. if(_table.size() == 0)
  176. return false;
  177. Hash convert;
  178. Equal equal;
  179. KeyOfT kot;
  180. size_t hashAddress = convert(key) % _table.size();
  181. Node* cur = _table[hashAddress];
  182. Node* prev = nullptr;
  183. while(cur) {
  184. if(equal(kot(cur->_data), key)) {
  185. if(prev)
  186. prev->_next = cur->_next;
  187. else
  188. _table[hashAddress] = cur->_next;
  189. delete cur;
  190. --_size;
  191. return true;
  192. }
  193. prev = cur;
  194. cur = cur->_next;
  195. }
  196. // 不存在该节点
  197. return false;
  198. }
  199. // 哈希表的长度
  200. size_t TableSize() {
  201. return _table.size();
  202. }
  203. // 非空哈希桶的个数
  204. size_t BucketNum() {
  205. size_t num = 0;
  206. for(auto&ptr:_table) {
  207. if(ptr)
  208. num++;
  209. }
  210. return num;
  211. }
  212. // 哈希表中数据的个数
  213. size_t Size() {
  214. return _size;
  215. }
  216. // 最大的桶的长度
  217. size_t MaxBucketLength() {
  218. size_t max = 0;
  219. for(size_t i = 0; i < _table.size(); ++i) {
  220. size_t len = 0;
  221. Node* ptr = _table[i];
  222. while(ptr)
  223. {
  224. len++;
  225. ptr = ptr->_next;
  226. }
  227. if(len > max)
  228. max = len;
  229. // if (len > 0)
  230. // printf("[%d]号桶长度:%d\n", i, len);
  231. }
  232. return max;
  233. }
  234. private:
  235. vector<HashNode<T>*> _table;
  236. size_t _size = 0;
  237. };

Unordered_map.h 

  1. //
  2. // Created by yangzilong on 2022/11/16.
  3. //
  4. #pragma once
  5. #include "HashTable.h"
  6. namespace yzl
  7. {
  8. template <class K>
  9. struct MapEqual
  10. {
  11. bool operator()(const K& k1, const K& k2) {
  12. return k1 == k2;
  13. }
  14. };
  15. // unordered_map的key需要支持转为整型,相等判断,若关键字类型不支持,可传递仿函数类。
  16. template<class K, class V, class Hash = hash<K>, class Equal = MapEqual<K>>
  17. class unordered_map {
  18. struct MapKeyOfT
  19. {
  20. const K& operator()(const pair<K,V>& kv) {
  21. return kv.first;
  22. }
  23. };
  24. public:
  25. typedef typename HashTable<K, pair<K,V>, MapKeyOfT, Hash, Equal>::iterator iterator;
  26. iterator begin() {
  27. return _table.begin();
  28. }
  29. iterator end() {
  30. return _table.end();
  31. }
  32. pair<iterator, bool> insert(const pair<K,V>& kv) {
  33. return _table.Insert(kv);
  34. }
  35. V& operator[](const K& key) {
  36. pair<iterator, bool> ret = _table.Insert(make_pair(key, V()));
  37. return ret.first->second;
  38. }
  39. private:
  40. HashTable<K, pair<K,V>, MapKeyOfT, Hash, Equal> _table;
  41. };
  42. }

unordered_set.h

  1. //
  2. // Created by yangzilong on 2022/11/16.
  3. //
  4. #pragma once
  5. #include "HashTable.h"
  6. namespace yzl
  7. {
  8. template <class K>
  9. struct hash
  10. {
  11. size_t operator()(const K& key) {
  12. return key;
  13. }
  14. };
  15. // 模板特化
  16. template <>
  17. struct hash<string>
  18. {
  19. size_t operator()(const string& str) {
  20. size_t sum = 0;
  21. for(auto&ch:str)
  22. {
  23. sum*=131;
  24. sum+=ch;
  25. }
  26. return sum;
  27. }
  28. };
  29. template <class K>
  30. struct SetEqual
  31. {
  32. bool operator()(const K& k1, const K& k2) {
  33. return k1 == k2;
  34. }
  35. };
  36. template<class K, class Hash = hash<K>, class Equal = SetEqual<K>>
  37. class unordered_set {
  38. struct SetKeyOfT
  39. {
  40. const K& operator()(const K& key) {
  41. return key;
  42. }
  43. };
  44. public:
  45. typedef typename HashTable<K, K, SetKeyOfT, Hash, Equal>::iterator iterator;
  46. iterator begin() {
  47. return _table.begin();
  48. }
  49. iterator end() {
  50. return _table.end();
  51. }
  52. pair<iterator, bool> insert(const K& key) {
  53. return _table.Insert(key);
  54. }
  55. private:
  56. HashTable<K, K, SetKeyOfT, Hash, Equal> _table;
  57. };
  58. }

三、解析:

0. 这里和红黑树封装map set的整体结构十分相似。红黑树的数据成员是一个RBTreeNode*(因为是树结构),而这里的哈希表采取开散列法,存储的是vector<HashNode<T>*> _table 和一个size_t _size;     

1. Insert,Find成员函数的实现都在哈希表类中。而unordered_map 和 unordered_set只是对哈希表的Insert和Find进行了简单调用封装。这样是因为unordered_map和 unordered_set只是在哈希表中存储的数据类型不同,一个是key value键值对类型,一个是key类型。而这里的实现最关键的也是利用了C++的模板。

2. 明白这里的模板参数的作用与对应关系:

这里和红黑树封装map set很类似。只是因为红黑树和哈希表结构的不同,对关键值的使用和要求不同,新增了一些模板参数实现一些功能。

这里的unordered_map 和 unordered_set 对比STL实现,除了少了最后一个内存池模板参数,其他都一样。

开散列哈希表中结点存储的数据类型只有一个T,u_map为pair  u_set为K。这是由HashTable的第二个模板参数传递的。再往上追溯,u_map 和 u_set的数据成员中,只有一个HashTable,对应的传递的第二个模板实参就是pair<K,V>和K.

因为在哈希表的Find实现中,参数为关键值类型,而如果只传递HashTable的第二个T(value_type)类型,无法得知关键值类型,所以在HashTable中有了第一个模板参数。也就是set中,你可以用T代替K,因为它们一样,但是在map中,无法得到关键值类型,而Find和Erase中又要用,所以有了第一个模板参数K。

因为map和set的底层哈希表中T的类型不同,也就是结点中存储的数据类型不同,而有时候又要取出T中的key,这里T是pair或者K,对于unordered_set不需要取,但是对于unordered_map需要取出pair中的key。所以,传递第三个模板参数KeyOfT,是仿函数类型,用于取出T中的key。这里和map set 红黑树那里的功能一样。(这个东西不需要unordered_map unordered_set的使用者传递,所以实现在了容器类内部)

对于Hash模板参数:哈希表的关键在于求出关键值通过哈希函数得到的哈希地址,而上方采用的哈希函数是除留余数法,所以,需要要求关键值为整型(或者可以直接强转为整型)。但是使用哈希表时,key(K)又不可能永远都是整型或者可以转换成整型,故,当传递某些不能直接转为整型的关键值类型时,Hash类模板参数起到了仿函数的作用,用于将关键值转为整型,从而求出哈希地址。

Equal类模板参数:因为在哈希表的查找删除实现中,需要对关键值进行相等比较,所以,对于不支持==运算符的key(K)类型,可以传递Equal模板参数,仿函数类型,用于判断关键值是否相等。

3. 迭代器:

和map set有些类似,begin end等的实现直接实现在了哈希表中,unordered_map unordered_set只是简单封装。

关键在于思考哈希表迭代器的++如何实现:只有一个结点指针是不够的,所以迭代器中多了一个哈希表指针数据成员,所以,哈希表的类模板参数,迭代器这里都需要,并且还需要前置声明。

以往,在list map set的迭代器中,const迭代器和普通迭代器只通过Ptr Ref模板参数来指定operator* 和 operator->的返回类型即可区分,但是这里哈希表的迭代器并没有这样做,所以没有Ptr Ref类模板参数。const迭代器需要单独定义一份(STL中也是这样的)。具体怎么实现自己研究吧。

又因为哈希桶底层为单链表结构,故迭代器没有--操作,为单向迭代器(与map set的区别)

四、unordered_map unordered_set 和 map set对于关键值类型的要求:

map set底层为红黑树,因此,
关键值类型必须支持小于比较。若不支持,需要显式提供比较的仿函数

unordered_map unordered_set的底层为哈希,因此,
关键值类型需要支持转换成整型。若不支持,需要显式提供转换为整型的仿函数
关键值类型需要支持==比较。若不支持,需要显式提供进行等于比较的仿函数

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

闽ICP备14008679号