• C++ 哈希桶模拟实现(补充)


    目录

    定义基本的存储结构

    Insert()和Find()

    Erase()

    如何控制哈希冲突?

    Insert()中添加扩容操作

    其他问题的解决

     

    UnorderedMap.h和UnorderedSet.h

    迭代器实现与UnorderedMap.h和UnorderedSet.h的封装


    定义基本的存储结构

    1. #pragma once
    2. #include
    3. #include
    4. using namespace std;
    5. namespace OpenHash
    6. {
    7. template<class K,class V>
    8. struct HashNode
    9. {
    10. HashNode* _next;
    11. pair _kv;
    12. //构造函数
    13. HashNode(const pair& kv)
    14. :_next(nullptr)
    15. ,_kv(kv)
    16. {}
    17. };
    18. template<class K,class V>
    19. class HashTable
    20. {
    21. typedef HashNodeNode;
    22. public:
    23. //待写
    24. private:
    25. vector _table;
    26. size_t _n = 0; //有效数据的个数
    27. };
    28. }

    Insert()和Find()

    1. bool Insert(const pair& kv)
    2. {
    3. //Find()函数防止冗余
    4. if (Find(kv.first))
    5. return false;
    6. size_t index = kv.first % _table.size();
    7. //采用头插法,尾插法还需要找尾,麻烦
    8. Node* newnode = new Node(kv);
    9. newnode->_next = _table[index];
    10. _table[index] = newnode;
    11. ++_n;
    12. return true;
    13. }
    14. Node* Find(const K& key)
    15. {
    16. size_t index = kv.first % _table.size();
    17. Node* cur = _table[index];
    18. while (cur)
    19. {
    20. if (cur->_kv.first == key)
    21. {
    22. return cur;
    23. }
    24. else
    25. {
    26. cur = cur->_next;
    27. }
    28. }
    29. return nullptr;
    30. }

    Erase()

    思考:如果单链表不给头结点,如何删除指定节点?采用替换法删除

    将下一个节点的值赋给指定节点,再删除下一个节点

     思考:采用替换法如何删除尾节点?以下图为例,删除104节点

     

    1. bool Erase(const K& key)
    2. {
    3. size_t index = kv.first % _table.size();
    4. Node* prev = nullptr;
    5. Node* cur = _table[index];
    6. while (cur)
    7. {
    8. if (cur->_kv.first == key)
    9. {
    10. if (_table[index] == cur)
    11. {
    12. _table[index] = cur->_next;
    13. }
    14. else
    15. {
    16. prev->_next = cur->_next;
    17. }
    18. --_n;
    19. delete cur;
    20. return true;
    21. }
    22. prev = cur;
    23. cur = cur->_next;
    24. }
    25. return false;
    26. }

    如何控制哈希冲突

     

    Insert()中添加扩容操作

    1. bool Insert(const pair& kv)
    2. {
    3. //Find()函数防止冗余
    4. if (Find(kv.first))
    5. return false;
    6. //负载因子到1的时候增容
    7. //负载因子为0的时候要防止除零错误
    8. if (_n == _table.size())
    9. {
    10. vector newtable;
    11. size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
    12. newtable.resize(newSize, nullptr);
    13. //遍历取旧表中节点,重新算映射到新表中的位置,挂到新表中
    14. for (size_t i = 0; i < _table.size(); ++i)
    15. {
    16. if (_table[i])//operator[]会检查给的下标是否在0到size这个范围之内
    17. {
    18. Node* cur = _table[i];
    19. while (cur)//若不为空,则把该桶取完,计算其在新表中的位置
    20. {
    21. Node* next = cur->_next;//保存cur的下一个节点
    22. size_t index = cur->_kv.first % newtable.size();
    23. //头插
    24. cur->_next = newtable[index];
    25. _table[index] = cur;
    26. cur = next;
    27. }
    28. _table[i] = nullptr;//都处理完后旧表的当前位置可置空
    29. }
    30. }
    31. _table.swap(newtable); //交换新旧表
    32. }
    33. size_t index = kv.first % _table.size();
    34. //采用头插法,尾插法还需要找尾,麻烦
    35. Node* newnode = new Node(kv);
    36. newnode->_next = _table[index];
    37. _table[index] = newnode;
    38. ++_n;
    39. return true;
    40. }

     

    其他问题的解决

    1.K不支持取模的情况?提供一个默认返回的仿函数,再提供一个特化的仿函数。后续需要取模的地方使用仿函数即可(详细见全部代码)

    1. template<class K>
    2. struct Hash
    3. {
    4. size_t operator()(const K& key)
    5. {
    6. return key;
    7. }
    8. };
    9. // 特化
    10. template<>
    11. struct Hash
    12. {
    13. // "int" "insert"
    14. // 字符串转成对应一个整形值,因为整形才能取模算映射位置
    15. // 期望->字符串不同,转出的整形值尽量不同
    16. // "abcd" "bcad"
    17. // "abbb" "abca"
    18. size_t operator()(const string& s)
    19. {
    20. // BKDR Hash
    21. size_t value = 0;
    22. for (auto ch : s)
    23. {
    24. value += ch;
    25. value *= 131;
    26. }
    27. return value;
    28. }
    29. };

    2.除了控制负载因子,尽量让表的大小为素数也可以控制哈希冲突。但是此时对于增容的挑战很大:要保证增容二倍后还是表的大小还是素数。->通过素数表解决

    1. //提供一个素数表(来自STL源码)
    2. size_t GetNextPrime(size_t prime)
    3. {
    4. const int PRIMECOUNT = 28;
    5. static const size_t primeList[PRIMECOUNT] = //定义成静态的,不用每次重新创建数组
    6. {
    7. 53ul, 97ul, 193ul, 389ul, 769ul,
    8. 1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
    9. 49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
    10. 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
    11. 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
    12. 1610612741ul, 3221225473ul, 4294967291ul
    13. };
    14. size_t i = 0;
    15. for (; i < PRIMECOUNT; ++i)
    16. {
    17. //获取一个比当前素数要大的数
    18. if (primeList[i] > prime)
    19. return primeList[i];
    20. }
    21. return primeList[i];
    22. }

    此时增容函数就可进行修改

    1. bool Insert(const pair& kv)
    2. {
    3. //Find()函数防止冗余
    4. if (Find(kv.first))
    5. return false;
    6. HashFunc hf;
    7. //负载因子到1的时候增容
    8. //负载因子为0的时候要防止除零错误
    9. if (_n == _table.size())
    10. {
    11. vector newtable;
    12. //size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
    13. //newtable.resize(newSize, nullptr);
    14. newtable.resize(GetNextPrime(_table.size()));//通过素数表可重新写增容函数
    15. //遍历取旧表中节点,重新算映射到新表中的位置,挂到新表中
    16. for (size_t i = 0; i < _table.size(); ++i)
    17. {
    18. if (_table[i])//operator[]会检查给的下标是否在0到size这个范围之内
    19. {
    20. Node* cur = _table[i];
    21. while (cur)//若不为空,则把该桶取完,计算其在新表中的位置
    22. {
    23. Node* next = cur->_next;//保存cur的下一个节点
    24. size_t index = hf(cur->_kv.first) % newtable.size();
    25. //头插
    26. cur->_next = newtable[index];
    27. _table[index] = cur;
    28. cur = next;
    29. }
    30. _table[i] = nullptr;//都处理完后旧表的当前位置可置空
    31. }
    32. }
    33. _table.swap(newtable); //交换新旧表
    34. }
    35. size_t index = hf(kv.first) % _table.size();
    36. //采用头插法,尾插法还需要找尾,麻烦
    37. Node* newnode = new Node(kv);
    38. newnode->_next = _table[index];
    39. _table[index] = newnode;
    40. ++_n;
    41. return true;
    42. }

     

    UnorderedMap.h和UnorderedSet.h

    1. #pragma once
    2. #include"HashTable.h"
    3. namespace bit
    4. {
    5. template<class K, class V>
    6. class unordered_map
    7. {
    8. public:
    9. struct MapKeyOfT
    10. {
    11. const K& operator()(const pair& kv)
    12. {
    13. return kv.first;
    14. }
    15. };
    16. bool Insert(const pair& kv)
    17. {
    18. _ht.Insert(kv);
    19. return true;
    20. }
    21. private:
    22. OpenHash::HashTable, MapKeyOfT>_ht;
    23. };
    24. }
    1. #pragma once
    2. #include"HashTable.h"
    3. namespace bit
    4. {
    5. template<class K, class V>
    6. class unordered_set
    7. {
    8. public:
    9. struct SetKeyOfT
    10. {
    11. const K& operator()(const K& k)
    12. {
    13. return k;
    14. }
    15. };
    16. bool Insert(const K k)
    17. {
    18. _ht.Insert(k);
    19. return true;
    20. }
    21. private:
    22. OpenHash::HashTable_ht;
    23. };
    24. }

    迭代器实现与UnorderedMap.h和UnorderedSet.h的封装

    各个部分的改动和添加看注释即可,最终整体代码如下

    1. #pragma once
    2. #include
    3. #include
    4. using namespace std;
    5. namespace OpenHash
    6. {
    7. template<class K>
    8. struct Hash
    9. {
    10. size_t operator()(const K& key)
    11. {
    12. return key;
    13. }
    14. };
    15. // 特化
    16. template<>
    17. struct Hash
    18. {
    19. // "int" "insert"
    20. // 字符串转成对应一个整形值,因为整形才能取模算映射位置
    21. // 期望->字符串不同,转出的整形值尽量不同
    22. // "abcd" "bcad"
    23. // "abbb" "abca"
    24. size_t operator()(const string& s)
    25. {
    26. // BKDR Hash
    27. size_t value = 0;
    28. for (auto ch : s)
    29. {
    30. value += ch;
    31. value *= 131;
    32. }
    33. return value;
    34. }
    35. };
    36. template<class T>
    37. struct HashNode
    38. {
    39. HashNode* _next;
    40. T _data;
    41. HashNode(const T& data)
    42. :_next(nullptr)
    43. ,_data(data)
    44. {}
    45. };
    46. //前置声明
    47. //前置声明不需要给默认参数
    48. template<class K, class T, class KeyOfT, class HashFunc>
    49. class HashTable;
    50. //哈希桶迭代器
    51. template<class K, class T, class KeyOfT, class HashFunc = Hash>
    52. struct _HTIterator
    53. {
    54. typedef HashNode Node;
    55. typedef _HTIteratorSelf;
    56. typedef HashTableHT;
    57. Node* _node;
    58. HT* _pht;//传哈希表的指针
    59. _HTIterator(Node* node,HT* pht)
    60. :_node(node)
    61. ,_pht(pht)
    62. {}
    63. Self& operator++()
    64. {
    65. //1.当前桶中还有数据,那么就在当前桶往后走
    66. //2.当前桶走完了,需要往下一个桶去走
    67. if (_node->_next)
    68. {
    69. _node = _node->_next;
    70. }
    71. else
    72. {
    73. //迭代器走到当前桶的最后一个位置的时候如何进入下一个桶?
    74. _pht->_table.size();
    75. KeyOfT kot;
    76. HashFunc hf;
    77. size_t index = hf(kot(_node->_data)) % _pht->_table.size();//算出当前桶的位置
    78. //通过keyoft的仿函数取出data里面的k取出,k再通过HashFunc的仿函数转换成可以取模的整型
    79. ++index;
    80. while (index < _pht->_table.size())
    81. {
    82. if (_pht->_table[index])//找到下一个桶了
    83. {
    84. _node = _pht->_table[index];//指向桶里面的第一个节点
    85. return *this;
    86. }
    87. else
    88. {
    89. ++index;
    90. }
    91. }
    92. //return iterator(nullptr, _pht);//后面没有桶了,返回空的迭代器
    93. _node = nullptr;
    94. }
    95. return *this;
    96. }
    97. T& operator*()
    98. {
    99. return _node->_data;
    100. }
    101. T* operator->()
    102. {
    103. return &_node->_data;
    104. }
    105. bool operator != (const Self& s)const
    106. {
    107. return _node != s._node;
    108. }
    109. bool operator == (const Self& s)const
    110. {
    111. return _node == s._node;
    112. }
    113. };
    114. template<class K,class T, class KeyOfT,class HashFunc = Hash>
    115. class HashTable
    116. {
    117. typedef HashNodeNode;
    118. //设计类模板的友元
    119. template<class K, class T, class KeyOfT, class HashFunc>//需要添加模板参数
    120. friend struct _HTIterator;
    121. public:
    122. typedef _HTIterator iterator;
    123. //默认的构造
    124. HashTable() = default; //C++11关键词:显示指定生成默认构造
    125. //拷贝构造赋值
    126. HashTable(const HashTable& ht)
    127. {
    128. //深拷贝
    129. _n = ht._n;
    130. _table.resize(ht._table.size());
    131. for (size_t i = 0; i < ht._table.size(); i++)
    132. {
    133. Node* cur = ht._table[i];
    134. while (cur)
    135. {
    136. Node* copy = new Node(cur->_data);
    137. //头插到新表
    138. copy->_next = table[i];
    139. _table[i] = copy;
    140. cur = cur->_next;
    141. }
    142. }
    143. }
    144. //赋值
    145. HashTable& operator=(const HashTable ht)
    146. {
    147. _table.swap(ht._table);
    148. swap(_n,ht._n);
    149. return *this;
    150. }
    151. //析构
    152. ~HashTable()
    153. {
    154. for (size_t i = 0; i < _table.size();i++)
    155. {
    156. Node* cur = _table[i];
    157. while (cur)
    158. {
    159. Node* next = cur->_next;//保存当前节点
    160. delete cur; //释放当前节点
    161. cur = next;
    162. }
    163. _table[i] = nullptr;
    164. }
    165. }
    166. iterator begin()
    167. {
    168. //返回第一个桶里面的第一个节点
    169. size_t i = 0;
    170. while (i<_table.size())
    171. {
    172. if (_table[i])//不为空就找到第一个桶
    173. {
    174. return iterator(_table[i],this);//迭代器需要一个节点的指针,_table[i]就是节点的指针
    175. //成员函数this就是自己对象的地址,即this就是哈希表的地址
    176. }
    177. ++i;
    178. }
    179. return end();
    180. }
    181. iterator end()
    182. {
    183. return iterator(nullptr, this);
    184. }
    185. //提供一个素数表(来自STL源码)
    186. size_t GetNextPrime(size_t prime)
    187. {
    188. const int PRIMECOUNT = 28;
    189. static const size_t primeList[PRIMECOUNT] = //定义成静态的,不用每次重新创建数组
    190. {
    191. 53ul, 97ul, 193ul, 389ul, 769ul,
    192. 1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
    193. 49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
    194. 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
    195. 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
    196. 1610612741ul, 3221225473ul, 4294967291ul
    197. };
    198. size_t i = 0;
    199. for (; i < PRIMECOUNT; ++i)
    200. {
    201. //获取一个比当前素数要大的数
    202. if (primeList[i] > prime)
    203. return primeList[i];
    204. }
    205. return primeList[i];
    206. }
    207. pairbool> Insert(const T& data)
    208. {
    209. KeyOfT kot;
    210. //找到了
    211. auto ret = Find(kot(data));
    212. if(ret != end())
    213. return make_pair(ret,false);
    214. HashFunc hf;
    215. //负载因子到1的时候增容
    216. //负载因子为0的时候要防止除零错误
    217. if (_n == _table.size())
    218. {
    219. vector newtable;
    220. //size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
    221. //newtable.resize(newSize, nullptr);
    222. newtable.resize(GetNextPrime(_table.size()));//通过素数表可重新写增容函数
    223. //遍历取旧表中节点,重新算映射到新表中的位置,挂到新表中
    224. for (size_t i = 0; i < _table.size(); ++i)
    225. {
    226. if (_table[i])//operator[]会检查给的下标是否在0到size这个范围之内
    227. {
    228. Node* cur = _table[i];
    229. while (cur)//若不为空,则把该桶取完,计算其在新表中的位置
    230. {
    231. Node* next = cur->_next;//保存cur的下一个节点
    232. size_t index = hf(kot(cur->_data)) % newtable.size();
    233. //头插
    234. cur->_next = newtable[index];
    235. _table[index] = cur;
    236. cur = next;
    237. }
    238. _table[i] = nullptr;//都处理完后旧表的当前位置可置空
    239. }
    240. }
    241. _table.swap(newtable); //交换新旧表
    242. }
    243. size_t index = hf(kot(data)) % _table.size();
    244. //采用头插法,尾插法还需要找尾,麻烦
    245. Node* newnode = new Node(data);
    246. newnode->_next = _table[index];
    247. _table[index] = newnode;
    248. ++_n;
    249. return make_pair(iterator(newnode, this), true);
    250. }
    251. iterator Find(const K& key)
    252. {
    253. if (_table.size() == 0)
    254. {
    255. return end();
    256. }
    257. KeyOfT kot;
    258. HashFunc hf;
    259. size_t index = hf(key) % _table.size();
    260. Node* cur = _table[index];
    261. while (cur)
    262. {
    263. if (kot(cur->_data) == key)
    264. {
    265. return iterator(cur,this);//Find的返回值为迭代器
    266. }
    267. else
    268. {
    269. cur = cur->_next;
    270. }
    271. }
    272. return end();
    273. }
    274. bool Erase(const K& key)
    275. {
    276. size_t index = hf(kot(key)) % _table.size();
    277. Node* prev = nullptr;
    278. Node* cur = _table[index];
    279. while (cur)
    280. {
    281. if (kot(cur->data) == key)
    282. {
    283. if (_table[index] == cur)
    284. {
    285. _table[index] = cur->_next;
    286. }
    287. else
    288. {
    289. prev->_next = cur->_next;
    290. }
    291. --_n;
    292. delete cur;
    293. return true;
    294. }
    295. prev = cur;
    296. cur = cur->_next;
    297. }
    298. return false;
    299. }
    300. void TestHashTable1()
    301. {
    302. int a[] = { 1,5,30,100000,100,18,15,7,40,44 };
    303. HashTable<int, int>ht;
    304. for (auto e : a)
    305. {
    306. ht.Insert(make_pair(e.e));
    307. }
    308. ht.Insert(make_pair(25, 25));
    309. }
    310. private:
    311. vector _table;
    312. size_t _n = 0; //有效数据的个数
    313. };
    314. }
    1. #pragma once
    2. #include "HashTable.h"
    3. namespace bit
    4. {
    5. template<class K>
    6. class unordered_set
    7. {
    8. struct SetKeyOfT
    9. {
    10. const K& operator()(const K& k)
    11. {
    12. return k;
    13. }
    14. };
    15. public:
    16. typedef typename OpenHash::HashTable::iterator iterator;
    17. iterator begin()
    18. {
    19. return _ht.begin();
    20. }
    21. iterator end()
    22. {
    23. return _ht.end();
    24. }
    25. pairbool> insert(const K k)
    26. {
    27. return _ht.Insert(k);
    28. }
    29. private:
    30. OpenHash::HashTable _ht;
    31. };
    32. void test_unordered_set1()
    33. {
    34. unordered_set<int> us;
    35. us.insert(200);
    36. us.insert(1);
    37. us.insert(2);
    38. us.insert(33);
    39. us.insert(50);
    40. us.insert(60);
    41. us.insert(243);
    42. us.insert(6);
    43. unordered_set<int>::iterator it = us.begin();
    44. while (it != us.end())
    45. {
    46. cout << *it << " ";
    47. ++it;
    48. }
    49. }
    50. }
    1. #pragma once
    2. #include "HashTable.h"
    3. namespace bit
    4. {
    5. template<class K, class V>
    6. class unordered_map
    7. {
    8. struct MapKeyOfT
    9. {
    10. const K& operator()(const pair& kv)
    11. {
    12. return kv.first;
    13. }
    14. };
    15. public:
    16. typedef typename OpenHash::HashTable, MapKeyOfT>::iterator iterator;
    17. iterator begin()
    18. {
    19. return _ht.begin();
    20. }
    21. iterator end()
    22. {
    23. return _ht.end();
    24. }
    25. pairbool> insert(const pair& kv)
    26. {
    27. return _ht.Insert(kv);
    28. }
    29. V& operator[](const K& key)
    30. {
    31. pairbool> ret = _ht.Insert(make_pair(key, V()));
    32. return ret.first->second;
    33. }
    34. private:
    35. OpenHash::HashTable, MapKeyOfT> _ht;
    36. };
    37. void test_unordered_map1()
    38. {
    39. unordered_map dict;
    40. dict.insert(make_pair("sort", "排序"));
    41. dict["left"] = "左边";
    42. dict["left"] = "剩余";
    43. dict["map"] = "映射";
    44. dict["string"] = "字符串ַ";
    45. dict["set"] = "集合";
    46. unordered_map::iterator it = dict.begin();
    47. while (it != dict.end())
    48. {
    49. cout << it->first << ":" << it->second << endl;
    50. ++it;
    51. }
    52. cout << endl;
    53. }
    54. }

  • 相关阅读:
    Dynamsoft Barcode Reader 9.4.0
    JavaScript 三种作用域 + 匿名函数 + 立即执行函数 详解
    【前端】webpack打包去除console.log
    聚观365|抖音上线“防打扰保护工具”;亚马逊拟计划裁员1万人
    文件共享方法
    springboot证书管理系统的设计与实现毕业设计源码162317
    JS中BigInt的使用
    小米手机买什么蓝牙耳机好?适配小米手机的蓝牙耳机推荐
    docker安装mysql并挂载配置文件和修改密码
    mongodb一些常用的查询语句
  • 原文地址:https://blog.csdn.net/m0_61548909/article/details/126600355