当前位置:   article > 正文

C++模拟实现unordered_map和unordered_set(哈希)

模拟实现unordered

目录

一、unordered系列关联式容器

1.1 unordered_map

1.1.1 unordered_map

1.1.2 unordered_map接口说明

1. unordered_map的容量

2. unordered_map的迭代器

3.unordered_map的元素访问

4. unordered_map的查询

5. unordered_map的修改操作

6. unordered_map的桶操作unordere_set在线文档说明

1.2 unordered_set

1.3 unordered_map与map查找性能比较

二、底层结构

2.1 哈希概念

2.2 哈希冲突

2.3 哈希函数

2.4 哈希冲突解决

2.4.1 闭散列

线性探测

线性探测的实现

线性探测优缺点

2.4.2 开散列

1.概念

2.开散列实现

3.开散列增容

4.非整形key的开散列

三、模拟实现unordered系列

3.1 哈希表的改造

        1.模板参数列表的改造

        2.增加迭代器操作

        3、增加通过key获取value操作

3.2 模拟实现unordered_map

3.3 模拟实现unordered_set


一、unordered系列关联式容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到$log_2N$,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。

1.1 unordered_map

unordere_map在线文档说明

1.1.1 unordered_map

1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
2. 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此
键关联。键和映射值的类型可能不同。
3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内
找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问
value。
6. 它的迭代器至少是前向迭代器。

1.1.2 unordered_map接口说明


1. unordered_map的容量
函数声明功能介绍
bool empty() const检测unordered_map是否为空
size_t size() const获取unordered_map的有效元素个数
2. unordered_map的迭代器
函数声明功能介绍
begin返回unordered_map第一个元素的迭代器
end返回unordered_map最后一个元素下一个位置的迭代器
cbegin返回unordered_map第一个元素的const迭代器
cend返回unordered_map最后一个元素下一个位置的const迭代器
3.unordered_map的元素访问
函数声明功能介绍
operator[]返回与key对应的value,没有一个默认值

注意:该函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶
中插入,如果key不在哈希桶中,插入成功,返回V(),插入失败,说明key已经在哈希桶中,
将key对应的value返回。

4. unordered_map的查询
函数声明功能介绍
iterator find(const K& key)返回key在哈希桶中的位置
size_t count(const K& key)返回哈希桶中关键码为key的键值对的个数

注意:unordered_map中key是不能重复的,因此count函数的返回值最大为1

5. unordered_map的修改操作
函数声明功能介绍
insert向容器中插入键值对
erase删除容器中的键值对
void clear()清空容器中有效元素个数
void swap(unordered_map&)交换两个容器中的
6. unordered_map的桶操作unordere_set在线文档说明
size_t bucket_count()const返回哈希桶中桶的总个数
size_t bucket_size(size_t n)const返回n号桶中有效元素的总个数
size_t bucket(const K& key)返回元素key所在的桶号

1.2 unordered_set

unordere_set在线文档说明

1.3 unordered_map与map查找性能比较

  1. 以下是使用C++的unordered_map和map进行性能比较的示例代码:
  2. cpp
  3. #include <iostream>
  4. #include <map>
  5. #include <unordered_map>
  6. #include <chrono>
  7. #include <string>
  8. int main() {
  9. std::map<int, std::string> map_container;
  10. std::unordered_map<int, std::string> unordered_map_container;
  11. // 填充数据
  12. for (int i = 0; i < 100000; i++) {
  13. map_container[i] = "element " + std::to_string(i);
  14. unordered_map_container[i] = "element " + std::to_string(i);
  15. }
  16. // 使用map进行查找
  17. auto start_time = std::chrono::high_resolution_clock::now();
  18. for (int i = 0; i < 100000; i++) {
  19. if (map_container.find(i) == map_container.end()) {
  20. std::cout << "Element not found" << std::endl;
  21. }
  22. }
  23. auto end_time = std::chrono::high_resolution_clock::now();
  24. auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
  25. std::cout << "Time taken by map: " << duration.count() << " microseconds" << std::endl;
  26. // 使用unordered_map进行查找
  27. start_time = std::chrono::high_resolution_clock::now();
  28. for (int i = 0; i < 100000; i++) {
  29. if (unordered_map_container.find(i) == unordered_map_container.end()) {
  30. std::cout << "Element not found" << std::endl;
  31. }
  32. }
  33. end_time = std::chrono::high_resolution_clock::now();
  34. duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
  35. std::cout << "Time taken by unordered_map: " << duration.count() << " microseconds" << std::endl;
  36. return 0;
  37. }
  38. //在此示例中,我们创建了两个容器,一个使用std::map,另一个使用std::unordered_map。然后,我们向每个容器添加100,000个元素,并使用find()函数在每个容器中查找每个元素。最后,我们使用std::chrono库计算两个操作的时间,并输出结果。
  39. //通常情况下,unordered_map比map更快,因为unordered_map使用哈希表实现,其查找操作的平均时间复杂度为O(1),而map使用红黑树实现,其查找操作的平均时间复杂度为O(log n)。但是,在某些情况下,unordered_map可能会比map慢,例如当元素数量较少时,或者当元素的大小相差很大时。此外,unordered_map需要更多的内存空间来存储哈希表中的元素。因此,在选择使用unordered_map还是map时,需要考虑数据的特点和性能需求。

二、底层结构

unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构

2.1 哈希概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素
时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即
O(log_2 N),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立
一映射的关系
,那么在查找时通过该函数可以很快找到该元素!

当插入元素:根据关键码key,用哈希函数计算出元素的存储位置进行存放


当搜索元素:同样通过关键码映射存储位置,在比较判断关键码是否一致

2.2 哈希冲突

哈希冲突:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突
或哈希碰撞。我们可以预想的是哈希冲突无法完全杜绝,只能用更高效的哈希函数算法来减少冲突。下面来谈谈常见的哈希函数!

2.3 哈希函数

1. 直接定址法--(常用)

取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
优点:简单、均匀
缺点:需要事先知道关键字的分布情况
使用场景:适合查找比较小且连续的情况
面试题:字符串中第一个只出现一次字符


2. 除留余数法--(常用) 

设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,
按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址


3. 平方取中法--(了解)

假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;
再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况


4. 折叠法--(了解)

折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这
几部分叠加求和,并按散列表表长,取后几位作为散列地址。
折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况


5. 随机数法--(了解) 

选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中
random为随机数函数。
通常应用于关键字长度不等时采用此法


6. 数学分析法--(了解)

设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定
相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只
有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散
列地址。例如:


假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前7位都是 相同
的,那么我们可以选择后面的四位作为散列地址,如果这样的抽取工作还容易出现 冲突,还
可以对抽取出来的数字进行反转(如1234改成4321)、右环位移(如1234改成4123)、左环移
位、前两数与后两数叠加(如1234改成12+34=46)等方法。
数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的
若干位分布较均匀的情况

注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突


2.4 哈希冲突解决

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

2.4.1 闭散列

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

        1.线性探测

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


插入:

1.通过哈希函数将关键字映射到哈希表中位置

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

删除:

采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素
会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影
响。因此线性探测采用标记的伪删除法来删除一个元素。

  1. // 哈希表每个空间给个标记
  2. // EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
  3. enum State{EMPTY, EXIST, DELETE};
线性探测的实现
  1. // 哈希表每个空间给个标记
  2. // EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
  3. enum State{EMPTY, EXIST, DELETE};
  4. // 注意:假如实现的哈希表中元素唯一,即key相同的元素不再进行插入
  5. // 为了实现简单,此哈希表中我们将比较直接与元素绑定在一起
  6. template<class K, class V>
  7. class HashTable
  8. {
  9. struct Elem
  10. {
  11. pair<K, V> _val;
  12. State _state;
  13. };
  14. public:
  15. HashTable(size_t capacity = 3)
  16. : _ht(capacity), _size(0)
  17. {
  18. for (size_t i = 0; i < capacity; ++i)
  19. _ht[i]._state = EMPTY;
  20. }
  21. bool Insert(const pair<K, V>& val)
  22. {
  23. // 检测哈希表底层空间是否充足
  24. // _CheckCapacity();
  25. size_t hashAddr = HashFunc(key);
  26. // size_t startAddr = hashAddr;
  27. while (_ht[hashAddr]._state != EMPTY)
  28. {
  29. if (_ht[hashAddr]._state == EXIST && _ht[hashAddr]._val.first
  30. == key)
  31. return false;
  32. hashAddr++;
  33. if (hashAddr == _ht.capacity())
  34. hashAddr = 0;
  35. /*
  36. // 转一圈也没有找到,注意:动态哈希表,该种情况可以不用考虑,哈希表中元
  37. 素个数到达一定的数量,哈希冲突概率会增大,需要扩容来降低哈希冲突,因此哈希表中元素是
  38. 不会存满的
  39. if(hashAddr == startAddr)
  40. return false;
  41. */
  42. }
  43. // 插入元素
  44. _ht[hashAddr]._state = EXIST;
  45. _ht[hashAddr]._val = val;
  46. _size++;
  47. return true;
  48. }
  49. int Find(const K& key)
  50. {
  51. size_t hashAddr = HashFunc(key);
  52. while (_ht[hashAddr]._state != EMPTY)
  53. {
  54. if (_ht[hashAddr]._state == EXIST && _ht[hashAddr]._val.first
  55. == key)
  56. return hashAddr;
  57. hashAddr++;
  58. }
  59. return hashAddr;
  60. }
  61. bool Erase(const K& key)
  62. {
  63. int index = Find(key);
  64. if (-1 != index)
  65. {
  66. _ht[index]._state = DELETE;
  67. _size++;
  68. return true;
  69. }
  70. return false;
  71. }
  72. size_t Size()const;
  73. bool Empty() const;
  74. void Swap(HashTable<K, V, HF>& ht);
  75. private:
  76. size_t HashFunc(const K& key)
  77. {
  78. return key % _ht.capacity();
  79. }
  80. private:
  81. vector<Elem> _ht;
  82. size_t _size;
  83. };

 思考:哈希表什么情况下进行扩容?如何扩容?

 扩容代码(思路联想现代拷贝构造):

  1. void CheckCapacity()
  2. {
  3. if (_size * 10 / _ht.capacity() >= 7)
  4. {
  5. HashTable<K, V, HF> newHt(GetNextPrime(ht.capacity));
  6. for (size_t i = 0; i < _ht.capacity(); ++i)
  7. {
  8. if (_ht[i]._state == EXIST)
  9. newHt.Insert(_ht[i]._val);
  10. }
  11. Swap(newHt);
  12. }
  13. }

线性探测优缺点

线性探测优点:实现非常简单,
线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同
关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降
低。


2.4.2 开散列

1.概念

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

 从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素


2.开散列实现
  1. template<class V>
  2. struct HashBucketNode
  3. {
  4. HashBucketNode(const V& data)
  5. : _pNext(nullptr), _data(data)
  6. {}
  7. HashBucketNode<V>* _pNext;
  8. V _data;
  9. };
  10. // 本文所实现的哈希桶中key是唯一的
  11. template<class V>
  12. class HashBucket
  13. {
  14. typedef HashBucketNode<V> Node;
  15. typedef Node* PNode;
  16. public:
  17. HashBucket(size_t capacity = 3) : _size(0)
  18. {
  19. _ht.resize(GetNextPrime(capacity), nullptr);
  20. }
  21. // 哈希桶中的元素不能重复
  22. PNode* Insert(const V& data)
  23. {
  24. // 确认是否需要扩容。。。
  25. // _CheckCapacity();
  26. // 1. 计算元素所在的桶号
  27. size_t bucketNo = HashFunc(data);
  28. // 2. 检测该元素是否在桶中
  29. PNode pCur = _ht[bucketNo];
  30. while (pCur)
  31. {
  32. if (pCur->_data == data)
  33. return pCur;
  34. pCur = pCur->_pNext;
  35. }
  36. // 3. 插入新元素
  37. pCur = new Node(data);
  38. pCur->_pNext = _ht[bucketNo];
  39. _ht[bucketNo] = pCur;
  40. _size++;
  41. return pCur;
  42. }
  43. // 删除哈希桶中为data的元素(data不会重复),返回删除元素的下一个节点
  44. PNode* Erase(const V& data)
  45. {
  46. size_t bucketNo = HashFunc(data);
  47. PNode pCur = _ht[bucketNo];
  48. PNode pPrev = nullptr, pRet = nullptr;
  49. while (pCur)
  50. {
  51. if (pCur->_data == data)
  52. {
  53. if (pCur == _ht[bucketNo])
  54. _ht[bucketNo] = pCur->_pNext;
  55. else
  56. pPrev->_pNext = pCur->_pNext;
  57. pRet = pCur->_pNext;
  58. delete pCur;
  59. _size--;
  60. return pRet;
  61. }
  62. }
  63. return nullptr;
  64. }
  65. PNode* Find(const V& data);
  66. size_t Size()const;
  67. bool Empty()const;
  68. void Clear();
  69. bool BucketCount()const;
  70. void Swap(HashBucket<V, HF>& ht;
  71. ~HashBucket();
  72. private:
  73. size_t HashFunc(const V& data)
  74. {
  75. return data % _ht.capacity();
  76. }
  77. private:
  78. vector<PNode*> _ht;
  79. size_t _size; // 哈希表中有效元素的个数
  80. };

3.开散列增容

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

  1. void _CheckCapacity()
  2. {
  3. size_t bucketCount = BucketCount();
  4. if (_size == bucketCount)
  5. {
  6. vector<Node*> newTable;
  7. newTable.resize(_table.size() * 2, nullptr);
  8. //移动结点
  9. for (auto cur : _table)
  10. {
  11. while (cur)
  12. {
  13. Node* next = cur->_next;
  14. size_t hashi = cur->_data % newTable.size();
  15. //头插入新链表
  16. cur->_next = newTable[hashi];
  17. newTable[hashi] = cur;
  18. cur = next;
  19. }
  20. }
  21. _table.clear();
  22. //交换数组,自动析构原数组
  23. swap(newTable, _table);
  24. }
  25. }

4.非整形key的开散列

1.如何将类型变为size_t类型 

字符串哈希算法!!!

  1. //如果是整形的相似类型,使用强转
  2. template <class K>
  3. class Hash{
  4. public:
  5. size_t operator()(const K& key)
  6. {
  7. return (size_t)key;
  8. }
  9. };
  10. //如果是字符串,模板特化,我们需要自己实现仿函数!
  11. template<>
  12. class Hash<string>
  13. {
  14. public:
  15. size_t operator()(const string& key)
  16. {
  17. size_t keyi = 0;
  18. for (int i = 0;i < key.size();i++)
  19. {
  20. keyi += key[i];
  21. }
  22. return keyi;
  23. }
  24. };

2.开辟空间开素数个效果好

  1. //素数表
  2. inline unsigned long __stl_next_prime(unsigned long n)
  3. {
  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. for (int i = 0; i < __stl_num_primes; ++i)
  15. {
  16. if (__stl_prime_list[i] > n)
  17. {
  18. return __stl_prime_list[i];
  19. }
  20. }
  21. return __stl_prime_list[__stl_num_primes - 1];
  22. }


三、模拟实现unordered系列

3.1 哈希表的改造

        1.模板参数列表的改造

  1. // K:关键码类型
  2. // V: 不同容器V的类型不同,如果是unordered_map,V代表一个键值对,如果是
  3. unordered_set,V 为 K
  4. // KeyOfValue: 因为V的类型不同,通过value取key的方式就不同,详细见
  5. unordered_map/set的实现
  6. // HF: 哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将Key转换为整形数字才能
  7. 取模
  8. template<class K, class V, class KeyOfValue, class HF = DefHashF<T> >
  9. class HashBucket;

        2.增加迭代器操作

  1. //哈希桶中与迭代器相互引用双方,迭代器用到桶需要首先申明一下!
  2. //前置申明模板类,模板也一定要带上!!!
  3. template<class K, class T, class KeyofT, class HashFunc>
  4. class HashTable;
  5. template<class K, class T, class ref, class ptr, class HashFunc, class KeyofT>
  6. struct Hash_iterator
  7. {
  8. typedef HashNode<T> Node;
  9. typedef Hash_iterator<K, T, ref, ptr, HashFunc, KeyofT> Self;
  10. typedef HashTable<K, T, KeyofT, HashFunc> Ht;
  11. typedef Hash_iterator<K, T, T&, T*, HashFunc, KeyofT> iterator;//申明一下,与std区别
  12. Node* pnode;
  13. Ht* _ht;//传哈希表是因为++中需要查找桶!
  14. Hash_iterator(Node* p, Ht* ht)
  15. :pnode(p)
  16. , _ht(ht)
  17. {}
  18. Hash_iterator(const iterator& it)
  19. :pnode(it.pnode)
  20. ,_ht(it._ht)
  21. {}
  22. //-->
  23. Self& operator++()
  24. {
  25. //判断是否为该桶链表的尾 1.是 -> 判断下一个桶是否为空 2.否 直接等于next
  26. if (pnode->_next)
  27. pnode = pnode->_next;
  28. else {
  29. HashFunc Hash;
  30. KeyofT getK;
  31. size_t hashi = Hash(getK(pnode->_data)) % _ht->_table.size()+1;
  32. while (hashi < _ht->_table.size())
  33. {
  34. //判断桶是否为空桶
  35. if (_ht->_table[hashi])
  36. {
  37. pnode = _ht->_table[hashi];
  38. break;
  39. }
  40. else ++hashi;
  41. }
  42. //后面没有桶了
  43. if (hashi == _ht->_table.size())
  44. pnode = nullptr;
  45. }
  46. return *this;
  47. }
  48. ref operator*()
  49. {
  50. return pnode->_data;
  51. }
  52. ptr operator->()
  53. {
  54. return &pnode->_data;
  55. }
  56. bool operator==(const Self& p)const
  57. {
  58. return pnode == p.pnode;
  59. }
  60. bool operator!=(const Self& p)const
  61. {
  62. return pnode != p.pnode;
  63. }
  64. };

        3、增加通过key获取value操作

  1. template<class K,class T,class KeyofT,class HashFunc>
  2. class HashTable
  3. {
  4. public:
  5. typedef HashNode<T> Node; //结点
  6. template<class K, class T, class ref,class ptr, class HushFunc,class KeyofT>
  7. friend struct Hash_iterator;
  8. template<class K, class T, class ref, class ptr, class HushFunc, class KeyofT>
  9. friend struct Hash_const_iterator;
  10. // Key Val& Val* 类型->整数映射 Val中取Key
  11. typedef Hash_iterator<K, T,T&, T*,HashFunc,KeyofT> iterator;
  12. typedef Hash_const_iterator<K, T, const T&, const T*, HashFunc, KeyofT> const_iterator;
  13. HashTable()
  14. {
  15. //__stl_next_prime(0)
  16. _table.resize(5, nullptr);
  17. _n = 0;
  18. }
  19. pair<iterator,bool> insert(const T& data)
  20. {
  21. HashFunc Hash;
  22. KeyofT getK;
  23. iterator it = Find(getK(data));
  24. if (it != end())
  25. return make_pair(it, false);
  26. //判断负载因子-->扩容?
  27. if (_n / _table.size() == 1)
  28. {
  29. vector<Node*> newTable;
  30. //__stl_next_prime(_table.size() * 2
  31. newTable.resize(_table.size()*2,nullptr);
  32. //移动结点
  33. for (auto cur : _table)
  34. {
  35. while (cur)
  36. {
  37. Node* next = cur->_next;
  38. size_t hashi = Hash(getK(cur->_data)) % newTable.size();
  39. //头插入新链表
  40. cur->_next = newTable[hashi];
  41. newTable[hashi] = cur;
  42. cur = next;
  43. }
  44. }
  45. _table.clear();
  46. swap(newTable, _table);
  47. }
  48. size_t hashi = Hash(getK(data)) % _table.size();
  49. Node* newnode = new Node(data);
  50. if (_table[hashi])
  51. {
  52. newnode->_next = _table[hashi];
  53. _table[hashi] = newnode;
  54. }
  55. else {
  56. _table[hashi] = newnode;
  57. }
  58. ++_n;
  59. return make_pair(iterator(newnode,this),true);
  60. }
  61. iterator Find(const K& key)
  62. {
  63. HashFunc Hash;
  64. KeyofT getK;
  65. size_t hashi = Hash(key) % _table.size();
  66. Node* cur = _table[hashi];
  67. Node* next=nullptr;
  68. while (cur)
  69. {
  70. next = cur->_next;
  71. if (getK(cur->_data) == key)
  72. return iterator(cur,this);
  73. cur = next;
  74. }
  75. return end();
  76. }
  77. bool Erase(const K& key)
  78. {
  79. HashFunc Hash;
  80. KeyofT getK;
  81. size_t hashi = Hash(key) % _table.size();
  82. Node* cur = _table[hashi];
  83. Node* prev = nullptr;
  84. while (cur)
  85. {
  86. if (getK(cur->_data) == key)
  87. {
  88. if (cur == _table[hashi])
  89. _table[hashi] = cur->_next;
  90. else prev->_next = cur->_next;
  91. delete cur;
  92. --_n;
  93. return true;
  94. }
  95. else {
  96. prev = cur;
  97. cur = cur->_next;
  98. }
  99. }
  100. return false;
  101. }
  102. iterator begin()
  103. {
  104. for (int i = 0;i < _table.size();i++)
  105. {
  106. if (_table[i])
  107. return iterator(_table[i], this);//!!! this 太妙了
  108. }
  109. return iterator(nullptr,this);
  110. }
  111. const_iterator begin()const
  112. {
  113. for (int i = 0;i < _table.size();i++)
  114. {
  115. if (_table[i])
  116. return const_iterator(_table[i], this);//!!! this 太妙了
  117. }
  118. return const_iterator(nullptr, this);
  119. }
  120. iterator end()
  121. {
  122. return iterator(nullptr, this);
  123. }
  124. const_iterator end()const
  125. {
  126. return const_iterator(nullptr, this);
  127. }
  128. ~HashTable()
  129. {
  130. Node* cur = nullptr;
  131. Node* next = nullptr;
  132. for (int i = 0;i < _table.size();i++)
  133. {
  134. cur = _table[i];
  135. while (cur)
  136. {
  137. next = cur->_next;
  138. delete cur;
  139. cur = next;
  140. }
  141. }
  142. }
  143. private:
  144. vector<Node*> _table;
  145. size_t _n;
  146. };

3.2 模拟实现unordered_map

  1. namespace wyz
  2. {
  3. template<class K, class V>
  4. class unorder_map
  5. {
  6. public:
  7. struct GetKey
  8. {
  9. const K& operator()(const pair<K, V>& kv)
  10. {
  11. return kv.first;
  12. }
  13. };
  14. typedef HashTable<K, pair<K, V>, GetKey, Hash<K>> hash;
  15. typedef typename hash::iterator iterator;
  16. typedef typename hash::const_iterator const_iterator;
  17. iterator begin()
  18. {
  19. return _ht.begin();
  20. }
  21. iterator end()
  22. {
  23. return _ht.end();
  24. }
  25. const_iterator begin()const
  26. {
  27. return _ht.begin();
  28. }
  29. const_iterator end()const
  30. {
  31. return _ht.end();
  32. }
  33. pair<iterator, bool> insert(const pair<K, V>& val)
  34. {
  35. return _ht.insert(val);
  36. }
  37. iterator Find(const K& val)
  38. {
  39. return _ht.Find(val);
  40. }
  41. bool Erase(const K& val)
  42. {
  43. return _ht.Erase();
  44. }
  45. V& operator[](const K& key)
  46. {
  47. pair<iterator, bool> ret = insert(make_pair(key, V()));
  48. return ret.first->second;
  49. }
  50. const V& operator[](const K& key)const
  51. {
  52. pair<iterator, bool> ret = insert(make_pair(key, V()));
  53. return ret.first->second;
  54. }
  55. private:
  56. hash _ht;//底层是哈希表
  57. };
  58. }

测试代码:

  1. void Test_unorder_map()
  2. {
  3. string arr[] = { "苹果", "西瓜", "香蕉", "草莓", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
  4. wyz::unorder_map<string, int> countMap;
  5. for (auto e : arr)
  6. {
  7. countMap[e]++;
  8. }
  9. wyz::unorder_map<string, int>::iterator it = countMap.begin();
  10. while (it != countMap.end())
  11. {
  12. cout << it->first << ":" << it->second << endl;
  13. ++it;
  14. }
  15. }

测试map const 迭代器:

  1. void Test_unordermap()
  2. {
  3. int arr[] = { 9,2,1,10,3,56,78,28,30,29 };
  4. wyz::unorder_map<int, int> countMap;
  5. for (auto e : arr)
  6. {
  7. countMap[e]++;
  8. }
  9. wyz::unorder_map<int, int>::const_iterator it = countMap.begin();
  10. while (it != countMap.end())
  11. {
  12. ++(it->second);
  13. cout << it->first << ":" << it->second << endl;
  14. ++it;
  15. }
  16. }


3.3 模拟实现unordered_set

  1. template <class K>
  2. class unorder_set
  3. {
  4. public:
  5. struct GetKey
  6. {
  7. const K& operator()(const K& val)
  8. {
  9. return val;
  10. }
  11. };
  12. typedef HashTable<K, K, GetKey,Hash<K>> hash;
  13. typedef typename hash::const_iterator iterator;
  14. typedef typename hash::const_iterator const_iterator;
  15. iterator begin()
  16. {
  17. return _ht.begin();
  18. }
  19. iterator end()
  20. {
  21. return _ht.end();
  22. }
  23. const_iterator begin()const
  24. {
  25. return _ht.begin();
  26. }
  27. const_iterator end()const
  28. {
  29. return _ht.end();
  30. }
  31. pair<iterator, bool> insert(const K& k)
  32. {
  33. //注意ret类型中的迭代器是普通迭代器
  34. pair<typename hash::iterator, bool> ret = _ht.insert(k);
  35. //我们这里需要用到普通迭代器拷贝构造const迭代器!!!
  36. return make_pair(iterator(ret.first), ret.second);
  37. }
  38. bool Find(const K& val)
  39. {
  40. return _ht.Find(val);
  41. }
  42. bool Erase(const K& val)
  43. {
  44. return _ht.Erase();
  45. }
  46. private:
  47. hash _ht;
  48. };

测试代码:

  1. void Test_unorder_set()
  2. {
  3. wyz::unorder_set<int> myset;
  4. int arr[10] = { 9,8,5,3,2,1,6,7,4,11 };
  5. for (auto e : arr)
  6. {
  7. myset.insert(e);
  8. }
  9. wyz::unorder_set<int>::iterator it = myset.begin();
  10. while (it != myset.end())
  11. {
  12. //++(*it);
  13. cout << *it << ' ';
  14. ++it;
  15. }
  16. }

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

闽ICP备14008679号