• unordered_map和unordered_set模拟实现


    unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构。

    一、哈希

    1.1哈希概念

    构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

    1.2哈希冲突

    不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。

    1.3哈希函数

    引起哈希冲突的一个原因可能是:哈希函数设计不够合理。

    常见的哈希函数:

    1.直接定址法:

    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B

    优点:简单、均匀

    缺点:需要事先知道关键字的分布情况

    使用场景:适合查找比较小且连续的情况

    2.除留余数法:

    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。

    1.4哈希冲突解决

    解决哈希冲突两种常见的方法是:闭散列和开散列

    1.4.1闭散列

    闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。

    1.线性探测

    插入:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

    删除:删除时不可以物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。因为在查找时,遇到空就查找结束。

    线性探测的实现:

    1. template<class K>
    2. struct DefaultHashFunc
    3. {
    4. size_t operator()(const K& key)
    5. {
    6. return (size_t)key;
    7. }
    8. };
    9. template<>
    10. struct DefaultHashFunc
    11. {
    12. size_t operator()(const string& str)
    13. {
    14. size_t n = 0;
    15. for (auto ch : str)
    16. {
    17. n *= 131;
    18. n += ch;
    19. }
    20. return n;
    21. }
    22. };
    23. namespace open_address//开放地址法
    24. {
    25. //状态
    26. enum STATE
    27. {
    28. EMPTY, EXIST, DELETE//空,存在,删除
    29. };
    30. //数据类型
    31. template<class K, class V>
    32. struct HashData
    33. {
    34. pair _kv;
    35. STATE _state = EMPTY;
    36. };
    37. template<class K, class V, class HashFunc = DefaultHashFunc>
    38. class HashTable
    39. {
    40. public:
    41. HashTable()
    42. {
    43. _table.resize(10);
    44. }
    45. bool insert(const pair& kv)
    46. {
    47. //检查是否已经存在
    48. if (Find(kv.first))
    49. return false;
    50. //判断扩容
    51. HashFunc ht;
    52. int fac = (double)_n / _table.size();//负载因子
    53. if (fac >= 0.7)
    54. {
    55. //扩容之后可能会改变数据的映射关系,所以不能直接扩容
    56. size_t newcapacity = _table.capacity() * 2;
    57. HashTable newHash;
    58. newHash._table.resize(newcapacity);
    59. //遍历旧表,插入到新表
    60. for (int i = 0; i < _table.capacity(); i++)
    61. {
    62. //只有exist状态的才插入新表
    63. if (_table[i]._state == EXIST)
    64. newHash.insert(_table[i]._kv);
    65. }
    66. _table.swap(newHash._table);
    67. }
    68. //插入
    69. size_t hashi = ht(kv.first) % _table.size();
    70. while (_table[hashi]._state == EXIST)
    71. {
    72. hashi++;
    73. if (hashi == _table.size())//回到0位置
    74. hashi = 0;
    75. }
    76. _table[hashi]._kv = kv;
    77. _table[hashi]._state = EXIST;
    78. _n++;
    79. return true;
    80. }
    81. HashData<const K, V>* Find(const K& key)
    82. {
    83. HashFunc ht;
    84. size_t hashi = ht(key) % _table.size();
    85. while (_table[hashi]._state != EMPTY)
    86. {
    87. if (_table[hashi]._state != DELETE && _table[hashi]._kv.first == key)
    88. return (HashData<const K, V>*) & _table[hashi];
    89. hashi++;
    90. //转回去继续找
    91. if (hashi == _table.size())//回到0位置
    92. hashi = 0;
    93. }
    94. return nullptr;
    95. }
    96. bool Erase(const K& key)
    97. {
    98. HashData<const K, V>* data = Find(key);
    99. if (data)
    100. {
    101. data->_state = DELETE;
    102. _n--;
    103. return true;
    104. }
    105. return false;
    106. }
    107. private:
    108. vector> _table;
    109. size_t _n = 0;//存储数据的有效个数
    110. };
    111. }

    哈希表什么情况下扩容?如何扩容?

    2.二次探测

    线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为:

    若当前key与原来key产生相同的哈希地址,则当前key存在该地址后偏移量为(1,-1,2,-2,3,-3...)的二次方地址处
    key1:hash(key)+0

    key2:hash(key)+1^2

    key3:hash(key)+(-1)^2

    key4:hash(key)+2^2

    key5:hash(key)+(-2)^2

    1.4.2开散列

    开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

    开散列的实现:

    1. template<class K>
    2. struct DefaultHashFunc
    3. {
    4. size_t operator()(const K& key)
    5. {
    6. return (size_t)key;
    7. }
    8. };
    9. template<>
    10. struct DefaultHashFunc
    11. {
    12. size_t operator()(const string& str)
    13. {
    14. size_t n = 0;
    15. for (auto ch : str)
    16. {
    17. n *= 131;
    18. n += ch;
    19. }
    20. return n;
    21. }
    22. };
    23. namespace hash_bucket//拉链法——哈希桶
    24. {
    25. template<class K, class V>
    26. struct HashNode
    27. {
    28. HashNode(const pair& kv)
    29. :_kv(kv)
    30. ,_next(nullptr)
    31. {}
    32. pair _kv;
    33. HashNode* _next;
    34. };
    35. template<class K, class V, class HashFunc = DefaultHashFunc>
    36. class HashTable
    37. {
    38. typedef HashNode Node;
    39. public:
    40. HashTable()
    41. {
    42. _table.resize(10, nullptr);
    43. }
    44. ~HashTable()
    45. {
    46. for (int i = 0; i < _table.size(); i++)
    47. {
    48. Node* cur = _table[i];
    49. while (cur)
    50. {
    51. Node* next = cur->_next;
    52. delete cur;
    53. cur = next;
    54. }
    55. _table[i] = nullptr;
    56. }
    57. _n = 0;
    58. }
    59. bool insert(const pair& kv)
    60. {
    61. //检查是否已经存在
    62. if (Find(kv.first))
    63. return false;
    64. HashFunc ht;
    65. size_t fac = (double)_n / _table.size();//负载因子
    66. if (fac >= 1)//扩容
    67. {
    68. size_t newcapacity = _table.size() * 2;
    69. HashTable newht;
    70. newht._table.resize(newcapacity, nullptr);
    71. //遍历旧的,链入新的
    72. for (int i = 0; i < _table.size(); i++)
    73. {
    74. Node* cur = _table[i];
    75. while (cur)
    76. {
    77. Node* next = cur->_next;
    78. cur->_next = nullptr;//如果用屏蔽的方式,这里要置空
    79. size_t hashi = ht(cur->_kv.first) % newcapacity;
    80. //头插
    81. cur->_next = newht._table[hashi];
    82. newht._table[hashi] = cur;
    83. /*if (newht._table[hashi] == nullptr)
    84. {
    85. newht._table[hashi] = cur;
    86. }
    87. else
    88. {
    89. cur->_next = newht._table[hashi];
    90. newht._table[hashi] = cur;
    91. }*/
    92. cur = next;
    93. }
    94. _table[i] = nullptr;//这里必须要置空,否则不能释放
    95. }
    96. _table.swap(newht._table);
    97. }
    98. //头插
    99. Node* newnode = new Node(kv);
    100. size_t hashi = ht(kv.first) % _table.size();//找第几个桶
    101. newnode->_next = _table[hashi];
    102. _table[hashi] = newnode;
    103. /*if (_table[hashi] == nullptr)
    104. {
    105. _table[hashi] = newnode;
    106. }
    107. else
    108. {
    109. newnode->_next = _table[hashi];
    110. _table[hashi] = newnode;
    111. }*/
    112. _n++;
    113. return true;
    114. }
    115. Node* Find(const K& key)
    116. {
    117. HashFunc ht;
    118. size_t hashi = ht(key) % _table.size();
    119. Node* cur = _table[hashi];
    120. while (cur)
    121. {
    122. if (cur->_kv.first == key)
    123. {
    124. return cur;
    125. }
    126. cur = cur->_next;
    127. }
    128. return nullptr;
    129. }
    130. bool Erase(const K& key)
    131. {
    132. HashFunc ht;
    133. size_t hashi = ht(key) % _table.size();
    134. Node* prev = nullptr;
    135. Node* cur = _table[hashi];
    136. while (cur)
    137. {
    138. if (cur->_kv.first == key)
    139. {
    140. if (prev == nullptr)
    141. {
    142. _table[hashi] = cur->_next;
    143. }
    144. else
    145. {
    146. prev->_next = cur->_next;
    147. }
    148. delete cur;
    149. return true;
    150. }
    151. else
    152. {
    153. prev = cur;
    154. cur = cur->_next;
    155. }
    156. }
    157. return false;
    158. }
    159. void Print()
    160. {
    161. for (int i = 0; i < _table.size(); i++)
    162. {
    163. Node* cur = _table[i];
    164. printf("[%d] : ", i);
    165. while (cur)
    166. {
    167. cout << "(" << cur->_kv.first << ":" << cur->_kv.second << ")" << "->";
    168. cur = cur->_next;
    169. }
    170. cout << "NULL" << endl;
    171. }
    172. cout << endl;
    173. }
    174. private:
    175. vector _table;
    176. size_t _n = 0;//存放有效数据的个数
    177. };
    178. }

    注意:如果我们要存放的数据是整型,可以使用上面的哈希函数来映射,但是如果我们存的是字符串或自定义类型的数据时,问题就来了,我们如何用除留余数法来计算呢?字符串和自定义类型不可以取模。

    我们可以使用仿函数,如果是整型就返回整型,如果是字符串或自定义类型,就将其转换成整型再返回:

    1. template<class K>
    2. struct DefaultHashFunc
    3. {
    4. size_t operator()(const K& key)
    5. {
    6. return (size_t)key;
    7. }
    8. };
    9. template<>
    10. struct DefaultHashFunc
    11. {
    12. size_t operator()(const string& str)
    13. {
    14. size_t n = 0;
    15. for (auto ch : str)
    16. {
    17. n *= 131;
    18. n += ch;
    19. }
    20. return n;
    21. }
    22. };

    二、unordered_map和unordered_set的模拟实现

    底层哈希桶:

    1. template<class K>
    2. struct DefaultHashFunc
    3. {
    4. size_t operator()(const K& key)
    5. {
    6. return (size_t)key;
    7. }
    8. };
    9. template<>
    10. struct DefaultHashFunc
    11. {
    12. size_t operator()(const string& str)
    13. {
    14. size_t n = 0;
    15. for (auto ch : str)
    16. {
    17. n *= 131;
    18. n += ch;
    19. }
    20. return n;
    21. }
    22. };
    23. namespace hash_bucket//拉链法——哈希桶
    24. {
    25. template<class T>
    26. struct HashNode
    27. {
    28. HashNode(const T& data)
    29. :_data(data)
    30. ,_next(nullptr)
    31. {}
    32. T _data;
    33. HashNode* _next;
    34. };
    35. template<class K, class T, class KofT, class HashFunc>
    36. class HashTable;//声明一下
    37. template<class K, class T, class Ref, class Ptr, class KofT, class HashFunc = DefaultHashFunc>
    38. struct __HT_Iterator
    39. {
    40. typedef HashNode Node;
    41. typedef __HT_Iterator Self;
    42. typedef __HT_Iterator Iterator;
    43. __HT_Iterator(Node* node, const HashTable* pht)
    44. :_node(node)
    45. ,_pht(pht)//_pht为const修饰的指针
    46. {}
    47. //当此时这个类是普通迭代器类时,这个函数为拷贝构造
    48. //当此时这个类是const迭代器类时,这个函数为构造
    49. __HT_Iterator(const Iterator& it)
    50. :_node(it._node)
    51. ,_pht(it._pht)
    52. {}
    53. Ref operator*()
    54. {
    55. return _node->_data;
    56. }
    57. Ptr operator->()
    58. {
    59. return &_node->_data;
    60. }
    61. Self operator++()
    62. {
    63. //走这个桶里的下一个节点
    64. if (_node->_next)
    65. {
    66. _node = _node->_next;
    67. }
    68. //找其他桶
    69. else
    70. {
    71. KofT kot;
    72. HashFunc ht;
    73. size_t hashi = ht(kot(_node->_data)) % _pht->_table.size();
    74. ++hashi;//找下一个桶
    75. while (hashi < _pht->_table.size())
    76. {
    77. if (_pht->_table[hashi])
    78. {
    79. _node = _pht->_table[hashi];
    80. return *this;
    81. }
    82. else
    83. {
    84. ++hashi;
    85. }
    86. }
    87. _node = nullptr;
    88. }
    89. return *this;
    90. }
    91. bool operator!=(const Self& it) const
    92. {
    93. return _node != it._node;
    94. }
    95. bool operator==(const Self& it) const
    96. {
    97. return _node == it._node;
    98. }
    99. Node* _node;
    100. const HashTable* _pht;
    101. };
    102. template<class K, class T, class KofT, class HashFunc = DefaultHashFunc>
    103. class HashTable
    104. {
    105. //友元声明
    106. template<class K, class T, class Ref, class Ptr, class KofT, class HashFunc>
    107. friend struct __HT_Iterator;
    108. typedef HashNode Node;
    109. public:
    110. typedef __HT_Iterator iterator;
    111. typedef __HT_Iteratorconst T&, const T*, KofT, HashFunc> const_iterator;
    112. iterator begin()
    113. {
    114. for (int i = 0; i < _table.size(); i++)
    115. {
    116. Node* cur = _table[i];
    117. if (cur)
    118. return iterator(cur, this);
    119. }
    120. return iterator(nullptr, this);
    121. }
    122. iterator end()
    123. {
    124. return iterator(nullptr, this);
    125. }
    126. const_iterator begin() const
    127. {
    128. for (int i = 0; i < _table.size(); i++)
    129. {
    130. Node* cur = _table[i];
    131. if (cur)
    132. return const_iterator(cur, this);
    133. }
    134. return const_iterator(nullptr, this);
    135. }
    136. const_iterator end() const
    137. {
    138. return const_iterator(nullptr, this);//这里的this是const修饰的,所以上面的迭代器里的构造的哈希表参数要设置成const
    139. }
    140. HashTable()
    141. {
    142. _table.resize(10, nullptr);
    143. }
    144. ~HashTable()
    145. {
    146. for (int i = 0; i < _table.size(); i++)
    147. {
    148. Node* cur = _table[i];
    149. while (cur)
    150. {
    151. Node* next = cur->_next;
    152. delete cur;
    153. cur = next;
    154. }
    155. _table[i] = nullptr;
    156. }
    157. _n = 0;
    158. }
    159. pairbool> insert(const T& data)
    160. {
    161. KofT kot;
    162. //检查是否已经存在
    163. iterator it = Find(kot(data));
    164. if (it != end())//找到了就返回此刻的pair
    165. return make_pair(it ,false);
    166. HashFunc ht;
    167. size_t fac = (double)_n / _table.size();//负载因子
    168. if (fac >= 1)//扩容
    169. {
    170. size_t newcapacity = _table.size() * 2;
    171. HashTable newht;
    172. newht._table.resize(newcapacity, nullptr);
    173. //遍历旧的,链入新的
    174. for (int i = 0; i < _table.size(); i++)
    175. {
    176. Node* cur = _table[i];
    177. while (cur)
    178. {
    179. Node* next = cur->_next;
    180. size_t hashi = ht(kot(cur->_data)) % newcapacity;
    181. //头插
    182. cur->_next = newht._table[hashi];
    183. newht._table[hashi] = cur;
    184. cur = next;
    185. }
    186. _table[i] = nullptr;//这里必须要置空,否则不能释放
    187. }
    188. _table.swap(newht._table);
    189. }
    190. //头插
    191. Node* newnode = new Node(data);
    192. size_t hashi = ht(kot(data)) % _table.size();//找第几个桶
    193. newnode->_next = _table[hashi];
    194. _table[hashi] = newnode;
    195. _n++;
    196. return make_pair(iterator(newnode, this), true);
    197. }
    198. iterator Find(const K& key)
    199. {
    200. KofT kot;
    201. HashFunc ht;
    202. size_t hashi = ht(key) % _table.size();
    203. Node* cur = _table[hashi];
    204. while (cur)
    205. {
    206. if (kot(cur->_data) == key)
    207. {
    208. return iterator(cur, this);
    209. }
    210. cur = cur->_next;
    211. }
    212. return end();
    213. }
    214. bool Erase(const K& key)
    215. {
    216. KofT kot;
    217. HashFunc ht;
    218. size_t hashi = ht(key) % _table.size();
    219. Node* prev = nullptr;
    220. Node* cur = _table[hashi];
    221. while (cur)
    222. {
    223. if (kot(cur->_data) == key)
    224. {
    225. if (prev == nullptr)
    226. {
    227. _table[hashi] = cur->_next;
    228. }
    229. else
    230. {
    231. prev->_next = cur->_next;
    232. }
    233. delete cur;
    234. _n--;
    235. return true;
    236. }
    237. else
    238. {
    239. prev = cur;
    240. cur = cur->_next;
    241. }
    242. }
    243. return false;
    244. }
    245. void Print()
    246. {
    247. for (int i = 0; i < _table.size(); i++)
    248. {
    249. Node* cur = _table[i];
    250. printf("[%d] : ", i);
    251. while (cur)
    252. {
    253. cout << "(" << cur->_kv.first << ":" << cur->_kv.second << ")" << "->";
    254. cur = cur->_next;
    255. }
    256. cout << "NULL" << endl;
    257. }
    258. cout << endl;
    259. }
    260. size_t count(const K& key)
    261. {
    262. if (Find(key) != end())
    263. return 1;
    264. else
    265. return 0;
    266. }
    267. size_t size()
    268. {
    269. return _n;
    270. }
    271. bool empty()
    272. {
    273. return _n == 0;
    274. }
    275. private:
    276. vector _table;
    277. size_t _n = 0;//存放有效数据的个数
    278. };
    279. }

    封装unordered_map和unordered_set

    1. template<class K, class V>
    2. struct mapKofT
    3. {
    4. K operator()(const pair& kv)
    5. {
    6. return kv.first;
    7. }
    8. };
    9. template<class K, class V>
    10. class Unordered_map
    11. {
    12. public:
    13. typedef typename hash_bucket::HashTableconst K, V>, mapKofT>::iterator iterator;
    14. typedef typename hash_bucket::HashTableconst K, V>, mapKofT>::const_iterator const_iterator;
    15. pairbool> insert(const pair& kv)
    16. {
    17. return _ht.insert(kv);
    18. }
    19. V& operator[](const K& key)
    20. {
    21. return _ht.insert(make_pair(key, V())).first->second;
    22. }
    23. bool Erase(const K& key)
    24. {
    25. return _ht.Erase(key);
    26. }
    27. iterator find(const K& key)
    28. {
    29. return _ht.Find(key);
    30. }
    31. size_t count(const K& key)
    32. {
    33. return _ht.count(key);
    34. }
    35. size_t size()
    36. {
    37. return _ht.size();
    38. }
    39. bool empty()
    40. {
    41. return _ht.empty();
    42. }
    43. iterator begin()
    44. {
    45. return _ht.begin();
    46. }
    47. iterator end()
    48. {
    49. return _ht.end();
    50. }
    51. const_iterator begin() const
    52. {
    53. return _ht.begin();
    54. }
    55. const_iterator end() const
    56. {
    57. return _ht.end();
    58. }
    59. private:
    60. hash_bucket::HashTableconst K, V>, mapKofT> _ht;
    61. };
    1. template<class K>
    2. struct setKofT
    3. {
    4. K operator()(const K& key)
    5. {
    6. return key;
    7. }
    8. };
    9. template<class K>
    10. class Unordered_set
    11. {
    12. public:
    13. typedef typename hash_bucket::HashTable>::const_iterator iterator;
    14. typedef typename hash_bucket::HashTable>::const_iterator const_iterator;
    15. pairbool> insert(const K& key)
    16. {
    17. return _ht.insert(key);
    18. }
    19. bool Erase(const K& key)
    20. {
    21. return _ht.Erase(key);
    22. }
    23. size_t size()
    24. {
    25. return _ht.size();
    26. }
    27. bool empty()
    28. {
    29. return _ht.empty();
    30. }
    31. size_t count(const K& key)
    32. {
    33. return _ht.count(key);
    34. }
    35. iterator find(const K& key)
    36. {
    37. return _ht.Find(key);
    38. }
    39. iterator begin() const
    40. {
    41. return _ht.begin();
    42. }
    43. iterator end() const
    44. {
    45. return _ht.end();
    46. }
    47. private:
    48. hash_bucket::HashTable> _ht;
    49. };
  • 相关阅读:
    G. SlavicG‘s Favorite Problem(DFS序) 2022绵阳H(思维构造),M(并查集)
    NISP和CISP中渗透测试的思路是什么?
    怒刷LeetCode的第8天(Java版)
    axure 8 修改axhub-column-data的数据和配置
    QListView
    kafka + Springboot 实战测试操作完整文档记录
    解密第三方登录-微信扫码登录 Java生成二维码
    【C++】类和对象(中下)
    springboot+高校学生实习档案管理 毕业设计-附源码221508
    Spring Session
  • 原文地址:https://blog.csdn.net/weixin_50601177/article/details/133553447