当前位置:   article > 正文

unordered_map和unordered_set的源码模拟实现_unorderedmap实现源码

unorderedmap实现源码

在源码中,这两个STL容器都是共用一个框架的所以,我们就用一个框架实现两个

容器,代码细节都在注释上

HashTable.h

  1. #pragma once
  2. #define _CRT_SECURE_NO_WARNINGS 1
  3. #include <iostream>
  4. #include <vector>
  5. #include <string>
  6. #include <utility>
  7. #include <algorithm>
  8. #include <utility>
  9. //namespace txh
  10. //{
  11. // enum state
  12. // {
  13. // EMPTY,//空
  14. // EXITS,//存在
  15. // DELETE,//被删除
  16. // };
  17. // template<class K,class V>//元素类
  18. // class HashData//先画一个存放数据的蓝图,声明和定义
  19. // {
  20. // public:
  21. // std::pair<K,V> _kv;//哈希表,我们要根据key(也是模板的K)值去模一个值,得到哈希值存到哈希表的位置
  22. // //V是数据类型
  23. //
  24. // //我们在查找的时候不知道用什么表示为空,没有一个合适的数,0吗,-1,不对,因为这些都有可能是某个数0,-1
  25. // //所以不能用,我们用另一种方法,那就是在每个数据上存一个标志代表是否为空
  26. //
  27. // //前面定义了enum类型,类名是state,
  28. // state _state = EMPTY;//默认都是空
  29. // };
  30. //
  31. //
  32. //
  33. // //线性探测法
  34. // template<class K,class V,class keyConv>
  35. // class HashTable
  36. // {
  37. // public:
  38. // typedef HashData<K, V> data;
  39. // keyConv kc;
  40. // HashTable()
  41. // :_n(0)
  42. // {
  43. // _table.resize(11);
  44. // }
  45. //
  46. // //那如果key值不是int而是string 类型呢,我们不可能去特意在模板里判断一个数是否是string类型
  47. // //那么我们可以传一个模板,因为我们hash主要的是要把key值转换成hash值
  48. // //我们用一个模板类去封装转换成hash值的方式
  49. // bool insert(const std::pair<K,V>& kv)//插入元素,我们用pair类接受
  50. // {
  51. // //如果表内有相同的值的话,就不插入了
  52. // if (find(kv.first))
  53. // {
  54. // return false;
  55. // }
  56. //
  57. // if (_n * 10 / _table.size() > 7)//负载因子大于0.7我们的冲突就会变大,导致效率下降
  58. // //在线性探测中,所以我们就需要扩容,扩多少倍呢,扩质数倍其实是很好的,java中只是扩2倍,没有按质数扩
  59. // //按质数扩会降低哈希冲突率
  60. // {
  61. // HashTable<K, V,keyConv> newtable;
  62. // newtable._table.resize(2 * _table.size());
  63. //
  64. // for (auto& e:_table)//为什么要重新插入,是因为我们扩容之后,下标映射变了,所以要重新插入
  65. // {
  66. // if(e._state == EXITS)
  67. // {
  68. // /* int hashi = e._kv.first % newtable._table.size();
  69. // newtable._table[hashi]._kv = e._kv;
  70. // newtable._table[hashi]._state = e._state;*/
  71. // newtable.insert(e._kv);//复用写法
  72. // }
  73. // }
  74. // _table.swap(newtable._table);//2023年的现代写法,新表代替了旧表
  75. // }
  76. //
  77. //
  78. // //线性探测
  79. // size_t hashi = kc(kv.first) % _table.size(); //成员里不用capacity的原因是我们的vector的下标引用只能引用size内的值,capacity
  80. // //肯定大于等于size,所以肯定不能去模除capacity
  81. // size_t i = 0;
  82. // while (_table[hashi]._state != EMPTY)//查看当前表的每个元素类是否有空,没空一直继续
  83. // {
  84. // //如果当前的hashi被占了,直接去下一个厕所看(就是相邻的下一个),所以有了i
  85. // //i控制每一轮要看的厕所不是上一次看过的,所以i++
  86. // ++hashi;
  87. // hashi %= _table.size();//防止越界
  88. // }
  89. // _table[hashi]._kv = kv;
  90. // _table[hashi]._state = EXITS;
  91. // ++_n;
  92. // return true;
  93. // }
  94. //
  95. // data* find(const K& key)
  96. // {
  97. // size_t hashi = kc(key) % _table.size();
  98. // size_t i = 0;
  99. // while (_table[hashi]._state != EMPTY)//这是线性探测
  100. // {
  101. // if (_table[hashi]._kv.first == key
  102. // && _table[hashi]._state == EXITS)
  103. // {
  104. // return &_table[hashi];
  105. // }
  106. // hashi++;
  107. // hashi %= _table.size();//防止越界
  108. // }
  109. //
  110. // return nullptr;
  111. // }
  112. //
  113. // bool erase(const K& key)
  114. // {
  115. // data* ret = find(key);
  116. // if (!ret)
  117. // {
  118. // return false;
  119. // }
  120. // else
  121. // {
  122. // ret->_state = DELETE;
  123. // _n--;
  124. // return true;
  125. // }
  126. // }
  127. // private:
  128. // std::vector<data> _table;//当前存放元素的表,
  129. // size_t _n;//代表当前表内有多少个元素
  130. // //负载因子是什么呢,就是总共插入的元素 除以 总元素 就是负载因子
  131. // };
  132. //}
  133. //
  134. template <typename T>
  135. struct HashFunc
  136. {
  137. size_t operator()(const T& key)//键值接受
  138. {
  139. size_t hashi = key;
  140. return hashi;
  141. }
  142. };//声明键值转换函数是模板类,因为模板类只是个图纸
  143. template <>//特化之前一定要有一个基础类
  144. struct HashFunc<std::string>
  145. {
  146. size_t operator()(const std::string& key)
  147. {
  148. size_t hashi = 0;
  149. for (auto e : key)//关键值转hash
  150. {
  151. hashi *= 131;
  152. hashi += e;
  153. }
  154. return hashi;
  155. }
  156. };
  157. namespace HashBucket
  158. {
  159. //开散列,拉链法
  160. template<class T>//改造后只有一个参数,我们自动去识别是一个参数还是两个参数
  161. struct HashNode
  162. {
  163. HashNode(const T& data) :_data(data), next(nullptr) {}
  164. T _data;
  165. HashNode* next;
  166. };
  167. template<class K, class T, class keyCon, class KeyofT>
  168. class HashTable;//在hashIterator类前面声明,编译器就会跟着这个声明去找到相应的模板
  169. //容器都有迭代器,所以我们要封装一个迭代器,因为我们的迭代器会有重载*,->这种,所以我们直接传ptr这种吧
  170. template<class K,class T,class Ref,class Ptr,class keyCon,class KeyofT>
  171. class _HashIterator
  172. {
  173. public:
  174. typedef HashNode<T> node;
  175. typedef _HashIterator<K, T, T&, T*, keyCon, KeyofT> iterator;
  176. typedef HashTable<K, T, keyCon, KeyofT> HT;
  177. _HashIterator(node* node1,HT* ht) :_node(node1),_ht(ht){}
  178. _HashIterator(const iterator& it):_node(it._node),_ht(it._ht){}
  179. typedef _HashIterator<K,T,Ref,Ptr,keyCon,KeyofT> self;
  180. self& operator++()
  181. {
  182. //先看当前桶里是否有元素,如果后续有元素,那就在桶里++,如果桶里没有,那么就去其他桶
  183. //里找
  184. node* cur = _node->next;
  185. if (cur)
  186. {
  187. _node = _node->next;
  188. //返回迭代器本身
  189. }
  190. else
  191. {
  192. //去遍历这个桶,找到后续第一个有元素的桶
  193. keyCon kc;
  194. //找到当前迭代器里的节点的桶的位置
  195. size_t hashi = kc(_node->_data) % _ht->_table.size();
  196. //遍历这个桶
  197. ++hashi;
  198. while (hashi < _ht->_table.size())
  199. {
  200. if (_ht->_table[hashi])
  201. {
  202. _node = _ht->_table[hashi];
  203. break;
  204. }
  205. hashi++;
  206. }
  207. if (hashi == _ht->_table.size())
  208. {
  209. _node = nullptr;
  210. }
  211. }
  212. return *this;
  213. }
  214. self operator++(int)
  215. {
  216. //后置++
  217. self ret = self(_node,_ht);
  218. node* cur = _node;
  219. if (cur)
  220. {
  221. _node = _node->next;
  222. //返回迭代器本身
  223. }
  224. else
  225. {
  226. //去遍历这个桶,找到后续第一个有元素的桶
  227. keyCon kc;
  228. //找到当前迭代器里的节点的桶的位置
  229. size_t hashi = kc(_node->_data) % _ht->_table.size();
  230. //遍历这个桶
  231. ++hashi;
  232. while (hashi < _ht->_table.size())
  233. {
  234. if (_ht->_table[hashi])
  235. {
  236. _node = _ht->_table[hashi];
  237. break;
  238. }
  239. hashi++;
  240. }
  241. if (hashi == _ht->_table.size())
  242. {
  243. _node = nullptr;
  244. }
  245. }
  246. return ret;//返回迭代器对象的时候,会进行拷贝构造,所以不用担心生命周期
  247. }
  248. bool operator!=(const self& s)
  249. {
  250. return s._node != _node;
  251. }
  252. Ref operator*()
  253. {
  254. return _node->_data;
  255. }
  256. Ptr& operator->()
  257. {
  258. return &_node->_data;
  259. }
  260. public:
  261. node* _node;//有了迭代器,我们就可以直接用迭代器,迭代器++,就是这里的_node = _node->next等
  262. //我们的++,--操作还会设计到遍历桶,我们的遍历桶只在HashTable里面设计了,所以我们需要有一个
  263. //可以操作HashTable的动作,因为都是算类,我们可以设计一个指针进行操作,我们用友元
  264. HT* _ht;
  265. };
  266. //第二个才是数,第一个只是我们的键值
  267. template<class K, class T, class keyCon,class KeyofT>
  268. class HashTable
  269. {
  270. public:
  271. //友元声明
  272. template<class K,class T,class Ref,class Ptr,class KeyCon, class KeyofT>//声明这个_hashIterator是个模板类,告诉编译器我要这个_HashIterator模板类
  273. //是HashTable类的友元
  274. friend class _HashIterator;
  275. typedef _HashIterator<K,T,T&,T*,keyCon,KeyofT> iterator;
  276. typedef _HashIterator<K, T,const T&,const T*,keyCon,KeyofT> const_iterator;
  277. //我们通过传的模板参数来控制我们的迭代器是否是const迭代器
  278. typedef HashNode<T> node;
  279. keyCon hash;
  280. KeyofT koft;
  281. size_t _stl_next_prime(size_t size)
  282. {
  283. static const int __stl_num_primes = 28;
  284. static const unsigned long __stl_prime_list[__stl_num_primes] =
  285. {
  286. 53, 97, 193, 389, 769,
  287. 1543, 3079, 6151, 12289, 24593,
  288. 49157, 98317, 196613, 393241, 786433,
  289. 1572869, 3145739, 6291469, 12582917, 25165843,
  290. 50331653, 100663319, 201326611, 402653189, 805306457,
  291. 1610612741, 3221225473, 4294967291
  292. };
  293. //这里存放素数,为什么设素数为长度呢,因为在模素数的
  294. //时候,造成的哈希冲突会竟可能的好
  295. for (int i = 0;i < __stl_num_primes;i++)
  296. {
  297. if (__stl_prime_list[i] > size)
  298. {
  299. return __stl_prime_list[i];
  300. }
  301. }
  302. }
  303. HashTable() :_n(0)
  304. {
  305. _table.resize(_stl_next_prime(_table.size()));
  306. }
  307. ~HashTable()
  308. {
  309. for (auto e : _table)
  310. {
  311. node* cur = e;
  312. while (cur)//把在桶里的元素删除
  313. {
  314. node* next = cur->next;
  315. delete cur;
  316. cur = next;
  317. }
  318. e = nullptr;
  319. }
  320. }
  321. iterator begin()
  322. {
  323. node* cur = nullptr;
  324. //找到第一个有元素的桶
  325. for (size_t hashi = 0;hashi < _table.size();hashi++)
  326. {
  327. cur = _table[hashi];
  328. if (cur)
  329. {
  330. break;
  331. }
  332. }
  333. return iterator(cur,this);//传值返回,生命周期就不用管,他会执行一次拷贝构造
  334. //匿名构造函数
  335. }
  336. iterator end()
  337. {
  338. return iterator(nullptr, this);//传值返回,生命周期就不用管,他会执行一次拷贝构造
  339. }
  340. const_iterator begin() const
  341. {
  342. node* cur = nullptr;
  343. for (size_t i = 0; i < _table.size(); ++i)
  344. {
  345. cur = _table[i];
  346. if (cur)
  347. {
  348. break;
  349. }
  350. }
  351. return const_iterator(cur, this);
  352. }
  353. const_iterator end() const{return const_iterator(nullptr, this);}
  354. std::pair<iterator,bool> insert(const T& data)
  355. {
  356. iterator ret = find(koft(data));
  357. if (ret != end())//有该点的话
  358. {
  359. return std::make_pair(ret, true);
  360. }
  361. //拉链法中,当负载因子等于1的时候,哈希的冲突就会变大,桶里的元素可能最多,所以负载因子为1就扩容最好
  362. if (_n == _table.size())//负载因子等于1
  363. {
  364. /*HashTable<K, V, keyCon> newHash;*/
  365. /*newHash._table.resize(_table.size() * 2);*/
  366. //这种做法很费效率,因为每次插入要重新new一个节点,并释放,所以同样映射的节点就不需要重新new了
  367. std::vector<node*> newtable;
  368. /*newtable.resize(_table.size() * 2,nullptr);*/
  369. //扩容
  370. newtable.resize(_stl_next_prime(_table.size()));
  371. //重新映射
  372. for (auto& e : _table)
  373. {
  374. node* cur = e;
  375. //头插
  376. while (cur)
  377. {
  378. node* next = cur->next;
  379. //哈希重新映射
  380. size_t hashi = hash(koft(cur->_data)) % newtable.size();
  381. cur->next = newtable[hashi];//将未插入之前的头插到cur的next
  382. newtable[hashi] = cur;
  383. cur = next;
  384. }
  385. e = nullptr;
  386. }
  387. //现代写法
  388. _table.swap(newtable);//旧表换新表,只是表新表的内容搬到旧表中
  389. //新表不用手动释放,生命周期只在当前作用域中,如果出了作用域会自动销毁(调用析构函数)
  390. }
  391. //这里的kc是实例化后的对象,所以可以kc(kv.first),但是不能用hash(kv.first),因为hash不是一个实例化的对象,需要把他实例化
  392. //之后才能调用
  393. size_t hashi = hash(koft(data)) % _table.size();
  394. //头插
  395. node* newnode = new node(data);
  396. newnode->next = _table[hashi];
  397. _table[hashi] = newnode;
  398. ++_n;
  399. return std::make_pair(iterator(newnode,this),true);
  400. }
  401. iterator find(const K& key)
  402. {
  403. size_t hashi = hash(key) % _table.size();//根据关键值算出哈希值
  404. node* cur = _table[hashi];
  405. //在桶里寻找
  406. while (cur)
  407. {
  408. if (koft(cur->_data) == key)
  409. {
  410. return iterator(cur,this);
  411. }
  412. cur = cur->next;
  413. }
  414. return iterator(nullptr,this);
  415. }
  416. bool erase(const K& key)
  417. {
  418. size_t hashi = hash(key) % _table.size();
  419. node* cur = _table[hashi];//找到了桶
  420. node* prev = nullptr;
  421. while (cur)
  422. {
  423. if (cur->_kv.first == key)
  424. {
  425. //如果是头节点的话
  426. if (cur->_kv == _table[hashi])
  427. {
  428. _table[hashi] = cur->next;
  429. }
  430. else//找到的这个key值不是头结点的话
  431. {
  432. prev->next = cur->next;
  433. }
  434. delete cur;
  435. --_n;
  436. return true;
  437. }
  438. else//继续找
  439. {
  440. prev = cur;
  441. cur = cur->next;
  442. }
  443. }
  444. return false;
  445. }
  446. private:
  447. std::vector<node*> _table;//每个位置存放hashnode类,里面有指针,存放的是头结点
  448. size_t _n;
  449. };
  450. }
  451. //先改造哈希表
  452. //int main()
  453. //{
  454. //
  455. // /*txh::HashTable<int,int,> h1;*/
  456. //
  457. // //h1.insert(std::make_pair(4,1));
  458. // //h1.insert(std::make_pair(4,1));
  459. // //h1.insert(std::make_pair(4,1));
  460. // //h1.insert(std::make_pair(3,1));
  461. //
  462. // //h1.insert(std::make_pair(7,1));
  463. //
  464. // /*txh::HashTable<std::string, int,HashFunc<std::string>> h1;
  465. // h1.insert(std::make_pair("苹果", 1));
  466. // h1.insert(std::make_pair("香蕉", 1));
  467. // h1.insert(std::make_pair("梨", 1));
  468. // h1.insert(std::make_pair("苹果", 1));
  469. // txh::HashData<std::string, int>* ret = h1.find("苹果");
  470. // ret->_kv.second++;
  471. // h1.erase("苹果");*/
  472. //
  473. // HashBucket::HashTable<int, int, HashFunc<int>> h1;
  474. //
  475. // h1.insert(std::make_pair(3, 1));
  476. // h1.insert(std::make_pair(4, 1));
  477. // h1.insert(std::make_pair(3, 1));
  478. // h1.insert(std::make_pair(3, 1));
  479. // h1.insert(std::make_pair(12, 1));
  480. // h1.insert(std::make_pair(12, 1));
  481. // return 0;
  482. //}
  483. template<class T>
  484. struct compare
  485. {
  486. bool operator()(const T& t1,const T& t2)
  487. {
  488. return t1 > t2;
  489. }
  490. };

unordered_map.h

  1. #pragma once
  2. #include "HashTable.h"
  3. #include "unordered_map.h"
  4. #include "unordered_set.h"
  5. #include <iostream>
  6. namespace txh
  7. {
  8. template<class K,class T,class keyCon = HashFunc<K>>
  9. class unordered_map
  10. {
  11. public:
  12. struct mapkeyoft
  13. {
  14. const K& operator()(const std::pair<K,T>& kv)
  15. {
  16. return kv.first;
  17. }
  18. };
  19. typedef typename HashBucket::HashTable<K, std::pair<K, T>, keyCon, mapkeyoft>::iterator iterator;
  20. typedef typename HashBucket::HashTable<K, std::pair<K, T>, keyCon, mapkeyoft>::const_iterator const_iterator;
  21. iterator begin()
  22. {
  23. return _ht.begin();
  24. }
  25. iterator end()
  26. {
  27. return _ht.end();
  28. }
  29. const_iterator begin()const
  30. {
  31. return _ht.begin();
  32. }
  33. const_iterator end()const
  34. {
  35. return _ht.end();
  36. }
  37. std::pair<iterator, bool> insert(const std::pair<K, T>& data)
  38. {
  39. return _ht.insert(data);
  40. }
  41. T& operator[](const K& key)
  42. {
  43. std::pair<iterator, bool> ret = insert(std::make_pair(key
  44. , T));
  45. return ret.first->second;
  46. //加->就是得到了data,然后second
  47. }
  48. iterator find(const K& key)
  49. {
  50. return _ht.find(key);
  51. }
  52. bool erase(const K& key)
  53. {
  54. _ht.erase(key);
  55. }
  56. private:
  57. HashBucket::HashTable<K, std::pair<K, T>, keyCon, mapkeyoft> _ht;
  58. };
  59. }

unordered_set.h

  1. #pragma once
  2. #include "HashTable.h"
  3. #include <iostream>
  4. #include <utility>
  5. namespace txh
  6. {
  7. template<class K,class keyCon = HashFunc<K>>
  8. class unordered_set
  9. {
  10. struct keyofset
  11. {
  12. const K& operator()(const K& data)
  13. {
  14. return data;
  15. }
  16. };
  17. public:
  18. typedef typename HashBucket::HashTable<K, K, keyCon, keyofset>::const_iterator iterator;
  19. typedef typename HashBucket::HashTable<K, K, keyCon, keyofset>::const_iterator const_iterator;
  20. iterator begin()
  21. {
  22. return _ht.begin();
  23. }
  24. iterator end()
  25. {
  26. return _ht.end();
  27. }
  28. const_iterator begin()const
  29. {
  30. return _ht.begin();
  31. }
  32. const_iterator end()const
  33. {
  34. return _ht.end();
  35. }
  36. std::pair<iterator, bool> insert(const K& data)
  37. {
  38. return _ht.insert(data);
  39. }
  40. iterator find(const K& data)
  41. {
  42. return _ht.find(data);
  43. }
  44. bool erase(const K& data)
  45. {
  46. return _ht.erase(data);
  47. }
  48. private:
  49. HashBucket::HashTable<K, K, keyCon, keyofset> _ht;
  50. };
  51. }

test.cpp

 

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include "unordered_map.h"
  3. #include "unordered_set.h"
  4. #include "HashTable.h"
  5. #include <iostream>
  6. #include <algorithm>
  7. #include <utility>
  8. int main()
  9. {
  10. txh::unordered_set<int, HashFunc<int>> s1;
  11. s1.insert(1);
  12. return 0;
  13. }

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/941253
推荐阅读
相关标签
  

闽ICP备14008679号