当前位置:   article > 正文

【数据结构】哈希桶

哈希桶

目录

前言:

开散列(哈希桶)

开散列的概念

哈希桶的模拟实现

 整体框架

查找

插入

删除

析构函数

拷贝构造函数

赋值运算符重载

哈希桶模拟实现源码


前言:

闭散列线性探测缺点:一旦发生哈希冲突,所有的产生哈希冲突的数据连续存储在一块区域,容易产生数据"堆积",即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低,并且闭散列导致空间利用率低,因此本文探索采用开散列(哈希桶)的数据结构从而避免数据 "堆积" ;

开散列(哈希桶)

开散列的概念

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

接起来,各链表的头结点存储在哈希表中;

哈希桶的模拟实现

 整体框架

  1. template<class K, class V>
  2. struct HashNode
  3. {
  4. HashNode<K, V>* _next;
  5. pair<K, V> _kv;
  6. //开辟结点时需要结点的构造函数
  7. HashNode(const pair<K, V>& kv)
  8. {
  9. _kv = kv;
  10. _next = nullptr;
  11. }
  12. };
  13. template<class K, class V>
  14. class HashTable
  15. {
  16. typedef HashNode<K, V> Node;
  17. public:
  18. //...
  19. private:
  20. vector<Node*> _tables;
  21. size_t _n;//记录哈希表中实际存放的数据个数
  22. };

查找

思路:

首先根据键值key使用哈希函数计算哈希地址,确定待查找的数据的位置;

其次遍历桶中数据,查找到返回数据所在结点的指针,查找不到返回空指针;

  1. Node* Find(const K& key)
  2. {
  3. size_t hashi = key % _tables.size();
  4. Node* cur = _tables[hashi];
  5. while (cur != nullptr)
  6. {
  7. if ((cur->_kv).first == key)
  8. {
  9. return cur;
  10. }
  11. cur = cur->_next;
  12. }
  13. return nullptr;
  14. }

插入

开散列最优情形:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,考虑哈希表扩容

  • 查看哈希表中是否存在该键值的键值对,若已存在则插入失败;
  • 当负载因子增加到1时,进行扩容操作(即创建一个新的哈希表,该哈希表的大小为原哈希表的两倍,然后遍历原哈希表,将原哈希表中的结点插入到新哈希表,最后将原哈希表与新哈希表交换即可);
  • 将结点插入哈希桶(头插);
  • 哈希表中的记录实际存储的数据个数自增1;
  1. //构造函数
  2. HashTable(size_t n = 10)
  3. {
  4. _tables.resize(n, nullptr);
  5. _n = 0;
  6. }
  1. bool Insert(const pair<K, V>& kv)
  2. {
  3. //若键值对中的键值key已存在,则插入失败
  4. Node* ret = Find(kv.first);
  5. if (ret != nullptr)
  6. {
  7. return false;
  8. }
  9. //控制负载因子为1,即哈希表中实际存放的数据个数与哈希表长度长度相等
  10. if (_n == _tables.size())
  11. {
  12. //新表扩容到旧表的两倍
  13. vector<Node*> _newtables(_tables.size() * 2);
  14. //遍历旧表,取旧表结点头插到新表
  15. for (size_t i = 0; i < _tables.size(); i++)
  16. {
  17. Node* cur = _tables[i];
  18. while (cur != nullptr)
  19. {
  20. Node* nextnode = cur->_next;
  21. size_t hashi = cur->_kv.first % _newtables.size();
  22. cur->_next = _newtables[hashi];
  23. _newtables[hashi] = cur;
  24. cur = nextnode;
  25. }
  26. _tables[i] = nullptr;
  27. }
  28. //交换新表与旧表
  29. _tables.swap(_newtables);
  30. }
  31. //插入
  32. //计算插入位置
  33. size_t hashi = kv.first % _tables.size();
  34. Node* newnode = new Node(kv);
  35. //单链表头插
  36. newnode->_next = _tables[hashi];
  37. _tables[hashi] = newnode;
  38. ++_n;
  39. return true;
  40. }

删除

  1. bool Erase(const K& key)
  2. {
  3. //确定待删除数据的位置
  4. size_t hashi = key % _tables.size();
  5. Node* cur = _tables[hashi];
  6. Node* prev = nullptr;
  7. while (cur != nullptr)
  8. {
  9. if ((cur->_kv).first == key)
  10. {
  11. //删除
  12. if (prev != nullptr)
  13. {
  14. prev->_next = cur->_next;
  15. }
  16. else
  17. {
  18. _tables[hashi] = cur->_next;
  19. }
  20. delete cur;
  21. --_n;
  22. return true;
  23. }
  24. else
  25. {
  26. prev = cur;
  27. cur = cur->_next;
  28. }
  29. }
  30. return false;
  31. }

析构函数

  1. //析构函数
  2. ~HashTable()
  3. {
  4. for (size_t i = 0; i < _tables.size(); i++)
  5. {
  6. Node* cur = _tables[i];
  7. while (cur != nullptr)
  8. {
  9. Node* next = cur->_next;
  10. delete cur;
  11. cur = next;
  12. }
  13. _tables[i] = nullptr;
  14. }
  15. }

当哈希表中的HashNode存储string时,string类型的数据不支持取模运算,若采用运算符重载,需要更改库中的string,除留余数法如何建立string类型数据与存储位置的映射关系吗?

解决方案:

  1. 首先将string类型转换为整型,建立string类型与整型的映射关系(采用仿函数实现);
  2. 其次转换后的整型值与存储位置建立映射关系(模版参数增加1个接收仿函数);

思考:任意类型的值如何转换为整型值 ?

  • 本身为整型家族的成员,则可以通过强制类型转换可以转化为整型;
  • 对于string类型,可以取每个字符的ASCII码值,逐个相加且每相加一次乘以权重31;
  • 对于其他类型,按数据类型各自的特征自定义仿函数实现转换为整型;
  1. template<class K>
  2. struct HashFunc
  3. {
  4. size_t operator()(const K& key)
  5. {
  6. return (size_t)key;
  7. }
  8. };
  9. //string类型使用模版的特化
  10. template<>
  11. struct HashFunc<string>
  12. {
  13. size_t operator()(const string& key)
  14. {
  15. size_t hash = 0;
  16. for (auto e : key)
  17. {
  18. hash += e;
  19. hash *= 31;//BKDR字符串哈希算法,累乘因子为31
  20. }
  21. return hash;
  22. }
  23. };
  1. template<class K, class V, class Hash = HashFunc<K>>
  2. class HashTable
  3. {
  4. typedef HashNode<K, V> Node;
  5. public:
  6. //...
  7. private:
  8. vector<Node*> _tables;
  9. size_t _n;//记录哈希表中实际存放的数据个数
  10. };

拷贝构造函数

  1. //拷贝构造函数 ht2(ht1)
  2. HashTable(const HashTable& ht)
  3. {
  4. Hash hs;
  5. //开辟相同大小的空间
  6. _tables.resize(ht._tables.size());
  7. //遍历旧表,头插到新表
  8. for (size_t i = 0; i < ht._tables.size(); i++)
  9. {
  10. Node* cur = ht._tables[i];
  11. while (cur != nullptr)
  12. {
  13. Node* next = cur->_next;
  14. //计算新表的插入位置
  15. size_t hashi = hs(cur->_kv.first) % _tables.size();
  16. cur->_next = _tables[hashi];
  17. _tables[hashi] = cur;
  18. cur = next;
  19. }
  20. }
  21. _n = ht._n;
  22. }

赋值运算符重载

  1. //赋值运算符重载 ht2=ht1
  2. HashTable& operator=(HashTable ht)
  3. {
  4. _tables.swap(ht._tables);
  5. swap(_n, ht._n);
  6. return *this;
  7. }

哈希桶模拟实现源码

  1. template<class K>
  2. struct HashFunc
  3. {
  4. size_t operator()(const K& key)
  5. {
  6. return (size_t)key;
  7. }
  8. };
  9. // 特化
  10. template<>
  11. struct HashFunc<string>
  12. {
  13. size_t operator()(const string& s)
  14. {
  15. size_t hash = 0;
  16. for (auto e : s)
  17. {
  18. hash += e;
  19. hash *= 31;
  20. }
  21. return hash;
  22. }
  23. };
  24. template<class K, class V>
  25. struct HashNode
  26. {
  27. HashNode<K, V>* _next;
  28. pair<K, V> _kv;
  29. HashNode(const pair<K, V>& kv)
  30. {
  31. _kv = kv;
  32. _next = nullptr;
  33. }
  34. };
  35. template<class K, class V, class Hash = HashFunc<K>>
  36. class HashTable
  37. {
  38. typedef HashNode<K, V> Node;
  39. public:
  40. //构造函数
  41. HashTable(size_t n = 10)
  42. {
  43. _tables.resize(n, nullptr);
  44. _n = 0;
  45. }
  46. //析构函数
  47. ~HashTable()
  48. {
  49. for (size_t i = 0; i < _tables.size(); i++)
  50. {
  51. Node* cur = _tables[i];
  52. while (cur != nullptr)
  53. {
  54. Node* next = cur->_next;
  55. delete cur;
  56. cur = next;
  57. }
  58. _tables[i] = nullptr;
  59. }
  60. }
  61. //拷贝构造函数 ht2(ht1)
  62. HashTable(const HashTable& ht)
  63. {
  64. Hash hs;
  65. //开辟相同大小的空间
  66. _tables.resize(ht._tables.size());
  67. //遍历旧表,头插到新表
  68. for (size_t i = 0; i < ht._tables.size(); i++)
  69. {
  70. Node* cur = ht._tables[i];
  71. while (cur != nullptr)
  72. {
  73. Node* next = cur->_next;
  74. //计算新表的插入位置
  75. size_t hashi = hs(cur->_kv.first) % _tables.size();
  76. cur->_next = _tables[hashi];
  77. _tables[hashi] = cur;
  78. cur = next;
  79. }
  80. }
  81. _n = ht._n;
  82. }
  83. //ht2=ht1
  84. HashTable& operator=(HashTable ht)
  85. {
  86. _tables.swap(ht._tables);
  87. swap(_n, ht._n);
  88. return *this;
  89. }
  90. Node* Find(const K& key)
  91. {
  92. Hash hs;
  93. size_t hashi = hs(key) % _tables.size();
  94. Node* cur = _tables[hashi];
  95. while (cur != nullptr)
  96. {
  97. if ((cur->_kv).first == key)
  98. {
  99. return cur;
  100. }
  101. cur = cur->_next;
  102. }
  103. return nullptr;
  104. }
  105. bool Insert(const pair<K, V>& kv)
  106. {
  107. //若键值对中的键值key已存在,则插入失败
  108. Node* ret = Find(kv.first);
  109. if (ret != nullptr)
  110. {
  111. return false;
  112. }
  113. //控制负载因子为1,即哈希表中实际存放的数据个数与哈希表长度长度相等
  114. if (_n == _tables.size())
  115. {
  116. //新表扩容到旧表的两倍
  117. vector<Node*> _newtables(_tables.size() * 2);
  118. //遍历旧表,取旧表结点头插到新表
  119. for (size_t i = 0; i < _tables.size(); i++)
  120. {
  121. Node* cur = _tables[i];
  122. while (cur != nullptr)
  123. {
  124. Node* nextnode = cur->_next;
  125. Hash hs;
  126. size_t hashi = hs(cur->_kv.first) % _newtables.size();
  127. cur->_next = _newtables[hashi];
  128. _newtables[hashi] = cur;
  129. cur = nextnode;
  130. }
  131. _tables[i] = nullptr;
  132. }
  133. //交换新表与旧表
  134. _tables.swap(_newtables);
  135. }
  136. //插入
  137. //计算插入位置
  138. Hash hs;
  139. size_t hashi = hs(kv.first) % _tables.size();
  140. Node* newnode = new Node(kv);
  141. //单链表头插
  142. newnode->_next = _tables[hashi];
  143. _tables[hashi] = newnode;
  144. ++_n;
  145. return true;
  146. }
  147. bool Erase(const K& key)
  148. {
  149. Hash hs;
  150. //确定待删除数据的位置
  151. size_t hashi = hs(key) % _tables.size();
  152. Node* cur = _tables[hashi];
  153. Node* prev = nullptr;
  154. while (cur != nullptr)
  155. {
  156. if ((cur->_kv).first == key)
  157. {
  158. //删除
  159. if (prev != nullptr)
  160. {
  161. prev->_next = cur->_next;
  162. }
  163. else
  164. {
  165. _tables[hashi] = cur->_next;
  166. }
  167. delete cur;
  168. --_n;
  169. return true;
  170. }
  171. else
  172. {
  173. prev = cur;
  174. cur = cur->_next;
  175. }
  176. }
  177. return false;
  178. }
  179. private:
  180. vector<Node*> _tables;
  181. size_t _n;//记录哈希表中实际存放的数据个数
  182. };

欢迎大家批评指正,博主会持续输出优质内容,谢谢各位观众老爷观看,码字画图不易,希望大家给个一键三连支持~ 你的支持是我创作的不竭动力~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/837864
推荐阅读
相关标签
  

闽ICP备14008679号