当前位置:   article > 正文

C++数据结构:哈希桶 -- 通过开散列的方法解决哈希冲突_哈希冲突的解决方法,哈希桶是什么

哈希冲突的解决方法,哈希桶是什么

目录

一. 什么是哈希桶

二. 哈希桶的实现

2.1 哈希表节点数据

2.2 特定Key值的查找find

2.3 哈希桶的扩容

2.4 数据插入操作insert

2.5 数据删除操作erase

2.6 哈希桶的析构函数 

附录:哈希桶的实现完整版代码


一. 什么是哈希桶

之前的博客中我提到过,可以采用闭散列(二次探测、线性探测)的方法来解决哈希冲突,但是,无论是线性探测还是二次探测,其解决哈希冲突的本质方法都是去“占位”,即:发生哈希冲突的Key会去占用其他Key值原本应当占用的位置,这样一个冲突可能引发一串冲突。

为了解决闭散列方法的上述弊端,可以使用哈希桶(开散列)的方法来解决哈希冲突。在hashTable的每个位置都挂一个哈希桶,哈希桶用单链表来表示。将所有发生冲突的数据都挂在一个桶上。

此时,hashTable存储的数据类型时单链表节点,hashTable[i]是每个哈希桶的头结点,发生冲突时的数据插入相当于单链表的头插操作。

图1.1 哈希桶结构图

二. 哈希桶的实现

2.1 哈希表节点数据

由于哈希桶的本质是挂在哈希表上的单链表,因此,哈希表节点的数据应当为单链表节点,包括:

  • 一个键值对pair<K, V> _kv,记录key值和与之配对的value值。
  • 一个节点指针_next,指向单链表的下一个节点。

在Hash类模板中,将hashDate<K, V>类型重定义为Node。

代码2.1:(哈希表节点)

  1. template<class K, class V>
  2. struct hashDate
  3. {
  4. std::pair<K, V> _kv;
  5. hashDate<K, V>* _next;
  6. hashDate(const std::pair<K, V>& kv)
  7. : _kv(kv)
  8. , _next(nullptr)
  9. { }
  10. };

2.2 特定Key值的查找find

函数Node* find(const K& key),找到了返回key所在的节点地址,找不到返回nullptr。

  1. 通过哈希函数,获取key所在的哈希桶应当挂在_hashTable哪个下标位置处(记为hashi)。
  2. 遍历以_hashTable[hashi]为头结点的单链表,查找key,找到了返回链表节点。
  3. 如果遍历到nullptr还没找到,则表示key不存在于哈希桶中,函数返回false。

代码2.2:(find函数的实现)

  1. Node* find(const K& key)
  2. {
  3. //哈希表中没有数据就直接返回false
  4. //这里是为了避免模0(%0)错误
  5. if (_hashTable.size() == 0)
  6. {
  7. return nullptr;
  8. }
  9. HashKey hashKey;
  10. //通过哈希函数计算key所在哈希桶
  11. size_t hashi = hashKey(key) % _hashTable.size();
  12. //哈希桶单链表头结点
  13. Node* cur = _hashTable[hashi];
  14. //变量哈希桶(单链表),寻找key
  15. while (cur)
  16. {
  17. if (hashKey(cur->_kv.first) == hashKey(key))
  18. {
  19. return cur;
  20. }
  21. cur = cur->_next;
  22. }
  23. return nullptr;
  24. }

2.3 哈希桶的扩容

如果通过闭散列(线性探测或二次探测)的方法来解决哈希冲突,那么哈希表中存储的数据个数_size一定不能超过_table.size()。但是如果使用哈希桶,哈希表的每个位置挂的哈希桶可以有多个数据,因此负载因子是可以大于1的。

闭散列一般当负载因子大于0.7~0.8是扩容,而哈希桶则应适度提高。可以认为当插入数据后负载因子大于1就扩容。

哈希桶扩容同样需要改变原来的结构,因为不同的Key值对于的存储位置会发生改变。这里不再复用insert函数来实现数据位置的改变,而是依次遍历每个哈希桶的每个节点,创建一个新的vector作为hashTable,通过单链表头插,来使数据满足扩容后的哈希结构要求。

图2.3 扩容前后哈希桶结构的变化

代码2.3:(哈希表扩容) 

  1. //扩容(负载因子大于1)
  2. if (_hashTable.size() == 0 || _size == _hashTable.size())
  3. {
  4. size_t newSize = _hashTable.size() == 0 ? 10 : 2 * _hashTable.size();
  5. //将原来哈希表数据的位置进行相应改变
  6. //这里使用一个新的vector来实现
  7. std::vector<Node*> newTable;
  8. newTable.resize(newSize, nullptr);
  9. //将原来table每个位置处挂的单链表,挪到新的table对应位置
  10. for (size_t i = 0; i < _hashTable.size(); ++i)
  11. {
  12. Node* cur = _hashTable[i];
  13. while (cur)
  14. {
  15. Node* next = cur->_next;
  16. size_t newHashi = hashKey(cur->_kv.first) % newSize;
  17. //执行头插操作
  18. cur->_next = newTable[newHashi];
  19. newTable[newHashi] = cur;
  20. cur = next;
  21. }
  22. _hashTable[i] = nullptr;
  23. }
  24. _hashTable.swap(newTable);
  25. }

2.4 数据插入操作insert

哈希桶是挂在hashTable指定位置处的单链表。进行数据插入时,只需在检查数据是否已经存在、是否需要扩容之后,要通过哈希函数Hash(Key)找到对应的位置,然后执行单链表头插操作即可。

图2.4 数据插入操作

2.5 数据删除操作erase

通过哈希函数获得哈希桶的位置后,遍历单链表(哈希桶)找到key所在的位置,执行单链表节点删除操作即可。

代码2.5:(数据删除)

  1. bool erase(const K& key)
  2. {
  3. if (_hashTable.size() == 0)
  4. {
  5. return false;
  6. }
  7. HashKey hashKey;
  8. size_t hashi = hashKey(key) % _hashTable.size();
  9. Node* prev = nullptr;
  10. Node* cur = _hashTable[hashi];
  11. while (cur)
  12. {
  13. //找到key了,删除
  14. if (hashKey(cur->_kv.first) == key)
  15. {
  16. //头删
  17. if (prev == nullptr)
  18. {
  19. _hashTable[hashi] = cur->_next;
  20. free(cur);
  21. cur = nullptr;
  22. }
  23. else //中间删或尾删
  24. {
  25. prev->_next = cur->_next;
  26. free(cur);
  27. cur = nullptr;
  28. }
  29. --_size;
  30. return true;
  31. }
  32. prev = cur;
  33. cur = cur->_next;
  34. }
  35. return false;
  36. }

2.6 哈希桶的析构函数 

自己编写代码,依次对每个哈希桶进行释放,每个哈希桶的析构等同于单链表的析构。由于vector为自定义类型数据,编译的会主动调用vector的析构函数释放hashTable的空间。

代码2.6:(析构函数)

  1. ~Hash() //析构函数
  2. {
  3. //释放每个节点上挂的数据
  4. for (size_t i = 0; i < _hashTable.size(); ++i)
  5. {
  6. Node* cur = _hashTable[i];
  7. //释放每个非空节点
  8. while (cur)
  9. {
  10. Node* next = cur->_next;
  11. delete cur;
  12. cur = next;
  13. }
  14. _hashTable[i] = nullptr;
  15. }
  16. }

附录:哈希桶的实现完整版代码

  1. #include<vector>
  2. template<class K, class V>
  3. struct hashDate
  4. {
  5. std::pair<K, V> _kv;
  6. hashDate<K, V>* _next;
  7. hashDate(const std::pair<K, V>& kv)
  8. : _kv(kv)
  9. , _next(nullptr)
  10. { }
  11. };
  12. template<class K>
  13. struct HashKeyVal
  14. {
  15. size_t operator()(const K& key)
  16. {
  17. return (size_t)key;
  18. }
  19. };
  20. template<class K, class V, class HashKey = HashKeyVal<K>>
  21. class Hash
  22. {
  23. typedef hashDate<K, V> Node;
  24. public:
  25. ~Hash() //析构函数
  26. {
  27. //释放每个节点上挂的数据
  28. for (size_t i = 0; i < _hashTable.size(); ++i)
  29. {
  30. Node* cur = _hashTable[i];
  31. //释放每个非空节点
  32. while (cur)
  33. {
  34. Node* next = cur->_next;
  35. delete cur;
  36. cur = next;
  37. }
  38. _hashTable[i] = nullptr;
  39. }
  40. }
  41. bool insert(const std::pair<K, V>& kv)
  42. {
  43. //查找 -- 确保不插入重复数据
  44. if (find(kv.first))
  45. {
  46. return false;
  47. }
  48. HashKey hashKey;
  49. //扩容(负载因子大于1)
  50. if (_hashTable.size() == 0 || _size == _hashTable.size())
  51. {
  52. size_t newSize = _hashTable.size() == 0 ? 10 : 2 * _hashTable.size();
  53. //将原来哈希表数据的位置进行相应改变
  54. //这里使用一个新的vector来实现
  55. std::vector<Node*> newTable;
  56. newTable.resize(newSize, nullptr);
  57. //将原来table每个位置处挂的单链表,挪到新的table对应位置
  58. for (size_t i = 0; i < _hashTable.size(); ++i)
  59. {
  60. Node* cur = _hashTable[i];
  61. while (cur)
  62. {
  63. Node* next = cur->_next;
  64. size_t newHashi = hashKey(cur->_kv.first) % newSize;
  65. //执行头插操作
  66. cur->_next = newTable[newHashi];
  67. newTable[newHashi] = cur;
  68. cur = next;
  69. }
  70. _hashTable[i] = nullptr;
  71. }
  72. _hashTable.swap(newTable);
  73. }
  74. //获取插入位置
  75. size_t hashi = hashKey(kv.first) % _hashTable.size();
  76. Node* node = new Node(kv);
  77. //插入数据
  78. node->_next = _hashTable[hashi];
  79. _hashTable[hashi] = node;
  80. ++_size;
  81. }
  82. bool erase(const K& key)
  83. {
  84. if (_hashTable.size() == 0)
  85. {
  86. return false;
  87. }
  88. HashKey hashKey;
  89. size_t hashi = hashKey(key) % _hashTable.size();
  90. Node* prev = nullptr;
  91. Node* cur = _hashTable[hashi];
  92. while (cur)
  93. {
  94. //找到key了,删除
  95. if (hashKey(cur->_kv.first) == key)
  96. {
  97. //头删
  98. if (prev == nullptr)
  99. {
  100. _hashTable[hashi] = cur->_next;
  101. free(cur);
  102. cur = nullptr;
  103. }
  104. else //中间删或尾删
  105. {
  106. prev->_next = cur->_next;
  107. free(cur);
  108. cur = nullptr;
  109. }
  110. --_size;
  111. return true;
  112. }
  113. prev = cur;
  114. cur = cur->_next;
  115. }
  116. return false;
  117. }
  118. Node* find(const K& key)
  119. {
  120. //哈希表中没有数据就直接返回false
  121. //这里是为了避免模0(%0)错误
  122. if (_hashTable.size() == 0)
  123. {
  124. return nullptr;
  125. }
  126. HashKey hashKey;
  127. //通过哈希函数计算key所在哈希桶
  128. size_t hashi = hashKey(key) % _hashTable.size();
  129. //哈希桶单链表头结点
  130. Node* cur = _hashTable[hashi];
  131. //变量哈希桶(单链表),寻找key
  132. while (cur)
  133. {
  134. if (hashKey(cur->_kv.first) == hashKey(key))
  135. {
  136. return cur;
  137. }
  138. cur = cur->_next;
  139. }
  140. return nullptr;
  141. }
  142. private:
  143. size_t _size = 0; //哈希表中数据个数
  144. std::vector<Node*> _hashTable; //哈希表(存储桶节点)
  145. };
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/578904
推荐阅读
相关标签
  

闽ICP备14008679号