当前位置:   article > 正文

hash:哈希表 哈希桶

哈希桶

目录

1.哈希的思想

2.解决冲突

3.哈希表(采用的闭散列,线性探测)

4.哈希桶(开散列)

5.总结


1.哈希的思想

hash是一种根据存储的关键字的哈希值确定存储位置的算法。哈希值通过哈希函数可以计算得出结果,最常用的哈希函数就是除留余数法了。


2.解决冲突

如果俩个关键字值不同但是哈希值相同就会发生不明确存哪的问题,这种情况被称为哈希冲突。

解决冲突的方式1:闭散列,采用用线性探测,即跳过被占用的按顺序向下探测位置。

解决冲突的方式2:开散列,由于是链式结构,所以直接在对应位置串起来即可。


3.哈希表(采用的闭散列,线性探测

上菜:

  1. enum state
  2. {
  3. empty, //表示空状态
  4. exist, //表示占据状态
  5. dlete //表示删除状态
  6. };
  7. template<class K>
  8. class HashFunc //仿函数,用来计算关键字的hash值
  9. {
  10. int operator()(const K& key)
  11. {
  12. return key;
  13. }
  14. };
  15. template<> //特化,采用string的assic码来计算
  16. class HashFunc<string>
  17. {
  18. int operator()(const string& key)
  19. {
  20. int val = 0;
  21. for (auto e : key)
  22. {
  23. val *= 131; //结论,用数学公式证明的,用就完了。
  24. val += e;
  25. }
  26. return val;
  27. }
  28. };
  29. template<class K,class V>
  30. struct hashnode
  31. {
  32. state st=empty;
  33. pair<K, V> _kv;
  34. };
  35. template<class K,class V,class Hash=HashFunc<K>>
  36. class hashtable
  37. {
  38. private:
  39. vector<hashnode<K,V>> _table;
  40. int size = 0;
  41. public:
  42. bool insert(const pair<K, V>& kv) //插入
  43. {
  44. if (_table.size() == 0 || 10*size/_table.size() >= 7) //负载因子确定为0.7
  45. {
  46. int newsize = _table.size() == 0 ? 10 : _table.size() = _table.size() * 2;
  47. hashtable<K,V,Hash> newtable; //建立新表
  48. newtable._table.resize(newsize); //修改长度
  49. for (auto e : _table)
  50. {
  51. //将旧表元素插入到新表中
  52. if (e.st == empty)
  53. {
  54. newtable.insert(e._kv);
  55. }
  56. }
  57. _table.swap(newtable._table);//新旧表交换
  58. }
  59. Hash hash;
  60. int val=hash(kv.first); //计算下标
  61. int hashi = val % (_table.size());
  62. while (_table[hashi].st == exist) //已经被占据了就继续查找
  63. {
  64. hashi++;
  65. hashi %= (_table.size()); //防止越界
  66. }
  67. _table[hashi]._kv = kv;
  68. _table[hashi].st = exist; //记得标记为占有状态
  69. size++;
  70. return true;
  71. }
  72. hashnode<K, V>* find(const K& key) //要找一个循环,因为元素可能发生了冲突
  73. {
  74. Hash hash;
  75. int start = hash(key) % (_table.size()); //标记起始位置
  76. int hashi = start;
  77. while (hashi != start && _table[hashi].st != empty)//不能为空位置
  78. {
  79. if (_table[hashi].st != delete && _table[hashi]._kv.first == key)
  80. {
  81. return &_table[hashi];
  82. }
  83. hashi++;
  84. hashi %= (_table.size());
  85. }
  86. return nullptr;
  87. }
  88. bool erase(const K& key)
  89. {
  90. hashnode<K, V>* ret = find(key); //复用find查找位置
  91. if (ret)
  92. {
  93. ret->st = dlete;
  94. --size; //记得--size
  95. return true;
  96. }
  97. return false;
  98. }
  99. void print() //打印看看结果
  100. {
  101. for (size_t i = 0; i < _tables.size(); ++i)
  102. {
  103. if (_tables[i].st == exist)
  104. {
  105. printf("[%d:%d] ", i, _tables[i]._kv.first);
  106. }
  107. else
  108. {
  109. printf("[%d:*] ", i);
  110. }
  111. }
  112. cout << endl;
  113. }
  114. };

思路非常简单,先用hash函数找到对应位置。被占住了就继续向后找空位置。写这段代码需要注意的是在扩容时,由于长度的变化,需要映射新的位置。可以采用复用insert的方式完成映射。


4.哈希桶(开散列)

上菜:

  1. #pragma once
  2. #include<iostream>
  3. #include<vector>
  4. using namespace std;
  5. template<class K>
  6. struct HashFunc
  7. {
  8. int operator()(const K& key)
  9. {
  10. return key;
  11. }
  12. };
  13. template<>
  14. struct HashFunc<string>
  15. {
  16. int operator()(const string& key)
  17. {
  18. int val = 0;
  19. for (auto e : key)
  20. {
  21. val *= 131;
  22. val += e;
  23. }
  24. return val;
  25. }
  26. };
  27. template<class K, class V>
  28. struct HashNode //链式结构要创建结点
  29. {
  30. pair<K, V> _kv;
  31. HashNode* _next;
  32. HashNode(pair<K, V> kv)
  33. :_kv(kv)
  34. , _next(nullptr)
  35. {}
  36. };
  37. template<class K,class V,class Hash=HashFunc<K>>
  38. class Hashtable
  39. {
  40. private:
  41. vector<HashNode<K,V>*> _table;
  42. int size=0;
  43. public:
  44. ~Hashtable()
  45. {
  46. for (int i = 0; i < _table.size(); i++)
  47. {
  48. HashNode<K, V>* cur = _table[i];
  49. while (cur)
  50. {
  51. HashNode<K, V>* next = cur->_next; //记录下一个位置
  52. delete cur;
  53. cur = next;
  54. }
  55. _table[i] = nullptr; //记得置空
  56. }
  57. }
  58. HashNode<K,V>* find(const K& key)
  59. {
  60. Hash hash;
  61. if (_table.size() == 0)
  62. {
  63. return nullptr;
  64. }
  65. int hashi = hash(key) % _table.size(); //计算位置
  66. HashNode<K,V>* cur = _table[hashi];
  67. while (cur)
  68. {
  69. if (cur->_kv.first == key) //找到
  70. {
  71. return cur;
  72. }
  73. cur = cur->_next;
  74. }
  75. return nullptr;
  76. }
  77. bool insert(const pair<K, V>& kv)
  78. {
  79. if (find(kv.first)) //去重
  80. return false;
  81. if (_table.size() == size || _table.size() == 0) //扩容
  82. {
  83. int newsize = (_table.size() == 0 ? 10 : _table.size() * 2);
  84. Hashtable<K, V, Hash> newtable; //构建新表
  85. newtable._table.resize(newsize);
  86. for (int i = 0; i < _table.size(); i++)
  87. {
  88. //不能浪费空间,要将原来的资源直接插入到新表就不可以复用。
  89. HashNode<K, V>* cur = _table[i];
  90. while (cur)
  91. {
  92. HashNode<K, V>* next = cur->_next;
  93. Hash hash;
  94. int hashi = hash(cur->_kv.first)%(newtable._table.size());
  95. cur->_next = newtable._table[hashi];
  96. newtable._table[hashi] = cur;
  97. cur = next;
  98. }
  99. }
  100. _table.swap(newtable._table);
  101. }
  102. Hash hash;
  103. int hashi = hash(kv.first)%_table.size();
  104. HashNode<K, V>* newnode = new HashNode<K, V>(kv);
  105. //头插
  106. newnode->_next = _table[hashi];
  107. _table[hashi] = newnode;
  108. ++size; //记得++size
  109. }
  110. };

同样是先用hash函数找到对应位置,不同的是,可以直接头插不需要移位。唯一需要注意的依然是扩容,这里不可以复用代码,因为旧表的空间必须利用上,不然会出现资源泄露的问题。


5.总结

hash(链式)相对于平衡搜索数(avl 红黑树)有优有劣。

优点:a.对于查找来说,平衡搜索树时间复杂度是O(logn)。而hash的时间复杂度趋近O(1)。

           b.对于插入来说,平衡搜索树需要不断的翻转。而hash直接头插即可。

缺点:a.由于搜索树的性质决定了搜索树可以将数据有序化,而hash是无序的。

           b.随着数据的增大,hash空间消耗会大于平衡搜索树。

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

闽ICP备14008679号