当前位置:   article > 正文

C++ 哈希表实现

C++ 哈希表实现

目录

前言

一、什么是哈希表

二、直接定值法

三、开放定值法(闭散列)

1.开放定制法定义

2.开放定制法实现

2.1类的参数

2.2类的构造

2.3查找实现

2.4插入实现

2.5删除实现

2.6string做key

四、哈希桶(开散列)

1.开散列概念

2.开散列实现

2.1类的参数

2.2类的构造函数 

2.3查找实现

2.4插入实现

2.5 删除实现

2.6string做key

五、哈希桶与set和unordered_set的对比 


前言

什么叫做哈希表

相信大家在很多地方都听过哈希表这三个字。在之前,我看到java之父余胜军装小白面试的时候,对哈希表那是侃侃而谈,非常的羡慕他对哈希表的理解,在学习哈希表过后,我发现他的难度还不一定比得上红黑树,那让我们今天来揭开哈希表的神秘面纱。

一、什么是哈希表

哈希表跟set和map结构类似,库函数如果是unordered_set,那就是Key模型库函数如果是unordered_map,那就是Key,Value模型。

表我们很容易理解,那么什么是哈希?

关键值与存储位置,建立一个关联关系,通过收到的Key,对Key进行处理,映射成一个不超过数组索引特殊值,存放在数组中。因此,哈希表也被称做散列表

这样一来,会使得我们查找效率近乎O(1)。

二、直接定值法

1.直接定制法,值和位置关系唯一,每个值都存放在唯一位置。

比如我们统计字符串中小写字母的个数,那么我们仅仅需要开辟26个空间的数组即可,对每一个字母都存放在对应位置(如a存放在索引0处,b存放索引1......z存放索引25)。

但这样也会存在一些问题。

比如数据十分分散的情况下,你需要先找出数组中的最大值和最小值,再来开辟空间存放数据。如下数据,就得开辟99999-2+1个空间,会造成空间的极度浪费

因此,直接定制法在特殊情况下非常好用,但是普遍性不够。不推荐日常使用.

三、开放定值法(闭散列)

1.开放定制法定义

同样是这串数据,我们可以通过哈希函数让key跟位置建立映射关系

如,函数为    hashi = key % len

比如len==10时,2%10 = 2,因此2存放在索引为2的空间,99%10 = 9  ,99存放在索引为9的空间,以此类推。

这样我们就可以在长度为len个范围内,去找到该值的索引,并存放数据了。

但是这又会引发一个问题:哈希冲突(不同的值映射到相同的位置上去)

不管你所给到的函数是什么样子的,都只可能尽量减少哈希冲突,不可完全避免哈希冲突,因此当发生哈希冲突的时候,我们应该如何处理呢?

1.线性探测法(hashi + i (i>=0))

        当你想存放的位置存在数据时,就存放在当前位置的下一个,如果下一个位置也有数据,就存放在下一个的下一个,直到没有数据为止。

2.二次探测法 (hashi + i^{2}(i>=0))

        当你想存放的位置存在数据时,存放在i^{2}的位置,i为1就是hashi+1,i为2就是hashi+4,因此类推,直到没有数据为止。

后续还有n次探测等等

2.开放定制法实现

2.1类的参数

首先定义一个枚举代表当前位置的状态,状态有   空、存在、删除。为什么要有这三个,而不是存在和不存在。主要是为了如下情况

当我们依次插入23,13,33,34。线性探测存值如下所示。

当我们删除13时。 如果只有存在和不存在,那么我们后续就找不到33和34了,因为找到空(不存在)就会停止。

什么?你说不停止继续找?如果不停止,那我搞个哈希表和线性表有什么区别,那时间复杂度还是O(1)吗?

因此我们需要   空、存在、删除三个状态,保证我们遇到删除状态后,能够继续往后寻找。哈希表_tables存放的内容为HashDate,这里使用库里面的vector帮助我们完成哈希表。同时还需要一个长度_n,代表存放了多少个值如果_n / _tables.size() 的结果达到我们设定的某个值(比如百分之70),这个值我们称做负载因子,达到这个限制值后,如果再插入结点会发生很多的哈希冲突,因此我们需要扩容。

正因为负载因子的存在,就算发生了哈希冲突,我们也能较容易的找到下一个该存放的位置。

  1. enum Status
  2. {
  3. EMPTY,
  4. EXIST,
  5. DELETE,
  6. };
  7. template<class K, class V>
  8. struct HashDate
  9. {
  10. pair<K, V> _kv;
  11. Status _s;
  12. };
  13. template<class K,class V>
  14. class HashTable
  15. {
  16. private:
  17. vector<HashDate<K,V>> _tables;
  18. size_t _n;
  19. };

2.2类的构造

 构造很简单,我们想让哈希表初始化的时候,帮我们开辟好变量  vector<HashDate<K,V>> _tables  的空间,那么我们调用resize()函数帮助我们开辟空间即可。

  1. HashTable()
  2. {
  3. _tables.resize(10);
  4. }

2.3查找实现

首先,传入的参数毫无疑问,肯定是key,通过key值判断该值在不在哈希表中。

然后算出key经过哈希函数转化后的值,记为hashi。这个hashi就是我们映射在_tables表里的索引。

我们开始判断是否为空,为空就证明没找到该数据。

不为空,就判断该位置存放的_kv模型的first是否与key值相等,同时判断条件还得加上该位置不能为删除。(为什么要判断是否为删除,因为我们删除函数先找到后,就将他的状态设置为DELETE,并没有重置数据,我们也不知道该将数据重置为什么

找到了,就范围该位置的地址,不然就线性探测,++hashi往后寻找,代码hashi %= _tables.size();是为了防止越界,发生越界就会回到表的索引0处。直到状态为空结束。

代码部分如下 

  1. HashDate<K,V>* Find(const K& key)
  2. {
  3. size_t hashi = key % _tables.size();
  4. while (_tables[hashi]._s != EMPTY)
  5. {
  6. if (_tables[hashi]._s != DELETE && _tables[hashi]._kv.first == key)
  7. return &_tables[hashi];
  8. hashi++;
  9. hashi %= _tables.size();
  10. }
  11. return nullptr;
  12. }

2.4插入实现

插入的参数是pair模型。

先查看在不在,在就返回false。代表已存在,不能插入了。

依然是先算哈希值,存在就找下一个,为空或者为DELETE我们都选择插入,将状态修改为存在,++_n。

  1. bool Insert(const pair<K,V>& kv)
  2. {
  3. if (Find(kv.first))
  4. {
  5. return false;
  6. }
  7. size_t hashi = kv.first % _tables.size();
  8. while (_tables[hashi]._s == EXIST)
  9. {
  10. hashi++;
  11. hashi %= _tables.size();
  12. }
  13. _tables[hashi]._kv = kv;
  14. _tables[hashi]._s = EXIST;
  15. ++_n;
  16. return true;
  17. }

我们还差一些东西,比如扩容,当 _n / _tables.size()大于等于负载因子时,需要扩容,代码部分添加到Find函数后面,因为找到该值就会返回,不需要扩容。

        判断条件就跟负载因子有关,_n / _tables.size()就是我们的负载因子,由于这两个都是整数,因此我们表达式的前后都乘了10。当前的负载因子为0.7。

        这里我们选择用了更方便的写法,建立了一个新的哈希表newht,将size()开辟为之前的两倍,遍历复用插入,当我们遍历插入完毕后,新哈希表newht的_tables就是我们现在的_tables想要的内容,由于_tables是vector,因此我们调用一下swap就好了。

  1. if (_n*10 / _tables.size() >= 7)
  2. {
  3. int newcapacity = _tables.size() * 2;
  4. HashTable<K, V> newHT;
  5. newHT._tables.resize(newcapacity);
  6. for (size_t i = 0; i < _tables.size(); i++)
  7. {
  8. if(_tables[i]._s == EXIST)
  9. {
  10. newHT.Insert(_tables[i]._kv);
  11. }
  12. }
  13. _tables.swap(newHT._tables);
  14. }

我们运行测试一下,没问题。(打印代码不用管,只是方便我们查看,后面会给出,)

2.5删除实现

使用代码复用,通过Find函数找到该key存放的位置。

找到了就将状态置为DELETE,--_n,返回true,没找到就返回false。

  1. bool Erase(const K& key)
  2. {
  3. HashDate<K, V>* ret = Find(key);
  4. if (ret)
  5. {
  6. ret._s = DELETE;
  7. --_n;
  8. return true;
  9. }
  10. return false;
  11. }

测试一下 

2.6string做key

之前我们分析的都是整形来做key,自然而然就可以用除法去找到索引,那么string类型呢?在生活中string类型做key可是非常常见的。

这里我们选择添加一个模板参数,并且是缺省的,这个参数的目的就是利用仿函数将支持强转size_t类型的key转成size_t类型,对于其他类型,我们也可以通过一些方法转成size_t类型。

我们针对string类型使用了模板特化,当发现key为string时就会走该函数,因为特化是现成的代码,而上面那个还需要去推演出来类型。

 至于为什么我们代码的sum要*31,这是防止哈希冲突。

如果不处理,如“abc”和“acb”还有“bbb”就会发生哈希冲突,具体可以看大佬的文章字符串Hash函数

  1. template<class K>
  2. struct HashFunc
  3. {
  4. size_t operator()(const K& key)
  5. {
  6. return (size_t)key;
  7. }
  8. };
  9. template<>
  10. struct HashFunc<string>
  11. {
  12. size_t operator()(const string& s)
  13. {
  14. size_t sum = 0;
  15. for (auto& e : s)
  16. {
  17. sum *= 31;
  18. sum += e;
  19. }
  20. return sum;
  21. }
  22. };
  23. template<class K,class V ,class Hash = HashFunc<K>>
  24. class HashTable
  25. {
  26. //相关代码
  27. };

修改一下,在我们所有要对key变成整数的地方都套上一层,这里只套了一个,明白意思就好。 

测试一下代码,由于我们特化了string,并且HashTable第三个模板参数为缺省参数,因此这里可以不传参如果你的key是自定义类型,那么你需要自己写哈希函数并传参

四、哈希桶(开散列)

1.开散列概念

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

开放定制法如下图所示的图片

同样的数据,放在哈希桶如下,就像拉链一样,一个挂着一个。

那么哈希桶的优势在哪里呢?我们可以发现,当我们去寻找4和14这种数据,似乎区别不大。

但是一旦我们寻找54,差距就大了,开放定制法会一直找到9的后面才结束,而哈希桶只需要将当前挂载的链表找完就可以了。

再比如我们寻找9,开放定制法会一直找到9才结束,而哈希桶一下就找到了。

Java JDK1.8版本中,如果某个链表长度超过了8,会选择挂载红黑树,增加效率,C++还没有这样做,至于为什么,我们在实现中会测试,大家一起往下看吧。

2.开散列实现

2.1类的参数

我们的_tables里面存放的是结点,为什么不用库里面的list呢?

首先,我们只需要单向链表即可,要用也是用库里面的forward_list(单向链表),其次,使用库里面的链表会让我们后续封装迭代器更加麻烦,最后,使用自己手写结点也很简单,因此选择自己写一个。

HashNode存放_kv和_next,并且写出了构造函数,方便我们new结点。

  1. template<class K, class V>
  2. struct HashNode
  3. {
  4. pair<K, V> _kv;
  5. HashNode<K, V>* _next;
  6. HashNode(const pair<K,V>& kv)
  7. :_kv(kv)
  8. ,_next(nullptr)
  9. {}
  10. };
  11. template<class K, class V>
  12. class HashTable
  13. {
  14. typedef HashNode<K, V> Node;
  15. public:
  16. HashTable()
  17. {
  18. _tables.resize(10);
  19. }
  20. private:
  21. vector<Node*> _tables;
  22. size_t _n;
  23. };

2.2类的构造函数 

1.默认构造给_tables开辟十个空间。

2.拷贝构造我们也要自己写,需要深拷贝,选择头插,老生常谈的东西了。

3.operator= 赋值拷贝,传参选择不传&,这样就会发生拷贝构造,同时不传const,因为我们析构后会删除。再调用swap函数即可,将别人的交换一下,出了作用域自动调用析构函数。

4.析构函数遍历加一个个节点删除即可。

  1. HashTable()
  2. {
  3. _tables.resize(10);
  4. }
  5. HashTable(const HashTable<K,V>& ht)
  6. {
  7. _tables.resize(ht._tables.size(), nullptr);
  8. for (size_t i = 0; i < ht._tables.size(); i++)
  9. {
  10. Node* cur = ht._tables[i];
  11. while (cur)
  12. {
  13. Node* newnode = new Node(cur->_kv);
  14. if (_tables[i]==nullptr)
  15. {
  16. _tables[i] = newnode;
  17. }
  18. else
  19. {
  20. newnode->_next = _tables[i];
  21. _tables[i] = newnode;
  22. }
  23. cur = cur->_next;
  24. }
  25. }
  26. }
  27. HashTable<K, V>& operator=(HashTable<K, V> ht)
  28. {
  29. _tables.swap(ht._tables);
  30. swap(_n, ht._n);
  31. return *this;
  32. }
  33. ~HashTable()
  34. {
  35. for (size_t i = 0; i < _tables.size(); i++)
  36. {
  37. Node* cur = _tables[i];
  38. while (cur)
  39. {
  40. Node* next = cur->_next;
  41. delete cur;
  42. cur = next;
  43. }
  44. _tables[i] = nullptr;
  45. }
  46. }

2.3查找实现

一样先计算哈希值hashi,通过hashi去找到存放在_tables里面的结点,结点不为空,就证明该节点存放了链表,便开始遍历链表,找到就返回结点,没找到就一直找,直到为空

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

2.4插入实现

开散列的插入大致思路跟开放定制法一样,先查找,在判断负载因子,最后插入。

我们先来看插入部分,先计算映射的哈希值hashi,再new一个新节点,将新节点的_next指向hashi的地址,再将新节点复制给hashi,这样就完成了头插,最后++_n。具体插入如下图所示,newnode为11

对于扩容部分,由于是拉链法,负载因子可以适当大一点,因此我们将负载因子调整到了1,也就是_n==_tables.size()。并且我们并没有像开放定制法那样复用Insert,因为复用insert又会开辟新结点,并且析构函数需要自己写,因为HashNode是自定义类型,并且有指针。这样析构起来速度会很慢,因此我们选择将原来_tables上的结点直接挪动过来,这样也节省了开辟结点和析构结点的时间

代码部分新创建一个newtables,先开辟原哈希表2倍的大小,遍历原哈希表,将挂载的索引节点重新映射到新表上,依然选择的头插法。当当前链表遍历完毕后,要将_tables[i]置空,因为这是浅拷贝,不置空析构会有问题。最后swap一下即可。

  1. bool Insert(const pair<K, V>& kv)
  2. {
  3. if (Find(kv.first))
  4. return false;
  5. if (_n == _tables.size())
  6. {
  7. vector<Node*> newtables;
  8. newtables.resize(_tables.size() * 2, nullptr);
  9. for (size_t i = 0; i < _tables.size(); i++)
  10. {
  11. Node* cur = _tables[i];
  12. while (cur)
  13. {
  14. Node* next = cur->_next;
  15. //映射到新表
  16. size_t hashi = cur->_kv.first % newtables.size();
  17. cur->_next = newtables[hashi];
  18. newtables[hashi] = cur;
  19. cur = next;
  20. }
  21. //置空,防止析构出现问题
  22. _tables[i] = nullptr;
  23. }
  24. _tables.swap(newtables);
  25. }
  26. size_t hashi = kv.first % _tables.size();
  27. Node* newnode = new Node(kv);
  28. newnode->_next = _tables[hashi];
  29. _tables[hashi] = newnode;
  30. ++_n;
  31. return true;
  32. }

运行打印,看看效果,没问题。

2.5 删除实现

 这里我们没有用Find来删除,因为就算我们找到了结点,由于是单链表,我们也无法找到节点的前一个,因此没有用Find函数。

取出哈希值hashi,遍历当前的哈希值的链表,同时记录好前一个。找到Key或者找到空结束,如果找到了,而且前一个为空,证明删除的是第一个,就直接将cur->_next给到_tables[hashi]即可,完成头删。

如果前一个不为空,则不是头删,则prev->_next = cur->_next;  最后detele 再return true即可。

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

 测试一下

2.6string做key

依然是我们之前写的HashFund,在本文的三、2.6

需要取整数的地方套上一层就可以了。不多赘述。

五、哈希桶与set和unordered_set的对比 

这里我们选取了一百万个随机值进行插入测试,(具体打印函数后续会给出)根据打印内容我们发现,哈希表处理数据的速度是要比红黑树快一点的。

至于之前我们说为什么C++不适用哈希表挂载红黑树的方式,我们可以通过maxBucketLen来知道,一个地方时很难挂载到很多数据的,除非你是人为恶心哈希表才会挂载很多数据。

最后为什么我们的哈希表比库里面的还要快,因为库里面还涉及到了一些东西,我们写的是简单版。

最后附上代码  

HashTable.h

  1. #pragma once
  2. #include<vector>
  3. template<class K>
  4. struct HashFunc
  5. {
  6. size_t operator()(const K& key)
  7. {
  8. return (size_t)key;
  9. }
  10. };
  11. template<>
  12. struct HashFunc<string>
  13. {
  14. size_t operator()(const string& s)
  15. {
  16. size_t sum = 0;
  17. for (auto& e : s)
  18. {
  19. sum *= 31;
  20. sum += e;
  21. }
  22. return sum;
  23. }
  24. };
  25. namespace kky_open_address
  26. {
  27. enum Status
  28. {
  29. EMPTY,
  30. EXIST,
  31. DELETE,
  32. };
  33. template<class K, class V>
  34. struct HashDate
  35. {
  36. pair<K, V> _kv;
  37. Status _s;
  38. };
  39. template<class K,class V ,class Hash = HashFunc<K>>
  40. class HashTable
  41. {
  42. public:
  43. HashTable()
  44. {
  45. _tables.resize(10);
  46. }
  47. Hash hs;
  48. HashDate<K,V>* Find(const K& key)
  49. {
  50. size_t hashi = hs(key) % _tables.size();
  51. while (_tables[hashi]._s != EMPTY)
  52. {
  53. if (_tables[hashi]._s != DELETE && _tables[hashi]._kv.first == key)
  54. return &_tables[hashi];
  55. hashi++;
  56. hashi %= _tables.size();
  57. }
  58. return nullptr;
  59. }
  60. bool Erase(const K& key)
  61. {
  62. HashDate<K, V>* ret = Find(key);
  63. if (ret)
  64. {
  65. ret->_s = DELETE;
  66. --_n;
  67. return true;
  68. }
  69. return false;
  70. }
  71. bool Insert(const pair<K,V>& kv)
  72. {
  73. if (Find(kv.first))
  74. {
  75. return false;
  76. }
  77. if (_n*10 / _tables.size() >= 7)
  78. {
  79. int newcapacity = _tables.size() * 2;
  80. HashTable<K, V> newHT;
  81. newHT._tables.resize(newcapacity);
  82. for (size_t i = 0; i < _tables.size(); i++)
  83. {
  84. if(_tables[i]._s == EXIST)
  85. {
  86. newHT.Insert(_tables[i]._kv);
  87. }
  88. }
  89. _tables.swap(newHT._tables);
  90. }
  91. size_t hashi = hs(kv.first) % _tables.size();
  92. while (_tables[hashi]._s == EXIST)
  93. {
  94. hashi++;
  95. hashi %= _tables.size();
  96. }
  97. _tables[hashi]._kv = kv;
  98. _tables[hashi]._s = EXIST;
  99. ++_n;
  100. return true;
  101. }
  102. void Print()
  103. {
  104. for (size_t i = 0; i < _tables.size(); i++)
  105. {
  106. if (_tables[i]._s == EXIST)
  107. {
  108. //printf("[%d]->%d:%s\n", i,_tables[i]._kv.first, "EXIST");
  109. cout << "[" << i << "]->" << _tables[i]._kv.first<<":" << _tables[i]._kv.second << endl;
  110. }
  111. else if(_tables[i]._s == EMPTY)
  112. {
  113. //printf("[%d]->%d:%s\n", i, _tables[i]._kv.first, "EMPTY");
  114. cout << "[" << i << "]->" <<endl;
  115. }
  116. else
  117. {
  118. //printf("[%d]->%d:%s\n", i, _tables[i]._kv.first, "DELETE");
  119. cout << "[" << i << "]->" <<"DELETE" << endl;
  120. }
  121. }
  122. cout << endl;
  123. }
  124. private:
  125. vector<HashDate<K,V>> _tables;
  126. size_t _n;
  127. };
  128. void test01()
  129. {
  130. HashTable<int, int> ht;
  131. int arr[] = { 3,13,23,4,5,14,7,13 };
  132. for (auto e : arr)
  133. {
  134. ht.Insert(make_pair(e, e));
  135. }
  136. ht.Print();
  137. ht.Erase(13);
  138. ht.Print();
  139. ht.Insert(make_pair(33,33));
  140. ht.Print();
  141. }
  142. void test02()
  143. {
  144. string arr[] = { "香蕉","苹果","橘子","香蕉","苹果" ,"香蕉","苹果" ,"香蕉" };
  145. HashTable<string, int> ht;
  146. for (auto e : arr)
  147. {
  148. HashDate<string, int>* ret = ht.Find(e);
  149. if(ret)
  150. ret->_kv.second++;
  151. else
  152. ht.Insert(make_pair(e, 1));
  153. }
  154. ht.Print();
  155. }
  156. }
  157. namespace kky_hash_bucket
  158. {
  159. template<class K, class V>
  160. struct HashNode
  161. {
  162. pair<K, V> _kv;
  163. HashNode<K, V>* _next;
  164. HashNode(const pair<K,V>& kv)
  165. :_kv(kv)
  166. ,_next(nullptr)
  167. {}
  168. };
  169. template<class K, class V, class Hash = HashFunc<K>>
  170. class HashTable
  171. {
  172. typedef HashNode<K, V> Node;
  173. public:
  174. HashTable()
  175. {
  176. _tables.resize(10);
  177. }
  178. HashTable(const HashTable<K,V>& ht)
  179. {
  180. _tables.resize(ht._tables.size(), nullptr);
  181. for (size_t i = 0; i < ht._tables.size(); i++)
  182. {
  183. Node* cur = ht._tables[i];
  184. while (cur)
  185. {
  186. Node* newnode = new Node(cur->_kv);
  187. if (_tables[i]==nullptr)
  188. {
  189. _tables[i] = newnode;
  190. }
  191. else
  192. {
  193. newnode->_next = _tables[i];
  194. _tables[i] = newnode;
  195. }
  196. cur = cur->_next;
  197. }
  198. }
  199. }
  200. HashTable<K, V>& operator=(HashTable<K, V> ht)
  201. {
  202. _tables.swap(ht._tables);
  203. swap(_n, ht._n);
  204. return *this;
  205. }
  206. ~HashTable()
  207. {
  208. for (size_t i = 0; i < _tables.size(); i++)
  209. {
  210. Node* cur = _tables[i];
  211. while (cur)
  212. {
  213. Node* next = cur->_next;
  214. delete cur;
  215. cur = next;
  216. }
  217. _tables[i] = nullptr;
  218. }
  219. }
  220. bool Insert(const pair<K, V>& kv)
  221. {
  222. Hash hs;
  223. if (Find(kv.first))
  224. return false;
  225. if (_n == _tables.size())
  226. {
  227. vector<Node*> newtables;
  228. newtables.resize(_tables.size() * 2, nullptr);
  229. for (size_t i = 0; i < _tables.size(); i++)
  230. {
  231. Node* cur = _tables[i];
  232. while (cur)
  233. {
  234. Node* next = cur->_next;
  235. //映射到新表
  236. size_t hashi = hs(cur->_kv.first) % newtables.size();
  237. cur->_next = newtables[hashi];
  238. newtables[hashi] = cur;
  239. cur = next;
  240. }
  241. //置空,防止析构出现问题
  242. _tables[i] = nullptr;
  243. }
  244. _tables.swap(newtables);
  245. }
  246. size_t hashi = hs(kv.first) % _tables.size();
  247. Node* newnode = new Node(kv);
  248. newnode->_next = _tables[hashi];
  249. _tables[hashi] = newnode;
  250. ++_n;
  251. return true;
  252. }
  253. Node* Find(const K& key)
  254. {
  255. Hash hs;
  256. size_t hashi = hs(key) % _tables.size();
  257. Node* cur = _tables[hashi];
  258. while (cur)
  259. {
  260. if (cur->_kv.first == key)
  261. {
  262. return cur;
  263. }
  264. cur = cur->_next;
  265. }
  266. return nullptr;
  267. }
  268. bool Erase(const K& key)
  269. {
  270. Hash hs;
  271. size_t hashi = hs(key) % _tables.size();
  272. Node* cur = _tables[hashi];
  273. Node* prev = nullptr;
  274. while (cur)
  275. {
  276. if (cur->_kv.first == key)
  277. {
  278. if (prev == nullptr)
  279. {
  280. _tables[hashi] = cur->_next;
  281. }
  282. else
  283. {
  284. prev->_next = cur->_next;
  285. }
  286. delete cur;
  287. return true;
  288. }
  289. prev = cur;
  290. cur = cur->_next;
  291. }
  292. return false;
  293. }
  294. void Print()
  295. {
  296. for (size_t i = 0; i < _tables.size(); i++)
  297. {
  298. Node* cur = _tables[i];
  299. cout << "[" << i << "]" << "挂载"<<"->";
  300. while (cur)
  301. {
  302. cout << cur->_kv.first << ":" << cur->_kv.second << "->";
  303. cur = cur->_next;
  304. }
  305. cout << endl;
  306. }
  307. cout << endl;
  308. }
  309. void Some()
  310. {
  311. size_t bucketSize = 0;
  312. size_t maxBucketLen = 0;
  313. double averageBucketLen = 0;
  314. for (size_t i = 0; i < _tables.size(); i++)
  315. {
  316. Node* cur = _tables[i];
  317. if (cur)
  318. {
  319. ++bucketSize;
  320. }
  321. size_t bucketLen = 0;
  322. while (cur)
  323. {
  324. ++bucketLen;
  325. cur = cur->_next;
  326. }
  327. if (bucketLen > maxBucketLen)
  328. {
  329. maxBucketLen = bucketLen;
  330. }
  331. }
  332. averageBucketLen = (double)bucketSize / (double)_n;
  333. printf("all bucketSize:%d\n",_tables.size());
  334. printf("bucketSize:%d\n", bucketSize);
  335. printf("maxBucketLen:%d\n", maxBucketLen);
  336. printf("averageBucketLen:%lf\n\n", averageBucketLen);
  337. }
  338. private:
  339. vector<Node*> _tables;
  340. size_t _n;
  341. };
  342. void test01()
  343. {
  344. HashTable<int, int> ht;
  345. int arr[] = { 3,13,23,4,5,14,33,34,43,44 };
  346. for (auto e : arr)
  347. {
  348. ht.Insert(make_pair(e, e));
  349. }
  350. ht.Print();
  351. cout << ht.Find(43) << endl;
  352. ht.Erase(43);
  353. ht.Print();
  354. cout << ht.Find(43) << endl;
  355. }
  356. void test02()
  357. {
  358. string arr[] = { "香蕉","苹果","橘子","香蕉","苹果" ,"香蕉","苹果" ,"香蕉" };
  359. HashTable<string, int> ht;
  360. for (auto e : arr)
  361. {
  362. HashNode<string, int>* ret = ht.Find(e);
  363. if (ret)
  364. ret->_kv.second++;
  365. else
  366. ht.Insert(make_pair(e, 1));
  367. }
  368. }
  369. void test03()
  370. {
  371. const size_t N = 1000000;
  372. unordered_set<int> us;
  373. set<int> s;
  374. HashTable<int, int> ht;
  375. vector<int> v;
  376. v.reserve(N);
  377. srand(time(0));
  378. for (size_t i = 0; i < N; ++i)
  379. {
  380. //v.push_back(rand()); // N比较大时,重复值比较多
  381. v.push_back(rand() + i); // 重复值相对少
  382. //v.push_back(i); // 没有重复,有序
  383. }
  384. size_t begin1 = clock();
  385. for (auto e : v)
  386. {
  387. s.insert(e);
  388. }
  389. size_t end1 = clock();
  390. cout << "set insert:" << end1 - begin1 << endl;
  391. size_t begin2 = clock();
  392. for (auto e : v)
  393. {
  394. us.insert(e);
  395. }
  396. size_t end2 = clock();
  397. cout << "unordered_set insert:" << end2 - begin2 << endl;
  398. size_t begin10 = clock();
  399. for (auto e : v)
  400. {
  401. ht.Insert(make_pair(e, e));
  402. }
  403. size_t end10 = clock();
  404. cout << "HashTbale insert:" << end10 - begin10 << endl << endl;
  405. size_t begin3 = clock();
  406. for (auto e : v)
  407. {
  408. s.find(e);
  409. }
  410. size_t end3 = clock();
  411. cout << "set find:" << end3 - begin3 << endl;
  412. size_t begin4 = clock();
  413. for (auto e : v)
  414. {
  415. us.find(e);
  416. }
  417. size_t end4 = clock();
  418. cout << "unordered_set find:" << end4 - begin4 << endl;
  419. size_t begin11 = clock();
  420. for (auto e : v)
  421. {
  422. ht.Find(e);
  423. }
  424. size_t end11 = clock();
  425. cout << "HashTable find:" << end11 - begin11 << endl << endl;
  426. cout << "插入数据个数:" << us.size() << endl << endl;
  427. ht.Some();
  428. size_t begin5 = clock();
  429. for (auto e : v)
  430. {
  431. s.erase(e);
  432. }
  433. size_t end5 = clock();
  434. cout << "set erase:" << end5 - begin5 << endl;
  435. size_t begin6 = clock();
  436. for (auto e : v)
  437. {
  438. us.erase(e);
  439. }
  440. size_t end6 = clock();
  441. cout << "unordered_set erase:" << end6 - begin6 << endl;
  442. size_t begin12 = clock();
  443. for (auto e : v)
  444. {
  445. ht.Erase(e);
  446. }
  447. size_t end12 = clock();
  448. cout << "HashTable Erase:" << end12 - begin12 << endl << endl;
  449. }
  450. }

 test.cpp

  1. #include<iostream>
  2. #include<string>
  3. #include<set>
  4. #include<unordered_set>
  5. using namespace std;
  6. #include"HashTable.h"
  7. #include"test.h"
  8. int main()
  9. {
  10. kky_hash_bucket::test03();
  11. }

 感谢大家观看!!!

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

闽ICP备14008679号