• 【C++】封装unordered_map和unordered_set(用哈希桶实现)


    前言:
           前面我们学习了unordered_map和unordered_set容器,比较了他们和map、set的查找效率,我们发现他们的效率比map、set高,进而我们研究他们的底层是由哈希实现。哈希是一种直接映射的方式,所以查找的效率很快。与学习红黑树和map、set的思路一样,我们现在学完了unordered_map和unordered_set,本章将模拟实现底层结构来封装该容器!

        作者建议在阅读本章前,可以先去看一下前面的红黑树封装map和set——红黑树封装map和set

    这两篇文章都重在强调泛型编程的思想,上一篇由于是初认识,作者讲解的会更详细一点~

    目录

    (一)如何复用一个哈希桶

    1、结点的定义:

    2、两个容器各自的模板参数类型​编辑

    3、改造哈希桶

    (二)哈希桶的迭代器的模拟实现

    1、begin()和end()的模拟实现

    2、operator*和operator->及operator!=和operator==的模拟实现 

    3、operator ++的模拟实现

    (三)迭代器和改造哈希桶的总代码

    (四)封装unordered_map和unordered_set


    (一)如何复用一个哈希桶

    我们学习过知道,unordered_map和unordered_set容器存放的结点并不一样,为了让它得到复用我们就需要对哈希桶进行改造,将哈希桶改造的更加泛型一点,既符合Key模型,也符合Key_Value模型。

    1、结点的定义:

     所以我们这里还是和封装map和set时一样,无论是Key还是Key_Value,都用一个类型T来接收,这里高维度的泛型哈希表中,实现还是用的是Kye_Value模型,K是不能省略的,同样的查找和删除要用,故我们可以引出两个容器各自模板参数类型。


    2、两个容器各自的模板参数类型

    如何取到想要的数据:

    • 我们给每个容器配一个仿函数
    • 各传不同的仿函数,拿到想要的不同的数据

    同时我们再给每个容器配一个哈希函数。

    3、改造哈希桶

    通过上面1和2,我们可以把各自存放的数据泛化成data:

    这样我们哈希桶的模板参数算是完成了

    • 哈希函数我们可以自由选择并传
    • 仿函数在各自容器的封装中实现,用于比较时我们可以取出各自容器想要的数据

    我们把上一篇文章封装的哈希桶拿来改造:

    1. //K --> 键值Key,T --> 数据
    2. //unordered_map ->HashTable<K, pair<K, V>, MapKeyOfT> _ht;
    3. //unordered_set ->HashTable<K, K, SetKeyOfT> _ht;
    4. template<class K, class T, class KeyOfT, class HashFunc>
    5. class HashTable
    6. {
    7. template<class K, class T, class KeyOfT, class HashFunc>
    8. friend class __HTIterator;
    9. typedef HashNode<T> Node;
    10. public:
    11. typedef __HTIterator<K, T, KeyOfT, HashFunc> iterator;
    12. iterator begin()
    13. {
    14. for (size_t i = 0; i < _tables.size(); i++)
    15. {
    16. Node* cur = _tables[i];
    17. if (cur)
    18. {
    19. return iterator(cur, this);
    20. }
    21. }
    22. return end();
    23. }
    24. iterator end()
    25. {
    26. return iterator(nullptr, this);
    27. }
    28. ~HashTable()
    29. {
    30. for (size_t i = 0; i < _tables.size(); i++)
    31. {
    32. Node* cur = _tables[i];
    33. while (cur)
    34. {
    35. Node* next = cur->_next;
    36. delete cur;
    37. cur = next;
    38. }
    39. _tables[i] = nullptr;
    40. }
    41. }
    42. size_t GetNextPrime(size_t prime)
    43. {
    44. const int PRIMECOUNT = 28;
    45. static const size_t primeList[PRIMECOUNT] =
    46. {
    47. 53, 97, 193, 389, 769,
    48. 1543, 3079, 6151, 12289, 24593,
    49. 49157, 98317, 196613, 393241, 786433,
    50. 1572869, 3145739, 6291469, 12582917, 25165843,
    51. 50331653, 100663319, 201326611, 402653189, 805306457,
    52. 1610612741, 3221225473, 4294967291
    53. };
    54. //获取比prime大那一个素数
    55. size_t i = 0;
    56. for (i = 0; i < PRIMECOUNT; i++)
    57. {
    58. if (primeList[i] > prime)
    59. return primeList[i];
    60. }
    61. return primeList[i];
    62. }
    63. pair<iterator, bool> Insert(const T& data)
    64. {
    65. HashFunc hf;
    66. KeyOfT kot;
    67. iterator pos = Find(kot(data));
    68. if (pos != end())
    69. {
    70. return make_pair(pos, false);
    71. }
    72. //负载因子 == 1 扩容 -- 平均每个桶挂一个结点
    73. if (_tables.size() == _n)
    74. {
    75. //size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
    76. size_t newSize = GetNextPrime(_tables.size());
    77. if (newSize != _tables.size())
    78. {
    79. vector<Node*> newTable;
    80. newTable.resize(newSize, nullptr);
    81. //遍历旧表
    82. for (size_t i = 0; i < _tables.size(); i++)
    83. {
    84. Node* cur = _tables[i];
    85. //再对每个桶挨个遍历
    86. while (cur)
    87. {
    88. Node* next = cur->_next;
    89. size_t hashi = hf(kot(cur->_data)) % newSize;
    90. //转移到新的表中
    91. cur->_next = newTable[hashi];
    92. newTable[hashi] = cur;
    93. cur = next;
    94. }
    95. //将原表置空
    96. _tables[i] = nullptr;
    97. }
    98. newTable.swap(_tables);
    99. }
    100. }
    101. size_t hashi = hf(kot(data));
    102. hashi %= _tables.size();
    103. //头插到对应的桶即可
    104. Node* newnode = new Node(data);
    105. newnode->_next = _tables[hashi];
    106. _tables[hashi] = newnode;
    107. //有效数据加一
    108. _n++;
    109. return make_pair(iterator(newnode, this), true);
    110. }
    111. iterator Find(const K& key)
    112. {
    113. if (_tables.size() == 0)
    114. {
    115. return iterator(nullptr, this);
    116. }
    117. KeyOfT kot;
    118. HashFunc hf;
    119. size_t hashi = hf(key);
    120. //size_t hashi = HashFunc()(key);
    121. hashi %= _tables.size();
    122. Node* cur = _tables[hashi];
    123. //找到指定的桶之后,顺着单链表挨个找
    124. while (cur)
    125. {
    126. if (kot(cur->_data) == key)
    127. {
    128. return iterator(cur, this);
    129. }
    130. cur = cur->_next;
    131. }
    132. //没找到返回空
    133. return iterator(nullptr, this);
    134. }
    135. bool Erase(const K& key)
    136. {
    137. if (_tables.size() == 0)
    138. {
    139. return false;
    140. }
    141. HashFunc hf;
    142. KeyOfT kot;
    143. size_t hashi = hf(key);
    144. hashi %= _tables.size();
    145. //单链表删除结点
    146. Node* prev = nullptr;
    147. Node* cur = _tables[hashi];
    148. while (cur)
    149. {
    150. if (kot(cur->_data) == key)
    151. {
    152. //头删
    153. if (prev == nullptr)
    154. {
    155. _tables[hashi] = cur->_next;
    156. }
    157. else
    158. {
    159. prev->_next = cur->_next;
    160. }
    161. delete cur;
    162. return true;
    163. }
    164. prev = cur;
    165. cur = cur->_next;
    166. }
    167. return false;
    168. }
    169. private:
    170. //指针数组
    171. vector<Node*> _tables;
    172. size_t _n = 0;
    173. };

    主要改造的地方就是上述所注意的地方:

    • 比较时需要调用各自的仿函数
    • 调用外部传的哈希函数

    还有对扩容的二次思考:

    研究表明:除留余数法,最好模一个素数

    • 通过查STL官方库我们也发现,其提供了一个取素数的函数
    • 所以我们也提供了一个,直接拷贝过来
      • 这样我们在扩容时就可以每次给素数个桶
      • 在扩容时加了一条判断语句是为了防止素数值太大,过分扩容容易直接把空间(堆)干崩了

    (二)哈希桶的迭代器的模拟实现

    1、begin()和end()的模拟实现

    • 以第一个桶中第一个不为空的结点为整个哈希桶的开始结点
    • 以空结点为哈希桶的结束结点

    2、operator*和operator->及operator!=和operator==的模拟实现 

    这两组和之前实现的一模一样,大家自行理解。

    3、operator ++的模拟实现

    注:

    • 这里要在哈希桶的类外面访问其私有成员
    • 我们要搞一个友元类
    • 迭代器类是哈希桶类的朋友
    • 这样就可以访问了

     

    思路:

    • 判断一个桶中的数据是否遍历完
    • 如果所在的桶没有遍历完,在该桶中返回下一个结点指针
    • 如果所在的桶遍历完了,进入下一个桶
    • 判断下一个桶是否为空
    • 非空返回桶中第一个节点
    • 空的话就遍历一个桶
    • 后置++和之前一眼老套路,不赘述

    注意:

    unordered_map和unordered_set是不支持反向迭代器的,从底层结构我们也能很好的理解(单链表找不了前驱)所以不支持实现迭代器的operator- -

    最后注意一点,我们需要知道哈希桶大小,所以不仅要传结点地址,还要传一个哈希桶,这样才能知道其大小,除此,由于哈希桶改造在后面,所以我们要在前面声明一下:

    (三)迭代器和改造哈希桶的总代码

    1. #include<vector>
    2. #include<string>
    3. #include<iostream>
    4. using namespace std;
    5. template<class K>
    6. struct DefaultHash
    7. {
    8. size_t operator()(const K& key)
    9. {
    10. return (size_t)key;
    11. }
    12. };
    13. template<>
    14. struct DefaultHash<string>
    15. {
    16. size_t operator()(const string& key)
    17. {
    18. //BKDR
    19. size_t hash = 0;
    20. for (auto ch : key)
    21. {
    22. hash = hash * 131 + ch;
    23. }
    24. return hash;
    25. }
    26. };
    27. namespace Bucket
    28. {
    29. template<class T>
    30. struct HashNode
    31. {
    32. T _data;
    33. HashNode<T>* _next;
    34. HashNode(const T& data)
    35. :_data(data)
    36. , _next(nullptr)
    37. {}
    38. };
    39. template<class K, class T, class KeyOfT, class HashFunc>
    40. class HashTable;
    41. //哈希桶的迭代器
    42. template<class K, class T, class KeyOfT, class HashFunc>
    43. class __HTIterator
    44. {
    45. typedef HashNode<T> Node;
    46. typedef __HTIterator<K, T, KeyOfT, HashFunc> Self;
    47. public:
    48. Node* _node;
    49. __HTIterator() {};
    50. //编译器的原则是向上查找(定义必须在前面,否则必须先声明)
    51. HashTable<K, T, KeyOfT, HashFunc>* _pht;
    52. __HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
    53. :_node(node)
    54. , _pht(pht)
    55. {}
    56. Self& operator++()
    57. {
    58. if (_node->_next)
    59. {
    60. _node = _node->_next;
    61. }
    62. else//当前桶已经走完了,要走下一个桶
    63. {
    64. KeyOfT kot;
    65. HashFunc hf;
    66. size_t hashi = hf(kot(_node->_data)) % _pht->_tables.size();
    67. hashi++;
    68. //找下一个不为空的桶 -- 访问到了哈希表中私有的成员(友元)
    69. for (; hashi < _pht->_tables.size(); hashi++)
    70. {
    71. if (_pht->_tables[hashi])
    72. {
    73. _node = _pht->_tables[hashi];
    74. break;
    75. }
    76. }
    77. //没有找到不为空的桶,用nullptr去做end标识
    78. if (hashi == _pht->_tables.size())
    79. {
    80. _node = nullptr;
    81. }
    82. }
    83. return *this;
    84. }
    85. T& operator*()
    86. {
    87. return _node->_data;
    88. }
    89. T* operator->()
    90. {
    91. return &_node->_data;
    92. }
    93. bool operator!=(const Self& s) const
    94. {
    95. return _node != s._node;
    96. }
    97. bool operator==(const Self& s) const
    98. {
    99. return _node == s._node;
    100. }
    101. };
    102. //K --> 键值Key,T --> 数据
    103. //unordered_map ->HashTable<K, pair<K, V>, MapKeyOfT> _ht;
    104. //unordered_set ->HashTable<K, K, SetKeyOfT> _ht;
    105. template<class K, class T, class KeyOfT, class HashFunc>
    106. class HashTable
    107. {
    108. template<class K, class T, class KeyOfT, class HashFunc>
    109. friend class __HTIterator;
    110. typedef HashNode<T> Node;
    111. public:
    112. typedef __HTIterator<K, T, KeyOfT, HashFunc> iterator;
    113. iterator begin()
    114. {
    115. for (size_t i = 0; i < _tables.size(); i++)
    116. {
    117. Node* cur = _tables[i];
    118. if (cur)
    119. {
    120. return iterator(cur, this);
    121. }
    122. }
    123. return end();
    124. }
    125. iterator end()
    126. {
    127. return iterator(nullptr, this);
    128. }
    129. ~HashTable()
    130. {
    131. for (size_t i = 0; i < _tables.size(); i++)
    132. {
    133. Node* cur = _tables[i];
    134. while (cur)
    135. {
    136. Node* next = cur->_next;
    137. delete cur;
    138. cur = next;
    139. }
    140. _tables[i] = nullptr;
    141. }
    142. }
    143. size_t GetNextPrime(size_t prime)
    144. {
    145. const int PRIMECOUNT = 28;
    146. static const size_t primeList[PRIMECOUNT] =
    147. {
    148. 53, 97, 193, 389, 769,
    149. 1543, 3079, 6151, 12289, 24593,
    150. 49157, 98317, 196613, 393241, 786433,
    151. 1572869, 3145739, 6291469, 12582917, 25165843,
    152. 50331653, 100663319, 201326611, 402653189, 805306457,
    153. 1610612741, 3221225473, 4294967291
    154. };
    155. //获取比prime大那一个素数
    156. size_t i = 0;
    157. for (i = 0; i < PRIMECOUNT; i++)
    158. {
    159. if (primeList[i] > prime)
    160. return primeList[i];
    161. }
    162. return primeList[i];
    163. }
    164. pair<iterator, bool> Insert(const T& data)
    165. {
    166. HashFunc hf;
    167. KeyOfT kot;
    168. iterator pos = Find(kot(data));
    169. if (pos != end())
    170. {
    171. return make_pair(pos, false);
    172. }
    173. //负载因子 == 1 扩容 -- 平均每个桶挂一个结点
    174. if (_tables.size() == _n)
    175. {
    176. //size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
    177. size_t newSize = GetNextPrime(_tables.size());
    178. if (newSize != _tables.size())
    179. {
    180. vector<Node*> newTable;
    181. newTable.resize(newSize, nullptr);
    182. //遍历旧表
    183. for (size_t i = 0; i < _tables.size(); i++)
    184. {
    185. Node* cur = _tables[i];
    186. //再对每个桶挨个遍历
    187. while (cur)
    188. {
    189. Node* next = cur->_next;
    190. size_t hashi = hf(kot(cur->_data)) % newSize;
    191. //转移到新的表中
    192. cur->_next = newTable[hashi];
    193. newTable[hashi] = cur;
    194. cur = next;
    195. }
    196. //将原表置空
    197. _tables[i] = nullptr;
    198. }
    199. newTable.swap(_tables);
    200. }
    201. }
    202. size_t hashi = hf(kot(data));
    203. hashi %= _tables.size();
    204. //头插到对应的桶即可
    205. Node* newnode = new Node(data);
    206. newnode->_next = _tables[hashi];
    207. _tables[hashi] = newnode;
    208. //有效数据加一
    209. _n++;
    210. return make_pair(iterator(newnode, this), true);
    211. }
    212. iterator Find(const K& key)
    213. {
    214. if (_tables.size() == 0)
    215. {
    216. return iterator(nullptr, this);
    217. }
    218. KeyOfT kot;
    219. HashFunc hf;
    220. size_t hashi = hf(key);
    221. //size_t hashi = HashFunc()(key);
    222. hashi %= _tables.size();
    223. Node* cur = _tables[hashi];
    224. //找到指定的桶之后,顺着单链表挨个找
    225. while (cur)
    226. {
    227. if (kot(cur->_data) == key)
    228. {
    229. return iterator(cur, this);
    230. }
    231. cur = cur->_next;
    232. }
    233. //没找到返回空
    234. return iterator(nullptr, this);
    235. }
    236. bool Erase(const K& key)
    237. {
    238. if (_tables.size() == 0)
    239. {
    240. return false;
    241. }
    242. HashFunc hf;
    243. KeyOfT kot;
    244. size_t hashi = hf(key);
    245. hashi %= _tables.size();
    246. //单链表删除结点
    247. Node* prev = nullptr;
    248. Node* cur = _tables[hashi];
    249. while (cur)
    250. {
    251. if (kot(cur->_data) == key)
    252. {
    253. //头删
    254. if (prev == nullptr)
    255. {
    256. _tables[hashi] = cur->_next;
    257. }
    258. else
    259. {
    260. prev->_next = cur->_next;
    261. }
    262. delete cur;
    263. return true;
    264. }
    265. prev = cur;
    266. cur = cur->_next;
    267. }
    268. return false;
    269. }
    270. private:
    271. //指针数组
    272. vector<Node*> _tables;
    273. size_t _n = 0;
    274. };
    275. }

    (四)封装unordered_map和unordered_set

    有了上面的哈希桶的改装,我们这里的对map和set的封装就显得很得心应手了。

    unordered_map的封装:

    1. #include "HashTable.h"
    2. namespace zc
    3. {
    4. template<class K, class V, class HashFunc = DefaultHash<K>>
    5. class unordered_map
    6. {
    7. struct MapKeyOfT
    8. {
    9. const K& operator()(const pair<K, V>& kv)
    10. {
    11. return kv.first;
    12. }
    13. };
    14. public:
    15. typedef typename Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc>::iterator iterator;
    16. iterator begin()
    17. {
    18. return _ht.begin();
    19. }
    20. iterator end()
    21. {
    22. return _ht.end();
    23. }
    24. pair<iterator, bool> insert(const pair<K, V>& kv)
    25. {
    26. return _ht.Insert(kv);
    27. }
    28. iterator find(const K& key)
    29. {
    30. return _ht.Find(key);
    31. }
    32. bool erase(const K& key)
    33. {
    34. return _ht.Erase(key);
    35. }
    36. V& operator[](const K& key)
    37. {
    38. pair<iterator, bool> ret = insert(make_pair(key, V()));
    39. return ret.first->second;
    40. }
    41. private:
    42. Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc> _ht;
    43. };
    44. void test_map()
    45. {
    46. unordered_map<string, string> dict;
    47. dict.insert(make_pair("sort", "排序"));
    48. dict.insert(make_pair("left", "左边"));
    49. dict.insert(make_pair("left", "下面"));
    50. dict["string"];
    51. dict["left"] = "上面";
    52. dict["string"] = "字符串";
    53. unordered_map<string, string>::iterator it = dict.begin();
    54. while (it != dict.end())
    55. {
    56. cout << it->first << " " << it->second << endl;
    57. ++it;
    58. }
    59. cout << endl;
    60. for (auto e : dict)
    61. {
    62. cout << e.first << " " << e.second << endl;
    63. }
    64. }
    65. }

    这里unordered_map中的operator[ ]我们知道其原理之后,模拟实现就非常方便,直接调用插入函数,控制好参数和返回值即可。

    对unordered_set的封装:

    1. #include "HashTable.h"
    2. #include "HashTable.h"
    3. namespace zc
    4. {
    5. template<class K, class HashFunc = DefaultHash<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. //2.48
    17. typedef typename Bucket::HashTable<K, K, SetKeyOfT, HashFunc>::iterator iterator;
    18. iterator begin()
    19. {
    20. return _ht.begin();
    21. }
    22. iterator end()
    23. {
    24. return _ht.end();
    25. }
    26. pair<iterator, bool> insert(const K& key)
    27. {
    28. return _ht.Insert(key);
    29. }
    30. iterator find(const K& key)
    31. {
    32. return _ht.Find(key);
    33. }
    34. bool erase(const K& key)
    35. {
    36. return _ht.Erase(key);
    37. }
    38. private:
    39. Bucket::HashTable<K, K, SetKeyOfT, HashFunc> _ht;
    40. };
    41. struct Date
    42. {
    43. Date(int year = 1, int month = 1, int day = 1)
    44. :_year(year)
    45. , _month(month)
    46. , _day(day)
    47. {}
    48. bool operator==(const Date& d) const
    49. {
    50. return _year == d._year
    51. && _month == d._month
    52. && _day == d._day;
    53. }
    54. int _year;
    55. int _month;
    56. int _day;
    57. };
    58. struct DateHash
    59. {
    60. size_t operator()(const Date& d)
    61. {
    62. //return d._year + d._month + d._day;
    63. size_t hash = 0;
    64. hash += d._year;
    65. hash *= 131;
    66. hash += d._month;
    67. hash *= 1313;
    68. hash += d._day;
    69. //cout << hash << endl;
    70. return hash;
    71. }
    72. };
    73. void test_set()
    74. {
    75. unordered_set<int> s;
    76. //set<int> s;
    77. s.insert(2);
    78. s.insert(3);
    79. s.insert(1);
    80. s.insert(2);
    81. s.insert(5);
    82. s.insert(12);
    83. unordered_set<int>::iterator it = s.begin();
    84. //auto it = s.begin();
    85. while (it != s.end())
    86. {
    87. cout << *it << " ";
    88. ++it;
    89. }
    90. cout << endl;
    91. for (auto e : s)
    92. {
    93. cout << e << " ";
    94. }
    95. cout << endl;
    96. unordered_set<Date, DateHash> sd;
    97. sd.insert(Date(2022, 3, 4));
    98. sd.insert(Date(2022, 4, 3));
    99. }
    100. }

    最后大家可以利用代码中给的测试函数进行测试!

    感谢你的阅读!

  • 相关阅读:
    小程序怎么做?个人小程序怎么做?新手教程
    Kafka高性能
    【云原生】Kubernetes----Metrics-Server组件与HPA资源
    PythonStudy5
    【无标题】
    而今迈步从头越|nacos逼我在mac上重新安装java8与环境变量的配置
    jQuery【菜单功能、淡入淡出轮播图(上)、淡入淡出轮播图(下)、折叠面板】(五)-全面详解(学习总结---从入门到深化)
    阿里云与信通院邀您参与云原生安全用户调研
    综述 | 关于点云配准的全面综述(一)
    Python&C++相互混合调用编程全面实战-21依赖的QT环境安装和信号槽机制的讲解
  • 原文地址:https://blog.csdn.net/m0_67821824/article/details/132839930