当前位置: 首页 > news >正文

【C++】用哈希表封装实现unordered_set和unordered_map

目录

前言

一、基本框架

1.改造原哈希表,实现出复用框架

2.实现unordered_set和unorder_map的框架

二、迭代器iterator的实现

1.哈希表中Iterator的基本框架

2.迭代器前置++的实现

3.Begin和End()实现以及测试+Bug修复

Bug1:

Bug2:

4.const迭代器的实现+Bug修复

Bug1:

Bug2:

三、Insert改造和unordered_map的operator[ ]实现

四、Find改造,以及unordered_set和unordered_map基本功能补全

五、完整代码展示

总结



前言

        用哈希表封装实现unordered_set和unordered_map的方式,与前面使用红黑树封装实现set和map是非常类似的,所以本文在相似的地方就简述一下。


一、基本框架

1.改造原哈希表,实现出复用框架

HashTable.h:

#pragma once
#include <vector>
#include <string>
#include <algorithm>
using namespace std;//链地址法的命名空间
namespace hash_bucket
{//定义默认仿函数template<class K>struct HashFunc{size_t operator()(const K& key){return (size_t)key;}};//string版的特化仿函数template<>struct HashFunc<string>{size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash += e;hash *= 131;}return hash;}};//定义哈希数据节点template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};//定义哈希表template<class K, class T, class KeyOfT, class Hash>class HashTable{typedef HashNode<T> Node;//获取下一个质数inline unsigned long __stl_next_prime(unsigned long n){static const int __stl_num_primes = 28;//定义质数列表大小static const unsigned long __stl_prime_list[__stl_num_primes] ={53,         97,         193,       389,       769,1543,       3079,       6151,      12289,     24593,49157,      98317,      196613,    393241,    786433,1572869,    3145739,    6291469,   12582917,  25165843,50331653,   100663319,  201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);//返回大于等于n的质数return pos == last ? *(last - 1) : *pos;}public://构造HashTable(){_tables.resize(__stl_next_prime(0), nullptr);}//析构~HashTable(){for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}//插入bool Insert(const T& data){KeyOfT kot;//插入前先查找判断该值是否已经存在,因为哈希表不支持插入重复值if (Find(kot(data)) != nullptr)return false;Hash hs;//扩容if (_n == _tables.size()){size_t newSize = __stl_next_prime((unsigned long)(_tables.size() + 1));//新空间大小vector<Node*> newTables(newSize, nullptr);//新空间//遍历旧表,把旧把数据重新装到新表for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];//循环遍历桶while (cur){Node* next = cur->_next;//先保存一份下一个节点//计算新位置size_t hashi = hs(kot(cur->_data)) % newSize;//将cur头插到新表cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;//接着遍历当前桶(如果还有节点)}_tables[i] = nullptr;//将旧表置空}_tables.swap(newTables);//交换}//除留余数法计算映射位置size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);//创建新节点//头插到哈希桶newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}//查找Node* Find(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];//遍历桶查找while (cur){if (kot(cur->_data) == key){return cur;}cur = cur->_next;}return nullptr;}//删除bool Erase(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();//先计算映射位置Node* prev = nullptr;//用于记录cur的上一个节点Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){//头删if (prev == nullptr){_tables[hashi] = cur->_next;}else//不是头删{prev->_next = cur->_next;}//删除curdelete cur;--_n;return true;}//没找到,继续遍历下一个节点prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _tables;//指针数组size_t _n = 0;//有效数据个数};
}
  • 这里的哈希表改造和前面哈希表封装时的改造是基本一样的,但也有一处细节不同。
  • 改造前的哈希表代码参考上一篇哈希表实现的文章。
  1. 首先将原写死的pair底层数据类型改用T模板参数替换,这样底层数据类型就可以多样化。
  2. 增加模板参数 KeyOfT,它用于取出T类型数据中的关键数据Key,对于unordered_set来说就是单个数据类型,对于unordered_map来说就是单个pair类型数据中的first数据。简单来说:KeyOfT是一个仿函数,它由unordered_set或者unordered_map进行传参,因此具体的KeyOfT仿函数是在具体的unordered_set或者unordered_map中进行实现。KeyOfT主要用于解决比较或者计算场景,注意是因为普通类型和pair类型的区别,普通类型比较或者计算的就是其数据本身,而pair结构参与比较或者计算的是其中的firsr,因此需要传递不同的仿函数进行统一格式计算。KeyOfT的具体实现在下面unorderedset.h和unorderedmap.h中。
  3. 用KeyOfT示例化出kot仿函数,广泛用于Insert、Find、Erase中涉及将_data中的数据取出用于比较或者计算的场景。这样一来,哈希表就能够支持复用了。
  4. 剩下这一点也是改造红黑树中没有的一步,就是哈希表中独有的模板参数Hash,它是用于将任意数据转换为一个无符号整数,作用于哈希函数的取模运算。它也是一个仿函数,这里的改造是去掉了哈希表中该参数的缺省参数,转而将其缺省参数放在了unordered_set和unordered_map中,再通过这两个类传参给哈希表的Hash,这样的好处是,在我们设计自定义类型并作为我们实现的unordered_set和unordered_map的底层数据时,原有的Hash仿函数不满足转换自定义类型为整形的要求,此时我们可以通过自定义Hash仿函数并且传参给Hash达到自己控制的转换函数从而解决这个问题。

2.实现unordered_set和unorder_map的框架

unorderedset.h:

#pragma once
#include "HashTable.h"namespace txp
{template<class K, class Hash = hash_bucket::HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:bool insert(const K& key){return _ht.Insert(key);}private:hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;};
}

unorderedmap.h:

#pragma once
#include "HashTable.h"namespace txp
{template<class K, class V, class Hash = hash_bucket::HashFunc<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:bool insert(const pair<K, V>& kv){return _ht.Insert(kv);}private:hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};
}
  • 剩下这两个就是unordered_set和unorder_map的基本框架。
  • 除了上面提到的在这里实现了的,还有一处需要注意,也就是创建底层结构 _ht 时的传参细节,我们应该知道,unordered_set中的数据是不能修改的。unordered_map中pair数据的K是不能修改的,但是V能修改。因此上面unordered_set和unorder_map给哈希表T模板参数传参时分别在不同的位置带上了const进行修饰。
  • 经过以上改造,Insert已经能够被unordered_set和unordered_map正常复用了。


二、迭代器iterator的实现

1.哈希表中Iterator的基本框架

//哈希表前置声明
template<class K, class T, class KeyOfT, class Hash>
class HashTable;//定义迭代器
template<class K, class T, class KeyOfT, class Hash>
struct __HTIterator
{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef __HTIterator<K, T, KeyOfT, Hash> Self;Node* _node;HT* _ht;__HTIterator(Node* node, HT* ht):_node(node), _ht(ht){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}Self& operator++(){}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}
};
  • 哈希表的迭代器实现,相较于红黑树,有一点点特别。当然同样作为单向迭代器,它们最重要的功能就是 ++ 的实现了。
  1. 首先哈希表迭代器的底层由一个_node节点和一个哈希表_ht构成,_node节点很好理解,就是迭代器指向的当前节点。那么为什么要创建一个哈希表_ht呢,这就涉及迭代器中关键接口—— operator++ 的实现了,operator++的实现我们下面在说,总之_ht是需要的。
  2. 模板参数中,除了必要的K,T外,KeOfT和Hash也都是为operator++ 的实现做铺垫的
  3. 那么剩下的常见迭代器接口我们已经实现过很多次了,比如两种解引用,比较相等与不等,这些就不再赘述了。

2.迭代器前置++的实现

  • 迭代器的++是如何走的呢,我们可以参考上面这张图。
  • ++流程:首先判断当前节点的下一个节点是否存在,因为在同一个哈希桶中的节点是以单链表的形式连接的,如此一来,假如下一个节点存在,那么++就走到_next节点即可,这就是在同一个桶中的情况,假如该桶走到最后了,_next指向为空了,比如3,此时++该怎么走?
  • 其实从一个桶走到另一个桶并不复杂,我们只需要先计算当前桶的位置,比如3,我们先用哈希函数计算3中的数据的哈希映射值hashi,hashi就是哈希表中该桶的位置,知道了当前桶的位置后,那么我们就可以先++hashi走到下一个桶的位置,那么这一步就需要判断了,首先hashi不能越界,然后就需要判断当前桶有没有数据了,这个很好判断if (_ht->_tables[hashi])即可。如果该if为真,说明找到下一个桶了直接获取桶顶的数据,将_node = _ht->_tables[hashi],然后break结束循环即可,if为假,说明桶为空,那么继续++hashi判断下一个桶,如此就可以循环起来。循环条件就是while (hashi < _ht->_tables.size())即hashi不越界即可。
  • 当然循环结束就有两种可能,一是找到了下一个桶,二是没有找到越界导致循环结束的。那么没有找到的情况就需要特殊处理了,也就是返回++走到了end()位置,由于我们没有设定哨兵位,所以end()我们是由nullptr也就是空代替了,那么++走到空只需要将_node = nullptr即可,最后返回*this,也就是迭代器本身即可。

代码实现:

Self& operator++()
{if (_node->_next){//当前桶还有节点_node = _node->_next;}else{//当前桶遍历完了KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();//确认当前桶的位置++hashi;//从下一个桶开始遍历while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){//找到了下一个桶_node = _ht->_tables[hashi];break;}++hashi;}if (hashi == _ht->_tables.size()){//没找到下一个桶_node = nullptr;}}return *this;
}

(当然,后置++你想实现也可以自行实现,这里就不赘述了)


3.Begin和End()实现以及测试+Bug修复

  • Begin()的实现不复杂,我们就从头循环遍历哈希表,只要有个哈希桶里面有值,这个就是Begin()的位置。
  • End()我们使用空指针。

HashTable.h:

//定义哈希表
template<class K, class T, class KeyOfT, class Hash>
class HashTable
{//....public:typedef __HTIterator<K, T, KeyOfT, Hash> Iterator;Iterator Begin(){for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return Iterator(cur, this);}}return End();}Iterator End(){return Iterator(nullptr, this);}//....
};

unorderedset.h:

template<class K, class Hash = hash_bucket::HashFunc<K>>
class unordered_set
{//....public:typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;iterator begin(){return _ht.Begin();}iterator end(){return _ht.End();}//....
};

unorderedmap.h:

template<class K, class V, class Hash = hash_bucket::HashFunc<K>>
class unordered_map
{//....public:typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;iterator begin(){return _ht.Begin();}iterator end(){return _ht.End();}//....
};
  • 以上是普通迭代器的实现与封装,现在我们来使用迭代器遍历简单测试一下:

test.cpp:

#include <iostream>
#include "unorderedset.h"
#include "unorderedmap.h"
using namespace std;void test1()
{txp::unordered_set<int> s1;s1.insert(1);s1.insert(54);s1.insert(3);s1.insert(5);s1.insert(2);txp::unordered_set<int>::iterator it1 = s1.begin();while (it1 != s1.end()){cout << *it1 << " ";++it1;}cout << endl;
}int main()
{test1();return 0;
}

测试结果:

Bug1:

  • 原因:这里是因为在迭代器Iterator类的实现中创建了哈希表_ht,但是哈希表的代码实现是写在迭代器的下面,导致迭代器中无法识别哈希表。本质原因是代码的编译是从上往下走的。
  • 解决方法:加上哈希表的前置声明,放在迭代器的代码上面。
  • //哈希表前置声明
    template<class K, class T, class KeyOfT, class Hash>
    class HashTable;

Bug2:

  • 原因:这个是因为迭代器中operator++的实现使用了哈希表的私有变量_tables。
  • 解决方法:这个目前最好的解决方法就是使用友元,在哈希表中加入迭代器的友元声明。
  • //定义哈希表
    template<class K, class T, class KeyOfT, class Hash>
    class HashTable
    {typedef HashNode<T> Node;//友元声明template<class K, class T, class KeyOfT, class Hash>friend struct __HTIterator;//....
    }

最后运行一下:

  • 结果测试成功


4.const迭代器的实现+Bug修复

实现完了普通迭代器,现在我们来实现const迭代器

  • 还是和之前红黑树或者链表一样,实现const需要给迭代器模板加入两个参数:Ref 和 Ptr。

代码实现:

HashTable.h:

//定义迭代器
template<class K, class T,class Ref, class Ptr, class KeyOfT, class Hash>
struct __HTIterator
{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef __HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;Node* _node;HT* _ht;__HTIterator(Node* node, HT* ht):_node(node), _ht(ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}//....
};//定义哈希表
template<class K, class T, class KeyOfT, class Hash>
class HashTable
{//....public:typedef __HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;typedef __HTIterator<K, T, const T&, const T*, KeyOfT, Hash> ConstIterator;Iterator Begin(){for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return Iterator(cur, this);}}return End();}Iterator End(){return Iterator(nullptr, this);}ConstIterator Begin() const{for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return ConstIterator(cur, this);}}return End();}ConstIterator End() const{return ConstIterator(nullptr, this);}//....

unorderedset.h:

template<class K, class Hash = hash_bucket::HashFunc<K>>
class unordered_set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator const_iterator;iterator begin(){return _ht.Begin();}iterator end(){return _ht.End();}const_iterator begin() const{return _ht.Begin();}const_iterator end() const{return _ht.End();}bool insert(const K& key){return _ht.Insert(key);}private:hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
};

unorderedmap.h:

template<class K, class V, class Hash = hash_bucket::HashFunc<K>>
class unordered_map
{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator const_iterator;iterator begin(){return _ht.Begin();}iterator end(){return _ht.End();}const_iterator begin() const{return _ht.Begin();}const_iterator end() const{return _ht.End();}bool insert(const pair<K, V>& kv){return _ht.Insert(kv);}private:hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};

  • 以上是const迭代器的实现,接下来我们进行一个简单的测试:使用const迭代器遍历来检测一下代码:

test.cpp:

#include <iostream>
#include "unorderedset.h"
#include "unorderedmap.h"
using namespace std;void test1()
{txp::unordered_set<int> s1;s1.insert(1);s1.insert(54);s1.insert(3);s1.insert(5);s1.insert(2);txp::unordered_set<int>::const_iterator it1 = s1.begin();while (it1 != s1.end()){cout << *it1 << " ";++it1;}cout << endl << endl;
}void test2()
{txp::unordered_map<int, int> m1;m1.insert({ 1,1 });m1.insert({ 2,2 });m1.insert({ 54,54 });m1.insert({ 3,3 });txp::unordered_map<int, int>::const_iterator it1 = m1.begin();while (it1 != m1.end()){cout << it1->first << ":" << it1->second << endl;++it1;}cout << endl;
}int main()
{test1();test2();return 0;
}

运行结果:

Bug1:

  • 原因:缺少适当的拷贝构造,将普通迭代器转换为const迭代器,这里注意是针对unordered_map,因为unordered_set的普通迭代器和const迭代器区别不大,这也是一个老问题了,红黑树中也是这样。
  • 解决方法:自己写一个拷贝构造,将普通迭代器作为形参,注意这里需要手动typedef定义一个普通迭代器Iterator。
  • //定义迭代器
    template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
    struct __HTIterator
    {typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef __HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;typedef __HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;Node* _node;HT* _ht;__HTIterator(Node* node, HT* ht):_node(node), _ht(ht){}//拷贝构造__HTIterator(const Iterator& it):_node(it._node), _ht(it._ht){}//....
    }

继续测试,运行结果:


  • 当然还有一个bug容易被忽略,比如以下这个测试:

test.cpp:

#include <iostream>
#include "unorderedset.h"
#include "unorderedmap.h"
using namespace std;void test1()
{const txp::unordered_set<int> s1;txp::unordered_set<int>::const_iterator it1 = s1.begin();while (it1 != s1.end()){cout << *it1 << " ";++it1;}cout << endl << endl;
}void test2()
{const txp::unordered_map<int, int> m1;txp::unordered_map<int, int>::const_iterator it1 = m1.begin();while (it1 != m1.end()){cout << it1->first << ":" << it1->second << endl;++it1;}cout << endl;
}int main()
{test1();test2();return 0;
}

运行结果:

Bug2:

  • 原因:编译器显示的报错原因并不准确,这里真正的原因在于迭代器的底层数据结构——哈希表_ht。以Begin()接口为例,我们对比普通版本和const版本的区别:
  • 表明上看只有返回值类型的区别,实际上,我们注意 this 指针的区别,普通迭代器中this 指针就是普通的哈希表本身的指针,const 迭代器中 this 指针是const修饰的哈希表本身的指针,这样会导致什么问题呢?问题的关键就是 return ConstIterator(cur, this) 这行代码,其中第二个参数传递的是两种类不一样的哈希表指针,我们再继续看迭代器中的构造函数:
  • 很明显,迭代器的构造函数中,哈希表指针_ht是没有const修饰的,而const修饰的Begin()传递的哈希表指针参数是有const修饰的,所以这里的问题本质就是权限的放大。
  • 解决方法:问题已经明了,解决方法就是将哈希表底层的_ht以及构造函数中的哈希表指针加上const修饰。
  • 修改后的迭代器构造函数:
  • //定义迭代器
    template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
    struct __HTIterator
    {typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef __HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;typedef __HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;Node* _node;const HT* _ht;__HTIterator(Node* node, const HT* ht):_node(node), _ht(ht){}//....
    }

bug2修复后上述就能正常运行了


三、Insert改造和unordered_map的operator[ ]实现

  • 要实现unordered_map的operator[ ]接口,也就是实现方括号的功能,就离不开Insert,具体原因红黑树的封装中讲过,简单点说[ ]是具有插入功能的,因此可以在Insert是进行一些改造。
  • Insert的具体改造就是将返回值更换为:pair<Iterator, bool>,其中Iterator是插入元素的迭代器,bool表示是否插入成功,false为插入失败,true为插入成功。

Insert改造:

//插入
pair<Iterator, bool> Insert(const T& data)
{KeyOfT kot;Node* ret = Find(kot(data));if (ret != nullptr)return make_pair(Iterator(ret, this), false);Hash hs;//扩容if (_n == _tables.size()){size_t newSize = __stl_next_prime((unsigned long)(_tables.size() + 1));//新空间大小vector<Node*> newTables(newSize, nullptr);//新空间//遍历旧表,把旧把数据重新装到新表for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];//循环遍历桶while (cur){Node* next = cur->_next;//先保存一份下一个节点//计算新位置size_t hashi = hs(kot(cur->_data)) % newSize;//将cur头插到新表cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;//接着遍历当前桶(如果还有节点)}_tables[i] = nullptr;//将旧表置空}_tables.swap(newTables);//交换}//除留余数法计算映射位置size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);//创建新节点//头插到哈希桶newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(Iterator(newnode, this), true);
}
  • Insert改造的地方就三处,1是返回值,2是两处return。
  • 关于开头处的return,后续Find改造后还会改一次。

unorderedmap.h:

namespace txp
{template<class K, class V, class Hash = hash_bucket::HashFunc<K>>class unordered_map{//....        pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = insert({ key, V() });return ret.first->second;}//....}
}
  • unordered_map的[ ] 实现,就是先插入Key和V的默认构造V()构成的pair元素,然后用ret 接收返回值,无论是否插入成功,其中的iterator都是指向以Key进行构造的pair元素的迭代器。这样一来,通过ret.first就能访问Key的迭代器iterator,再次->second就能访问其迭代器iterator中的pair的value,这其中跳转了两次是因为编译器自动跳转了一次,本来应该先->调用iterator的->接口访问_data(pair类型)指针的,这里编译器省略了这一步直接访问pair类型的_data的第二个参数value,这是编译器优化的结果。
  • 总之,通过改造Insert然后复用,就能轻松得到Key对应的Value了。
  • 然后因为Insert的改造,unordered_set和unordered_map的insert返回值都需要跟着改正。

unorderedset.h:

namespace txp
{template<class K, class Hash = hash_bucket::HashFunc<K>>class unordered_set{//....pair<iterator, bool> insert(const K& key){return _ht.Insert(key);}//....};
}

简单测试一下:

test.cpp:

#include <iostream>
#include "unorderedset.h"
#include "unorderedmap.h"
using namespace std;void test3()
{//修改测试txp::unordered_map<string, string> m1;m1.insert({ "sort", "排序" });m1.insert({ "left", "左边" });m1.insert({ "right", "右边" });m1["left"] = "左边,剩余";m1["insert"] = "插入";m1["string"];txp::unordered_map<string, string>::iterator it = m1.begin();while (it != m1.end()){cout << it->first << ":" << it->second << endl;++it;}cout << endl;//统计测试string arr[] = { "苹果","西瓜","苹果","西瓜","苹果","苹果","西瓜","苹果","香蕉","苹果","香港" };txp::unordered_map<string, int> countMap;for (auto& e : arr){countMap[e]++;}for (auto& e : countMap){cout << e.first << ":" << e.second << endl;}
}int main()
{test3();return 0;
}

运行结果:

  • 结果显示正常


四、Find改造,以及unordered_set和unordered_map基本功能补全

  • 当我们封装好迭代器后,Find的返回值就需要变为迭代器,一来是和stl保持一致,二来这样Find的返回值操作空间就更多了。本质这两者原因是一样的哈哈。

Find改造:

Iterator Find(const K& key)
{KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];//遍历桶查找while (cur){if (kot(cur->_data) == key){return Iterator(cur, this);}cur = cur->_next;}return Iterator(nullptr, this);
}
  • Find改造的地方主要也是两处:1是返回值,2是两处return。
  • Find被改造了,Insert的开头就需要跟着改一下:

Insert修正:

//插入
pair<Iterator, bool> Insert(const T& data)
{KeyOfT kot;Iterator it = Find(kot(data));if (it != End())return make_pair(it, false);//....
}
  • Insert简单修正,接下来是unordered_set和unordered_map基本功能补全了。
  • 其实主要也就两个功能,find和erase。

unorderedset.h:

namespace txp
{template<class K, class Hash = hash_bucket::HashFunc<K>>class unordered_set{//....iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}//....};
}

unorderedmap.h:

namespace txp
{template<class K, class V, class Hash = hash_bucket::HashFunc<K>>class unordered_map{//....iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}//....};
}


五、完整代码展示

HashTable.h:

#pragma once
#include <vector>
#include <string>
#include <algorithm>
using namespace std;//链地址法的命名空间
namespace hash_bucket
{//定义默认仿函数template<class K>struct HashFunc{size_t operator()(const K& key){return (size_t)key;}};//string版的特化仿函数template<>struct HashFunc<string>{size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash += e;hash *= 131;}return hash;}};//定义哈希数据节点template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};//哈希表前置声明template<class K, class T, class KeyOfT, class Hash>class HashTable;//定义迭代器template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>struct __HTIterator{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef __HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;typedef __HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;Node* _node;const HT* _ht;__HTIterator(Node* node, const HT* ht):_node(node), _ht(ht){}__HTIterator(const Iterator& it):_node(it._node), _ht(it._ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){if (_node->_next){//当前桶还有节点_node = _node->_next;}else{//当前桶遍历完了KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();//确认当前桶的位置++hashi;//从下一个桶开始遍历while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){//找到了下一个桶_node = _ht->_tables[hashi];break;}++hashi;}if (hashi == _ht->_tables.size()){//没找到下一个桶_node = nullptr;}}return *this;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}};//定义哈希表template<class K, class T, class KeyOfT, class Hash>class HashTable{typedef HashNode<T> Node;//友元声明template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>friend struct __HTIterator;//获取下一个质数inline unsigned long __stl_next_prime(unsigned long n){static const int __stl_num_primes = 28;//定义质数列表大小static const unsigned long __stl_prime_list[__stl_num_primes] ={53,         97,         193,       389,       769,1543,       3079,       6151,      12289,     24593,49157,      98317,      196613,    393241,    786433,1572869,    3145739,    6291469,   12582917,  25165843,50331653,   100663319,  201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);//返回大于等于n的质数return pos == last ? *(last - 1) : *pos;}public:typedef __HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;typedef __HTIterator<K, T, const T&, const T*, KeyOfT, Hash> ConstIterator;Iterator Begin(){for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return Iterator(cur, this);}}return End();}Iterator End(){return Iterator(nullptr, this);}ConstIterator Begin() const{for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return ConstIterator(cur, this);}}return End();}ConstIterator End() const{return ConstIterator(nullptr, this);}//构造HashTable(){_tables.resize(__stl_next_prime(0), nullptr);}//析构~HashTable(){for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}//插入pair<Iterator, bool> Insert(const T& data){KeyOfT kot;Iterator it = Find(kot(data));if (it != End())return make_pair(it, false);Hash hs;//扩容if (_n == _tables.size()){size_t newSize = __stl_next_prime((unsigned long)(_tables.size() + 1));//新空间大小vector<Node*> newTables(newSize, nullptr);//新空间//遍历旧表,把旧把数据重新装到新表for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];//循环遍历桶while (cur){Node* next = cur->_next;//先保存一份下一个节点//计算新位置size_t hashi = hs(kot(cur->_data)) % newSize;//将cur头插到新表cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;//接着遍历当前桶(如果还有节点)}_tables[i] = nullptr;//将旧表置空}_tables.swap(newTables);//交换}//除留余数法计算映射位置size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);//创建新节点//头插到哈希桶newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(Iterator(newnode, this), true);}//查找Iterator Find(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];//遍历桶查找while (cur){if (kot(cur->_data) == key){return Iterator(cur, this);}cur = cur->_next;}return Iterator(nullptr, this);}//删除bool Erase(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();//先计算映射位置Node* prev = nullptr;//用于记录cur的上一个节点Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){//头删if (prev == nullptr){_tables[hashi] = cur->_next;}else//不是头删{prev->_next = cur->_next;}//删除curdelete cur;--_n;return true;}//没找到,继续遍历下一个节点prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _tables;//指针数组size_t _n = 0;//有效数据个数};
}

unorderedset.h:

#pragma once
#include "HashTable.h"namespace txp
{template<class K, class Hash = hash_bucket::HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator const_iterator;iterator begin(){return _ht.Begin();}iterator end(){return _ht.End();}const_iterator begin() const{return _ht.Begin();}const_iterator end() const{return _ht.End();}pair<iterator, bool> insert(const K& key){return _ht.Insert(key);}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}private:hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;};
}

unorderedmap.h:

#pragma once
#include "HashTable.h"namespace txp
{template<class K, class V, class Hash = hash_bucket::HashFunc<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator const_iterator;iterator begin(){return _ht.Begin();}iterator end(){return _ht.End();}const_iterator begin() const{return _ht.Begin();}const_iterator end() const{return _ht.End();}pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = insert({ key, V() });return ret.first->second;}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}private:hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};
}


总结

        以上就是本文的全部内容了,感谢支持。

http://www.dtcms.com/a/351431.html

相关文章:

  • 深入剖析悲观锁、乐观锁与分布式锁
  • 如何才能使RISC V架构成为机器学习的核心
  • U-Net图像语义分割中梯度下降的直观解释
  • 动态规划:为什么暴力算法会有重复子问题
  • 深度学习自动驾驶BEV【专业名词解释汇总】
  • VS中创建Linux项目
  • Tomcat的VM options
  • 我在TSX开发中的实用指南:从基础到实战
  • Java大厂面试实战:从Spring Boot到微服务架构的全链路技术解析
  • swift 开发抠图工具实现思路,与代码详解
  • Java全栈开发面试实录:从基础到实战的深度解析
  • Nginx如何实现反向代理和负载均衡器等功能的
  • 要闻集锦|阿里官网调整为四大业务板块;华为云重组多个事业部涉及上千人;群核科技在港交所更新招股书
  • Swift高阶函数-contains、allSatisfy、reversed、lexicographicallyPrecedes
  • 【大前端】实现一个前端埋点SDK,并封装成NPM包
  • 如何避免频繁切换npm源
  • Redis相关命令详解及其原理
  • AI在提升阅读效率的同时,如何加强理解深度?
  • 嵌入式(day34) http协议
  • 使用Java对接印度股票市场API开发指南
  • Markdown学习笔记(4)
  • 计算神经科学数学建模编程深度前沿方向研究(上)
  • 新手向:pip安装指南
  • 《数据之心》——鱼小妖的觉醒(科研篇)
  • DAY 57 经典时序预测模型1
  • 如何在PC上轻松访问iPhone照片(已解决)
  • UE5 PCG 笔记(三) Normal To Density 节点
  • 神经网络参数量计算详解
  • linux服务器监控平台搭建流程
  • 深度学习:卷积神经网络(CNN)