当前位置:   article > 正文

unordered_map和unordered_set的模拟实现和封装

unordered_map和unordered_set的模拟实现和封装

散列哈希表和开散列哈希桶数据结构

  1. enum State//状态
  2. {
  3. EMPTY,//空
  4. EXIST,//存在
  5. DELETE//删除
  6. };
  7. template<class K, class V>
  8. struct HashData//数据结构
  9. {
  10. pair<K, V> _kv;
  11. State _state = EMPTY;
  12. };
  13. template<class T>
  14. struct HashNode
  15. {
  16. HashNode<T>* _next;
  17. T _data;
  18. HashNode(const T& data)
  19. :_next(nullptr)
  20. , _data(data)
  21. {}
  22. };

闭散列哈希表实际上就是一个vector数组,本质和顺序表一样,为了方便删除,我们直接给各个数据设置一个State值,当为EMPTY时则为空,能够插入值,当为EXIST时则为存在,不能插入值,当我们需要删除时,直接把状态值设置为DELETE,视为删除,插入值时只需要判断是否为EMPTY或DELETE即可。
开散列哈希桶则就是vector中存入指针,然后将key值链接起来。

哈希表HashTable

  1. class HashTable
  2. {
  3. public:
  4. bool Insert(const pair<K, V>& kv);
  5. HashData<K, V>* Find(const K& key);
  6. bool Erase(const K& key);
  7. private:
  8. vector<HashData<K, V>> _tables;
  9. size_t _n = 0; // 存储的数据个数
  10. };

Find()

  1. HashData<K, V>* Find(const K& key)
  2. {
  3. if (_tables.size() == 0)
  4. {
  5. return nullptr;
  6. }
  7. size_t hashi = key % _tables.size();
  8. // 线性探测
  9. size_t i = 1;
  10. size_t index = hashi;
  11. while (_tables[index]._state != EMPTY)
  12. {
  13. if (_tables[index]._state == EXIST
  14. && _tables[index]._kv.first == key)
  15. {
  16. return &_tables[index];
  17. }
  18. index = hashi + i;//往后找
  19. index %= _tables.size();//找了一圈后 index==hashi
  20. ++i;
  21. //找完全部后没找到就退出循环
  22. if (index == hashi)
  23. {
  24. break;
  25. }
  26. }
  27. return nullptr;
  28. }

数据查找只需判断这个值是否存在,然后再一个一个往后找即可,用hashi定位key位置,如果哈希冲突,则该key闭散列了,要往后找,index加上i值一个一个往后找,当找完一圈后再模%,相等即找完。

Erase()

  1. bool Erase(const K& key)
  2. {
  3. HashData<K, V>* ret = Find(key);
  4. if (ret)
  5. {
  6. ret->_state = DELETE;//修改状态值即可“删除”
  7. --_n;
  8. return true;
  9. }
  10. else
  11. {
  12. return false;
  13. }
  14. }

找到值后修改State状态为DELETE即可。

Insert()

首先判断是否需要扩容,如果负载因子>=7,则扩容

  1. //判断是否扩容
  2. //if ((double)_n / (double)_tables.size() >= 0.7)
  3. if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
  4. {
  5. size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
  6. HashTable<K, V> newht;
  7. newht._tables.resize(newsize);
  8. // 遍历旧表,重新映射到新表
  9. for (auto& data : _tables)
  10. {
  11. if (data._state == EXIST)
  12. {
  13. newht.Insert(data._kv);
  14. }
  15. }
  16. _tables.swap(newht._tables);
  17. }
  18. size_t hashi = kv.first % _tables.size();

扩容的方法是把旧表遍历一边,重新插入到新表中,然后再交换两个表,更新映射关系hashi即可

  1. bool Insert(const pair<K, V>& kv)
  2. {
  3. if (Find(kv.first))//找到就不插入了
  4. return false;
  5. // 判断扩容
  6. //
  7. // 线性探测
  8. size_t i = 1;
  9. size_t index = hashi;
  10. while (_tables[index]._state == EXIST)
  11. {
  12. index = hashi + i;
  13. index %= _tables.size();
  14. ++i;
  15. }
  16. _tables[index]._kv = kv;
  17. _tables[index]._state = EXIST;
  18. _n++;
  19. return true;
  20. }

扩容完成后再继续线性探测,找到空位置插入。

迭代器

  1. template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
  2. struct __HashIterator
  3. {
  4. typedef HashNode<T> Node;
  5. typedef HashTable<K, T, KeyOfT, Hash> HT;
  6. typedef __HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
  7. typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
  8. Node* _node;
  9. const HT* _ht;
  10. __HashIterator(Node* node, const HT* ht)
  11. :_node(node)
  12. , _ht(ht)
  13. {}
  14. __HashIterator(const Iterator& it)
  15. :_node(it._node)
  16. , _ht(it._ht)
  17. {}
  18. Ref operator*()
  19. {
  20. return _node->_data;
  21. }
  22. Ptr operator->()
  23. {
  24. return &_node->_data;
  25. }
  26. bool operator!=(const Self& s)
  27. {
  28. return _node != s._node;
  29. }

迭代器的实现难点在于套模版,其他和红黑树的迭代器一样,也可以用非const构造const迭代器

opertor++()

  1. Self& operator++()
  2. {
  3. if (_node->_next != nullptr)
  4. {
  5. _node = _node->_next;
  6. }
  7. else
  8. {
  9. // 找下一个不为空的桶
  10. KeyOfT kot;
  11. Hash hash;
  12. // 算出我当前的桶位置
  13. size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
  14. ++hashi;
  15. while (hashi < _ht->_tables.size())
  16. {
  17. if (_ht->_tables[hashi])
  18. {
  19. _node = _ht->_tables[hashi];
  20. break;
  21. }
  22. else
  23. {
  24. ++hashi;
  25. }
  26. }
  27. // 没有找到不为空的桶
  28. if (hashi == _ht->_tables.size())
  29. {
  30. _node = nullptr;
  31. }
  32. }
  33. return *this;
  34. }

一个桶的链表结束后,再找下一个不为空的桶。这里用了两个仿函数kot和hash,就是把_data中的key值传出来和size进行比较,后面封装unorderedmap和unorderedset会用到仿函数

  1. struct MapKeyOfT
  2. {
  3. const K& operator()(const pair<K, V>& kv)
  4. {
  5. return kv.first;
  6. }
  7. };
  8. struct SetKeyOfT
  9. {
  10. const K& operator()(const K& key)
  11. {
  12. return key;
  13. }
  14. };

Hash是将key值转化成[可以取模的整型]的仿函数

  1. template<class K>
  2. struct HashFunc
  3. {
  4. size_t operator()(const K& key)
  5. {
  6. return key;
  7. }
  8. };
  9. // 特化
  10. template<>
  11. struct HashFunc<string>
  12. {
  13. // BKDR
  14. size_t operator()(const string& s)
  15. {
  16. size_t hash = 0;
  17. for (auto ch : s)
  18. {
  19. hash += ch;
  20. hash *= 31;
  21. }
  22. return hash;
  23. }
  24. };

由于string的特殊性,需要单独处理,这里用到了特化这个案例;乘以31是为了处理字符相同但是顺序不同的情况。

哈希桶

  1. template<class K, class T, class KeyOfT, class Hash>
  2. class HashTable
  3. {
  4. template<class K1, class T1, class Ref, class Ptr, class KeyOfT1, class Hash1>
  5. friend struct __HashIterator;
  6. typedef HashNode<T> Node;
  7. public:
  8. typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> iterator;
  9. typedef __HashIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;
  10. private:
  11. vector<Node*> _tables; // 指针数组
  12. size_t _n = 0; // 存储有效数据个数
  13. };

要让迭代器可以访问私有成员,将它设置为友元函数,注意,模版的命名不能冲突

  1. iterator begin()
  2. {
  3. Node* cur = nullptr;
  4. for (size_t i = 0; i < _tables.size(); ++i)
  5. {
  6. cur = _tables[i];
  7. if (cur)
  8. {
  9. break;
  10. }
  11. }
  12. return iterator(cur, this);
  13. }
  14. iterator end()
  15. {
  16. return iterator(nullptr, this);
  17. }
  18. const_iterator begin() const
  19. {
  20. Node* cur = nullptr;
  21. for (size_t i = 0; i < _tables.size(); ++i)
  22. {
  23. cur = _tables[i];
  24. if (cur)
  25. {
  26. break;
  27. }
  28. }
  29. return const_iterator(cur, this);
  30. }
  31. const_iterator end() const
  32. {
  33. return const_iterator(nullptr, this);
  34. }

begin只需要找到第一个不为空的桶中的第一个节点即可,end则为空。

Find()

  1. iterator Find(const K& key)
  2. {
  3. if (_tables.size() == 0)
  4. return end();
  5. KeyOfT kot;
  6. Hash hash;
  7. size_t hashi = hash(key) % _tables.size();
  8. Node* cur = _tables[hashi];
  9. while (cur)
  10. {
  11. if (kot(cur->_data) == key)
  12. {
  13. return iterator(cur, this);
  14. }
  15. cur = cur->_next;
  16. }
  17. return end();
  18. }

find没什么好说的,直接找到映射的桶,然后遍历链表即可

Erase()

  1. bool Erase(const K& key)
  2. {
  3. Hash hash;
  4. KeyOfT kot;
  5. size_t hashi = hash(key) % _tables.size();
  6. Node* prev = nullptr;
  7. Node* cur = _tables[hashi];
  8. while (cur)
  9. {
  10. if (kot(cur->_data) == key)
  11. {
  12. if (prev == nullptr)
  13. {
  14. _tables[hashi] = cur->_next;
  15. }
  16. else
  17. {
  18. prev->_next = cur->_next;
  19. }
  20. delete cur;
  21. return true;
  22. }
  23. else
  24. {
  25. prev = cur;
  26. cur = cur->_next;
  27. }
  28. }
  29. return false;
  30. }

删除即是找到节点,然后再头删这个节点即可。

Insert()

  1. pair<iterator, bool> Insert(const T& data)
  2. {
  3. KeyOfT kot;
  4. iterator it = Find(kot(data));
  5. if (it != end())
  6. {
  7. return make_pair(it, false);
  8. }
  9. Hash hash;
  10. // 负载因因子==1时扩容
  11. if (_n == _tables.size())
  12. size_t newsize = GetNextPrime(_tables.size());
  13. vector<Node*> newtables(newsize, nullptr);
  14. //for (Node*& cur : _tables)
  15. for (auto& cur : _tables)
  16. {
  17. while (cur)
  18. {
  19. Node* next = cur->_next;
  20. size_t hashi = hash(kot(cur->_data)) % newtables.size();
  21. // 头插到新表
  22. cur->_next = newtables[hashi];
  23. newtables[hashi] = cur;
  24. cur = next;
  25. }
  26. }
  27. _tables.swap(newtables);
  28. }
  29. size_t hashi = hash(kot(data)) % _tables.size();
  30. // 头插
  31. Node* newnode = new Node(data);
  32. newnode->_next = _tables[hashi];
  33. _tables[hashi] = newnode;
  34. ++_n;
  35. return make_pair(iterator(newnode, this), false);
  36. }

插入主要还是扩容比较麻烦,这里用的是质数扩容,库里面用的就是这个:

  1. size_t GetNextPrime(size_t prime)
  2. {
  3. // SGI
  4. static const int __stl_num_primes = 28;
  5. static const unsigned long __stl_prime_list[__stl_num_primes] =
  6. {
  7. 53, 97, 193, 389, 769,
  8. 1543, 3079, 6151, 12289, 24593,
  9. 49157, 98317, 196613, 393241, 786433,
  10. 1572869, 3145739, 6291469, 12582917, 25165843,
  11. 50331653, 100663319, 201326611, 402653189, 805306457,
  12. 1610612741, 3221225473, 4294967291
  13. };
  14. size_t i = 0;
  15. for (; i < __stl_num_primes; ++i)
  16. {
  17. if (__stl_prime_list[i] > prime)
  18. return __stl_prime_list[i];
  19. }
  20. return __stl_prime_list[i];
  21. }

最大桶

  1. size_t MaxBucketSize()
  2. {
  3. size_t max = 0;
  4. for (size_t i = 0; i < _tables.size(); ++i)
  5. {
  6. auto cur = _tables[i];
  7. size_t size = 0;
  8. while (cur)
  9. {
  10. ++size;
  11. cur = cur->_next;
  12. }
  13. //printf("[%d]->%d\\n", i, size);
  14. if (size > max)
  15. {
  16. max = size;
  17. }
  18. }
  19. return max;
  20. }

 哈希表实现完后,对于unordered_map/set只需要简单封装一下即可

  1. #include "HashTable.h"
  2. namespace lty
  3. {
  4. template<class K, class V, class Hash = HashFunc<K>>
  5. class unordered_map
  6. {
  7. public:
  8. struct MapKeyOfT
  9. {
  10. const K& operator()(const pair<K, V>& kv)
  11. {
  12. return kv.first;
  13. }
  14. };
  15. public:
  16. typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;
  17. typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::const_iterator const_iterator;
  18. iterator begin()
  19. {
  20. return _ht.begin();
  21. }
  22. iterator end()
  23. {
  24. return _ht.end();
  25. }
  26. const_iterator begin() const
  27. {
  28. return _ht.begin();
  29. }
  30. const_iterator end() const
  31. {
  32. return _ht.end();
  33. }
  34. pair<iterator, bool> insert(const pair<K, V>& kv)
  35. {
  36. return _ht.Insert(kv);
  37. }
  38. V& operator[](const K& key)
  39. {
  40. pair<iterator, bool> ret = insert(make_pair(key, V()));
  41. return ret.first->second;
  42. //first==iterator first-> == _node->_data
  43. first->second==_node->_data->second,也就是V值
  44. }
  45. iterator find(const K& key)
  46. {
  47. return _ht.Find(key);
  48. }
  49. bool erase(const K& key)
  50. {
  51. return _ht.Erase(key);
  52. }
  53. private:
  54. HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
  55. };

set和map除了K、V值的不一样,还有就是map重载了[],和红黑树的[]一样,返回pari的second值

  1. #pragma once
  2. #include "HashTable.h"
  3. namespace lty
  4. {
  5. template<class K, class Hash = HashFunc<K>>
  6. class unordered_set
  7. {
  8. public:
  9. struct SetKeyOfT
  10. {
  11. const K& operator()(const K& key)
  12. {
  13. return key;
  14. }
  15. };
  16. public:
  17. typedef typename HashBucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;
  18. typedef typename HashBucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;
  19. iterator begin()
  20. {
  21. return _ht.begin();
  22. }
  23. iterator end()
  24. {
  25. return _ht.end();
  26. }
  27. const_iterator begin() const
  28. {
  29. return _ht.begin();
  30. }
  31. const_iterator end() const
  32. {
  33. return _ht.end();
  34. }
  35. pair<iterator, bool> insert(const K& key)
  36. {
  37. return _ht.Insert(key);
  38. }
  39. iterator find(const K& key)
  40. {
  41. return _ht.Find(key);
  42. }
  43. bool erase(const K& key)
  44. {
  45. return _ht.Erase(key);
  46. }
  47. private:
  48. HashBucket::HashTable<K, K, SetKeyOfT, Hash> _ht;
  49. };

迭代器直接调用哈希中的即可。

哈希表的实现难点就在于模版的套用,很绕,只要搞懂了模版是怎么套用的,实现就会变得简单,只要知道哈希表原理即可。 

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

闽ICP备14008679号