• 哈希表、unordered_map和unordered_set模拟


    目录

    哈希表

    闭散列

    开散列

    unordered_map和unordered_set模拟

    对开散列的哈希表改造

    unordered_set模拟

    unordered_map模拟


    哈希表

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

    ps:该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

    插入元素:根据待插入的元素的关键码,以此函数计算出该元素的存放位置并存放。

    搜索元素:根据元素的关键码,计算出该元素的存放位置,在该位置取元素比较,如果关键码相等,则查找成功。

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

    常见哈希函数----->1. 直接定址法--(常用)2. 除留余数法--(常用)

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

    闭散列

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

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

    1. //线性探测
    2. size_t start = val.first % _ht.size();
    3. size_t i = 0;
    4. size_t index = start;
    5. while (_ht[index]._state == EXIST) {
    6. i++;
    7. index = start + i;
    8. index %= _ht.size();
    9. }

    插入

            1、通过哈希函数获取待插入元素在哈希表中的位置

            2、如果该位置没有元素则直接插入新元素,如果该位置中发生哈希冲突,则使用线性探测找到下一个空位置,插入新元素。

     删除

            采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索(ps:因为对于其他元素的查找遇见EMPTY即停止查找)。因此,线性探测采用标记来删除一个元素。

    闭散列实现如下:

    1. template<class K>//仿函数处理key
    2. struct Hash {
    3. size_t operator()(const K& key) {
    4. return key;
    5. }
    6. };
    7. template<>
    8. struct Hash {
    9. size_t operator()(const string& s) {
    10. size_t value = 0;
    11. for (auto e : s) {
    12. value *= 31;
    13. value += e;
    14. }
    15. return value;
    16. }
    17. };
    18. namespace closeHash {
    19. enum State {
    20. EXIST,
    21. DELETE,
    22. EMPTY
    23. };
    24. template<class K,class V>
    25. struct HashData {
    26. pair _kv;
    27. State _state = EMPTY;
    28. };
    29. template<class K,class V,class HashFunc = Hash>
    30. class HashTable {
    31. public:
    32. bool Erase(const K& key) {
    33. HashData* ret = Find(key);
    34. if (ret == nullptr)
    35. return false;
    36. else {
    37. _n--;
    38. ret->_state = DELETE;
    39. return true;
    40. }
    41. }
    42. HashData* Find(const K& key) {
    43. if (_table.size() == 0)
    44. return nullptr;
    45. HashFunc hf;
    46. size_t start = hf(key) % _table.size();
    47. size_t i = 0;
    48. size_t index = start;
    49. while (_table[index]._state != EMPTY) {
    50. if (_table[index]._kv.first == key && _table[index]._state == EXIST)
    51. return &_table[index];
    52. i++;
    53. index = start + i;
    54. index %= _table.size();
    55. }
    56. return nullptr;
    57. }
    58. bool Insert(const pair& kv) {
    59. HashData* ret = Find(kv.first);
    60. if (ret)
    61. return false;
    62. if (_table.size() == 0 || _n * 10 / _table.size() > 7) {
    63. //扩容
    64. size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
    65. HashTable newTable;
    66. newTable._table.resize(newSize);
    67. for (size_t i = 0; i < _table.size(); i++) {
    68. if (_table[i]._state == EXIST)
    69. newTable.Insert(_table[i]._kv);
    70. }
    71. _table.swap(newTable._table);
    72. }
    73. HashFunc hf;
    74. size_t start = hf(kv.first) % _table.size();
    75. size_t i = 0;
    76. size_t index = start;
    77. while (_table[index]._state != EMPTY) {
    78. i++;
    79. index = start + i;
    80. index %= _table.size();
    81. }
    82. _table[index]._kv = kv;
    83. _table[index]._state = EXIST;
    84. _n++;
    85. return true;
    86. }
    87. private:
    88. vector> _table;
    89. size_t _n;
    90. };
    91. void test() {
    92. HashTable<int, int> ht;
    93. int a[] = { 2,12,22,32,42,52,62 };
    94. for (auto& e : a) {
    95. ht.Insert(make_pair(e, e));
    96. }
    97. ht.Insert(make_pair(72, 72));
    98. ht.Insert(make_pair(32, 32));
    99. //ht.Insert(make_pair(-1, -1));
    100. ht.Insert(make_pair(-999, -999));
    101. cout << ht.Find(12) << endl;
    102. ht.Erase(12);
    103. cout << ht.Find(12) << endl;
    104. }
    105. }

    开散列

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

    开散列的增容:桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可 能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希 表进行增容,那该条件怎么确认呢?开散列最好的情况是:每个哈希桶中刚好挂一个节点, 再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。

    size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;

    开散列实现如下:

    1. template<class K>//仿函数处理key
    2. struct Hash {
    3. size_t operator()(const K& key) {
    4. return key;
    5. }
    6. };
    7. template<>
    8. struct Hash {
    9. size_t operator()(const string& s) {
    10. size_t value = 0;
    11. for (auto e : s) {
    12. value *= 31;
    13. value += e;
    14. }
    15. return value;
    16. }
    17. };
    18. namespace openHash {
    19. template<class K,class V>
    20. struct HashNode {
    21. pair _kv;
    22. HashNode* _next;
    23. HashNode(const pair& kv)
    24. :_kv(kv)
    25. ,_next(nullptr)
    26. {}
    27. };
    28. template<class K,class V,class HashFunc = Hash>
    29. class HashBucket {
    30. typedef HashNode Node;
    31. public:
    32. bool Erase(const K& key) {
    33. if (_table.empty())
    34. return false;
    35. HashFunc hf;
    36. size_t index = hf(key) % _table.size();
    37. Node* cur = _table[index];
    38. Node* prev = nullptr;
    39. while (cur) {
    40. if (cur->_kv.first == key) {
    41. if (prev == nullptr)
    42. _table[index] = cur->_next;
    43. else
    44. prev->_next = cur->_next;
    45. delete cur;
    46. _n--;
    47. return true;
    48. }
    49. prev = cur;
    50. cur = cur->_next;
    51. }
    52. return false;
    53. }
    54. Node* Find(const K& key) {
    55. if (_table.empty())
    56. return nullptr;
    57. HashFunc hf;
    58. size_t index = hf(key) % _table.size();
    59. Node* cur = _table[index];
    60. while (cur) {
    61. if (cur->_kv.first == key)
    62. return cur;
    63. cur = cur->_next;
    64. }
    65. return nullptr;
    66. }
    67. bool Insert(const pair& kv) {
    68. Node* ret = Find(kv.first);
    69. if (ret)
    70. return false;
    71. HashFunc hf;
    72. if (_n == _table.size()) {
    73. //扩容
    74. size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
    75. vector newBucket;
    76. newBucket.resize(newSize);
    77. for (size_t i = 0; i < _table.size(); i++) {
    78. Node* cur = _table[i];
    79. while (cur) {
    80. Node* next = cur->_next;
    81. size_t index = hf(cur->_kv.first) % newBucket.size();
    82. cur->_next = newBucket[index];
    83. newBucket[index] = cur;
    84. cur = next;
    85. }
    86. _table[i] = nullptr;
    87. }
    88. _table.swap(newBucket);
    89. }
    90. size_t index = hf(kv.first) % _table.size();
    91. Node* newNode = new Node(kv);
    92. newNode->_next = _table[index];
    93. _table[index] = newNode;
    94. _n++;
    95. return true;
    96. }
    97. private:
    98. vector _table;
    99. size_t _n;
    100. };
    101. void test() {
    102. int a[] = { 4,24,14,7,37,27,57,67,34,14,54 };
    103. HashBucket<int, int> ht;
    104. for (auto e : a) {
    105. ht.Insert(make_pair(e, e));
    106. }
    107. ht.Insert(make_pair(84, 84));
    108. }
    109. }

    unordered_map和unordered_set模拟

    对开散列的哈希表改造

    1、将hashNode中存放的数据类型改为泛型

    1. template<class T>
    2. struct HashNode {
    3. T _data;
    4. HashNode* _next;
    5. HashNode(const T& data)
    6. :_data(data)
    7. ,_next(nullptr)
    8. {}
    9. };

    2、对哈希表增加迭代器

    1. typedef __htIterator iterator;
    2. iterator begin() {
    3. for (size_t i = 0; i < _table.size(); i++) {
    4. if (_table[i])
    5. return iterator(_table[i], this);
    6. }
    7. return end();
    8. }
    9. iterator end() {
    10. return iterator(nullptr, this);
    11. }

    具体增加的迭代器类如下:

    1. template<class K,class T,class Ref, class Ptr,class KeyOfT,class HashFunc>
    2. struct __htIterator {
    3. typedef HashNode Node;
    4. typedef __htIterator self;
    5. //迭代器的成员变量
    6. Node* _node;
    7. HashBucket* _pht;
    8. __htIterator(Node* node,HashBucket* pht)
    9. :_node(node)
    10. ,_pht(pht)
    11. {}
    12. Ref operator*() {
    13. return _node->_data;
    14. }
    15. Ptr operator->() {
    16. return &_node->_data;
    17. }
    18. self& operator++() {
    19. if (_node->_next) {
    20. _node = _node->_next;
    21. }
    22. else {
    23. KeyOfT kot;
    24. HashFunc hf;
    25. size_t index = hf(kot(_node->_data)) % _pht->_table.size();
    26. index++;
    27. //找到下一个不为空的桶
    28. while (index < _pht->_table.size()) {
    29. if (_pht->_table[index])
    30. break;
    31. else
    32. index++;
    33. }
    34. if (index == _pht->_table.size())
    35. _node = nullptr;
    36. else
    37. _node = _pht->_table[index];
    38. }
    39. return *this;
    40. }
    41. bool operator==(const self& s) const {
    42. return _node == s._node;
    43. }
    44. bool operator!=(const self& s) const {
    45. return _node != s._node;
    46. }
    47. };

    3、对insert的返回值进行修改,改为pair,方便后续的unordered_map实现operator[]的实现。

    pairbool> Insert(const T& data)

    4、对闭散列增加默认构造、拷贝函数、赋值和以及析构函数

    1. HashBucket() = default;
    2. HashBucket(const self& hb) {
    3. _table.resize(hb._table.size());
    4. for (size_t i = 0; i < hb._table.size(); i++) {
    5. Node* cur = hb._table[i];
    6. while (cur) {
    7. Node* copy = new Node(cur->_data);
    8. copy->_next = _table[i];
    9. _table[i] = copy;
    10. cur = cur->_next;
    11. }
    12. }
    13. }
    14. self& operator=(self hb) {
    15. swap(_n, hb._n);
    16. _table.swap(hb._table);
    17. return *this;
    18. }
    19. ~HashBucket() {
    20. for (size_t i = 0; i < _table.size(); i++) {
    21. Node* cur = _table[i];
    22. while (cur) {
    23. Node* next = cur->_next;
    24. delete cur;
    25. cur = next;
    26. }
    27. _table[i] = nullptr;
    28. }
    29. }

    完整的改造代码如下:

    1. namespace openHash {
    2. template<class T>
    3. struct HashNode {
    4. T _data;
    5. HashNode* _next;
    6. HashNode(const T& data)
    7. :_data(data)
    8. ,_next(nullptr)
    9. {}
    10. };
    11. template<class K, class T, class KeyOfT, class HashFunc>
    12. class HashBucket;
    13. template<class K,class T,class Ref, class Ptr,class KeyOfT,class HashFunc>
    14. struct __htIterator {
    15. typedef HashNode Node;
    16. typedef __htIterator self;
    17. Node* _node;
    18. HashBucket* _pht;
    19. __htIterator(Node* node,HashBucket* pht)
    20. :_node(node)
    21. ,_pht(pht)
    22. {}
    23. Ref operator*() {
    24. return _node->_data;
    25. }
    26. Ptr operator->() {
    27. return &_node->_data;
    28. }
    29. self& operator++() {
    30. if (_node->_next) {
    31. _node = _node->_next;
    32. }
    33. else {
    34. KeyOfT kot;
    35. HashFunc hf;
    36. size_t index = hf(kot(_node->_data)) % _pht->_table.size();
    37. index++;
    38. //找到下一个不为空的桶
    39. while (index < _pht->_table.size()) {
    40. if (_pht->_table[index])
    41. break;
    42. else
    43. index++;
    44. }
    45. if (index == _pht->_table.size())
    46. _node = nullptr;
    47. else
    48. _node = _pht->_table[index];
    49. }
    50. return *this;
    51. }
    52. bool operator==(const self& s) const {
    53. return _node == s._node;
    54. }
    55. bool operator!=(const self& s) const {
    56. return _node != s._node;
    57. }
    58. };
    59. template<class K,class T,class KeyOfT,class HashFunc>
    60. class HashBucket {
    61. typedef HashNode Node;
    62. template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
    63. friend struct __htIterator;
    64. typedef HashBucket self;
    65. public:
    66. typedef __htIterator iterator;
    67. /*HashBucket() {
    68. }*/
    69. HashBucket() = default;
    70. HashBucket(const self& hb) {
    71. _table.resize(hb._table.size());
    72. for (size_t i = 0; i < hb._table.size(); i++) {
    73. Node* cur = hb._table[i];
    74. while (cur) {
    75. Node* copy = new Node(cur->_data);
    76. copy->_next = _table[i];
    77. _table[i] = copy;
    78. cur = cur->_next;
    79. }
    80. }
    81. }
    82. self& operator=(self hb) {
    83. swap(_n, hb._n);
    84. _table.swap(hb._table);
    85. return *this;
    86. }
    87. ~HashBucket() {
    88. for (size_t i = 0; i < _table.size(); i++) {
    89. Node* cur = _table[i];
    90. while (cur) {
    91. Node* next = cur->_next;
    92. delete cur;
    93. cur = next;
    94. }
    95. _table[i] = nullptr;
    96. }
    97. }
    98. iterator begin() {
    99. for (size_t i = 0; i < _table.size(); i++) {
    100. if (_table[i])
    101. return iterator(_table[i], this);
    102. }
    103. return end();
    104. }
    105. iterator end() {
    106. return iterator(nullptr, this);
    107. }
    108. bool Erase(const K& key) {
    109. if (_table.empty())
    110. return false;
    111. HashFunc hf;
    112. size_t index = hf(key) % _table.size();
    113. Node* cur = _table[index];
    114. Node* prev = nullptr;
    115. KeyOfT kot;
    116. while (cur) {
    117. if (kot(cur->_data) == key) {
    118. if (prev == nullptr)
    119. _table[index] = cur->_next;
    120. else
    121. prev->_next = cur->_next;
    122. delete cur;
    123. _n--;
    124. return true;
    125. }
    126. prev = cur;
    127. cur = cur->_next;
    128. }
    129. return false;
    130. }
    131. iterator Find(const K& key) {
    132. if (_table.empty())
    133. return end();
    134. HashFunc hf;
    135. size_t index = hf(key) % _table.size();
    136. Node* cur = _table[index];
    137. KeyOfT kot;
    138. while (cur) {
    139. if (kot(cur->_data) == key)
    140. return iterator(cur, this);
    141. cur = cur->_next;
    142. }
    143. return end();
    144. }
    145. pairbool> Insert(const T& data) {
    146. KeyOfT kot;
    147. iterator ret = Find(kot(data));
    148. if (ret != end())
    149. return make_pair(ret, false);
    150. HashFunc hf;
    151. if (_n == _table.size()) {
    152. //扩容
    153. size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
    154. vector newBucket;
    155. newBucket.resize(newSize);
    156. for (size_t i = 0; i < _table.size(); i++) {
    157. Node* cur = _table[i];
    158. while (cur) {
    159. Node* next = cur->_next;
    160. size_t index = hf(kot(cur->_data)) % newBucket.size();
    161. cur->_next = newBucket[index];
    162. newBucket[index] = cur;
    163. cur = next;
    164. }
    165. _table[i] = nullptr;
    166. }
    167. _table.swap(newBucket);
    168. }
    169. size_t index = hf(kot(data)) % _table.size();
    170. Node* newNode = new Node(data);
    171. newNode->_next = _table[index];
    172. _table[index] = newNode;
    173. _n++;
    174. return make_pair(iterator(newNode, this), true);
    175. }
    176. private:
    177. vector _table;
    178. size_t _n = 0;
    179. };
    180. }

    unordered_set模拟

    unordered_set的底层使用的就是我们改造的开散列哈希表,因为改造的时候将hashnode中存放的数据改为了泛型,所以需要我们传入一个仿函数(这里我们传入的是SetKeyOfT这个函数),让哈希表拿到unordered_set的key,进行后续的逻辑操作。

    除了SetKeyOfT这个仿函数,我们还需要另一个仿函数,即hash = Hash,这个仿函数的作用是当我们通过SetKeyOfT拿到hashnode中的数据中的key值之后,将key的值转换成能够取模的整形。

    1. #pragma once
    2. #include "HTable.h"
    3. namespace hzp {
    4. template<class K, class hash = Hash>
    5. class My_Unordered_Set {
    6. struct SetKeyOfT {
    7. const K& operator()(const K& key) {
    8. return key;
    9. }
    10. };
    11. public:
    12. typedef typename openHash::HashBucket::iterator iterator;
    13. iterator begin() {
    14. return _hb.begin();
    15. }
    16. iterator end() {
    17. return _hb.end();
    18. }
    19. pairbool> insert(const K& key) {
    20. return _hb.Insert(key);
    21. }
    22. private:
    23. openHash::HashBucket _hb;
    24. };
    25. void test_set() {
    26. My_Unordered_Set<int> mus;
    27. int a[] = { 4,14,34,7,24,17 };
    28. for (auto e : a)
    29. mus.insert(e);
    30. My_Unordered_Set<int>::iterator it = mus.begin();
    31. while (it != mus.end()) {
    32. cout << *it << " ";
    33. ++it;
    34. }
    35. cout << endl;
    36. My_Unordered_Set uss;
    37. uss.insert("sort");
    38. uss.insert("hash");
    39. }
    40. }

    unordered_map模拟

    unordered_map的模拟与unordered_set的模拟极为相似,底层也是使用的改造的开散列的哈希表。请参考unordered_set的模拟读下边的代码。

    1. #pragma once
    2. #include "HTable.h"
    3. namespace hzp {
    4. template<class K, class V, class hash = Hash>
    5. class My_Unordered_Map {
    6. struct MapKeyOfT {
    7. const K& operator()(const pair& key) {
    8. return key.first;
    9. }
    10. };
    11. public:
    12. typedef typename openHash::HashBucket, MapKeyOfT, hash>::iterator iterator;
    13. iterator begin() {
    14. return hb.begin();
    15. }
    16. iterator end() {
    17. return hb.end();
    18. }
    19. V& operator[](const K& key) {
    20. auto ret = hb.Insert(make_pair(key, V()));
    21. return ret.first->second;
    22. }
    23. pairbool> insert(const pair& kv) {
    24. return hb.Insert(kv);
    25. }
    26. private:
    27. openHash::HashBucket, MapKeyOfT, hash> hb;
    28. };
    29. void test_map() {
    30. My_Unordered_Map dict;
    31. dict.insert(make_pair("sort", "排序"));
    32. dict.insert(make_pair("string", "ַ字符串"));
    33. dict.insert(make_pair("map", "地图"));
    34. dict["sort"] = "排序1";
    35. cout << dict["sort"] << endl;
    36. My_Unordered_Map::iterator it = dict.begin();
    37. while (it != dict.end())
    38. {
    39. cout << it->first << ":" << it->second << endl;
    40. ++it;
    41. }
    42. cout << endl;
    43. My_Unordered_Map copy(dict);
    44. for (auto& e : copy) {
    45. cout << e.first << ":" << e.second << endl;
    46. }
    47. }
    48. }

  • 相关阅读:
    HTML中文本框\单选框\按钮\多选框
    Kafka实战 - 03 Kafka消费消息实现数据的处置状态同步
    金额转大写查询易语言代码
    基于springboot用户信息增删改查源码【附源码】
    webSocket的通信流程,以及对网络等错误的处理
    opensips的dispatcher模块笔记
    数组名是什么
    深入剖析JavaScript(二)——异步编程
    GPS学习(一):在ROS2中将GPS经纬度数据转换为机器人ENU坐标系,在RVIZ中显示坐标轨迹
    NFTScan | 10.02~10.08 NFT 市场热点汇总
  • 原文地址:https://blog.csdn.net/qq_45576085/article/details/130838540