【C++】unordered_map,unordered_set模拟实现

unordered_map,unordered_set模拟实现

  • 插入
  • 普通迭代器
  • const迭代器
  • unordered_map的[ ]接口实现
  • 查找+修改
  • 哈希桶完整代码
  • unordered_map完整代码
  • unordered_set完整代码

在这里插入图片描述

喜欢的点赞,收藏,关注一下把!在这里插入图片描述

上一篇文章我们把unordered_map和unordered_set底层哈希桶的知识也都说清楚了,今天就根据哈希桶模拟实现出unordered_map和unordered_set。

这里如果看过以前文章【C++】map和set的模拟实现,应该会觉得简单。

因为unordered_map和unordered_set底层都是哈希桶,因此我们只需要一个哈希桶就好了。所以哈希桶的代码我们做一下修改,让它能够同时满足unordered_map和unordered_set的需求。

unordered_map是KV模型,unordered_set是K模型,因此先把链表节点改一下,传个T,这个T既可以是KV,也可以是K。

template<class T>
struct HashNode
{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data),_next(nullptr){}
};

插入这里也要改一下,因为find我们只要key,但是unordered_map是pair,我们要把key取出来,因此HashTable需要增加一个模板参数,这个模板参数分别由unordered_set、unordered_map传过来一个仿函数。作用是把key取出来

bool Insert(const T& data)
{KeyOfT kot;//不知道T是key还是pairif (Find(kot(data))//仿函数把key取出来return false;//负载因子控制在1,超过就扩容if (_n == _tables.size()){vector<Node*> newtable;//newtable.resize(_tables.size() * 2);newtable.resize(__stl_next_prime(_tables.size()));for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];//头插到新表while (cur){Node* next = cur->_next;size_t hashi = Hash()(kot(data)) % newtable.size();cur->_next = newtable[hashi];newtable[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtable);//旧表和新表交换一下}//匿名对象去调用仿函数,算在第几个桶里面int hashi = Hash()(kot(data)) % _tables.size();//头插Node* newnode = new Node(data);//调用Node的构造,因此Node写个构造newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;
}

然后再说这个模板参数Hash的问题

在这里插入图片描述

可以看见keyOfT写在Hash后面,Hash报错了,因为它有缺省值keyOfT没有。当然你也可以把Hash放在keyOfT后面,但是这里的问题不是这个。因为Hash在HashTable不应该有缺省值,应该是由它的上层unordered_map和unordered_set传过去。为什么?

因为不见得你以后写一个vector< string >这样类似的自定义类型底层可以把它转成整数,还是要由调用的unordered_map和unordered_set人传一个能把这样类型转成整数的仿函数。所以把Hash的缺省值放在unordered_map和unordered_set的模板参数中。

下面我们把unordered_map和unordered_set模拟实现大框架搭建出来

//UnorderedMap.h
namespace wdl
{template<class K,class V,class Hash=HashFunc<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:private://第二个参数决定是KV模型HashTable<K, pair<const K, V>, Hash, MapKeyOfT> _ht;};
}//UnorderedSet.h
namespace wdl
{template<class K,class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:private://第二个参数决定是K模型HashTable<K, K, Hash, SetKeyOfT> _ht;};
}

插入

复用哈希桶的插入

//UnorderedMap.h
bool insert(const pair<K, V>& kv)
{return _ht.Insert(kv);
}//UnorderedSet.h
bool insert(const K& key)
{return _ht.Insert(key);
}

插入很简单,但注意到库里的插入返回的是pair,插入成功是返回指向这个节点的指针和true,插入失败也就是插入的是相同的值返回指向这个相同节点的指针和false

在这里插入图片描述

所以下面我们先实现迭代器在把插入完善

普通迭代器

思考这样一个问题,++怎么走,如何做到把一个桶走完了找到下一个桶?

在这里插入图片描述

可不可以把这些桶都链接起来?

在这里插入图片描述

这样遍历起来就方便了,找到第一个桶的第一个位置然后next往下走就像单链表一样遍历就走完了。如果查找的话给每个桶最后一个节点标记位这样也可以解决找别的桶去了的问题。
但是真正麻烦的是插入怎么办?

在这里插入图片描述

插入13,要找到上一个桶的最后一个节点连接起来,再找到下一个桶的第一个节点连接起来。

删除怎么办?

在这里插入图片描述

删除13,不还是要找到上一个桶最后一个节点和下一个桶第一个节点然后连接起来吗。

其实哈希桶的遍历也很简单,无非就是当前桶走完了找下一个桶,如果我们有这个表的话,当前桶走完遍历一下找到下一个桶不就好了吗。因此我们不仅要有这个节点的指针,还要有这个表。库里就是这样的实现方式。

在这里插入图片描述
通过哈希表的指针找到这张表。

template<class K, class T, class Hash, class KeyOfT>
class __HTIterator
{typedef HashNode<T> Node;typedef __HTIterator<K, T, Hash, KeyOfT> Self;Node* _node;//节点的指针Self& operator++(){if (_node->_next){_node = _node->_next;//遍历当前桶的所有节点}else{//当前桶走完,要找下一个桶的第一个节点Hash hf;KeyOfT kot;size_t hashi = hf(kot(_node->_data)) % ;//计算当前在那个桶}}
};

要模表长,因此把表的指针传过去,当然也可以只把vector传过去。

template<class K, class T, class Hash, class KeyOfT>
class __HTIterator
{typedef HashNode<T> Node;typedef __HTIterator<K, T, Hash, KeyOfT> Self;typedef HashTable<K, T, Hash, KeyOfT> HT;Node* _node;//节点的指针HT* _ht;//哈希表的指针Self& operator++(){if (_node->_next){_node = _node->_next;//遍历当前桶的所有节点}else{//当前桶走完,要找下一个桶的第一个节点Hash hf;KeyOfT kot;size_t hashi = hf(kot(_node->_data)) % _ht->_tables.size();//计算当前在那个桶}}
};

但是vector在HashTable可是私有成员。这里不能直接用。

在这里插入图片描述

因此在HashTable声明一下__HTIterator是HashTable的友元类就可以用HashTable私有成员

在这里插入图片描述

Self& operator++()
{//_node最开始指向第一个存数据桶的第一个节点if (_node->_next){_node = _node->_next;//遍历当前桶的所有节点}else{//当前桶走完,要找下一个存数据桶的第一个节点Hash hf;KeyOfT kot;size_t hashi = hf(kot(_node->_data)) % _ht->_tables.size();//计算当前在那个桶++hashi;//从下一个桶开始找while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){_node = _ht->_tables[hashi];//找到下一个存数据桶的第一个节点break;}else{++hashi;}}//走到表的结束,没找到下一个桶if (hashi == _ht->_tables.size()){_node == nullptr;}}return *this;
}

接下来我们把剩下的都补充一下,

template<class K, class T, class Hash, class KeyOfT>
class __HTIterator
{typedef HashNode<T> Node;typedef __HTIterator<K, T, Hash, KeyOfT> Self;typedef HashTable<K, T, Hash, KeyOfT> HT;Node* _node;//节点的指针HT* _ht;//哈希表的指针public:__HTIterator(Node* node,HT* ht):_node(node),_ht(ht){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}Self& operator++(){//_node最开始指向第一个存数据桶的第一个节点if (_node->_next){_node = _node->_next;//遍历当前桶的所有节点}else{//当前桶走完,要找下一个存数据桶的第一个节点Hash hf;KeyOfT kot;size_t hashi = hf(kot(_node->_data)) % _ht->_tables.size();//计算当前在那个桶++hashi;//从下一个桶开始找while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){_node = _ht->_tables[hashi];//找到下一个存数据桶的第一个节点break;}else{++hashi;}}//走到表的结束,没找到下一个桶if (hashi == _ht->_tables.size()){_node == nullptr;}}return *this;}
};

在把哈希桶的begin和end写一下。

template<class K, class T, class Hash ,class KeyOfT>
class HashTable
{typedef HashNode<T> Node;//友元类template<class K, class T, class Hash, class KeyOfT>friend class __HTIterator;
public:typedef __HTIterator<K, T, Hash, KeyOfT> iterator;public:iterator begin(){//找第一个桶for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i])return iterator(_tables[i], this);//哈希表对象的指针通过this得到}return iterator(nullptr, this);}iterator end(){//走到最后一个桶的最后一个节点在++就是nullptrreturn iterator(nullptr, this);}//。。。
}

但是这里还有问题,会编译报错。

在这里插入图片描述

因为这里存在相互引用的问题,

在这里插入图片描述

迭代器要哈希表,哈希表要迭代器。
以前是容器要迭代器,迭代器在前面,容器就可以直接用迭代器了。但是现在存在相互引用的问题。
解决方法:把哈希表放在前面前置声明一下

在这里插入图片描述

这样就没问题了。

把unordered_map和unordered_set加上迭代器

//UnorderedMap.h
namespace wdl
{template<class K,class V,class Hash=HashFunc<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:typedef typename HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}bool insert(const pair<K, V>& kv){return _ht.Insert(kv);}private://第二个参数决定是KV模型HashTable<K, pair<const K, V>, Hash, MapKeyOfT> _ht;};
}//UnorderedSet.h
namespace wdl
{template<class K,class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}bool insert(const K& key){return _ht.Insert(key);}private://第二个参数决定是K模型HashTable<K, K, Hash, SetKeyOfT> _ht;};
}

HashTable没有实例化不能区分iterator是一个静态变量还是内置类型,因为静态变量也可以这样用,因此加个typename告诉编译器这是一个类型,先不要报错等实例化之后在去取。

跑一下unordered_set的迭代器看一眼效果

在这里插入图片描述

能正常跑是没错。

但真的没问题吗?

在这里插入图片描述

K模型竟然可以改变key值。
这里就如同模拟实现set一样的问题。所以unordered_set迭代器都改成const迭代器。

在这里插入图片描述

const迭代器

这里先和以前一样的实现。const迭代器和普通迭代器共用一个模板,因为参数传的不同,所以同一份模板可以实例化出不同对象

//前置声明
template<class K, class T, class Hash, class KeyOfT>
class HashTable;template<class K, class T,class Ref,class Ptr, class Hash, class KeyOfT>
class __HTIterator
{typedef HashNode<T> Node;typedef __HTIterator<K, T, Ref, Ptr, Hash, KeyOfT> Self;typedef HashTable<K, T, Hash, KeyOfT> HT;Node* _node;//节点的指针HT* _ht;//哈希表的指针public:__HTIterator(Node* node,HT* ht):_node(node),_ht(ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){//_node最开始指向第一个存数据桶的第一个节点if (_node->_next){_node = _node->_next;//遍历当前桶的所有节点}else{//当前桶走完,要找下一个存数据桶的第一个节点Hash hf;KeyOfT kot;size_t hashi = hf(kot(_node->_data)) % _ht->_tables.size();//计算当前在那个桶++hashi;//从下一个桶开始找while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){_node = _ht->_tables[hashi];//找到下一个存数据桶的第一个节点break;}else{++hashi;}}//走到表的结束,没找到下一个桶if (hashi == _ht->_tables.size()){_node = nullptr;}}return *this;}bool operator!=(const Self& s){return _node != s._node;}
};template<class K, class T, class Hash ,class KeyOfT>
class HashTable
{typedef HashNode<T> Node;//友元类template<class K, class T,class Ref,class Ptr, class Hash, class KeyOfT>friend class __HTIterator;public:typedef __HTIterator<K, T,T&,T*, Hash, KeyOfT> iterator;typedef __HTIterator<K, T, const T&, const T*, Hash, KeyOfT> const_iterator;public:iterator begin(){//找第一个桶for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i])return iterator(_tables[i], this);}return iterator(nullptr, this);}iterator end(){return iterator(nullptr, this);}const_iterator begin() const{//找第一个桶for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i])return const_iterator(_tables[i], this);}return const_iterator(nullptr, this);}const_iterator end() const{return const_iterator(nullptr, this);}

再把unordered_set的迭代器修改一下

namespace wdl
{template<class K,class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename HashTable<K, K, Hash, SetKeyOfT>::const_iterator iterator;typedef typename HashTable<K, K, Hash, SetKeyOfT>::const_iterator const_iterator;//加上const普通对象和const对象都能调用begin//但是因此对象不同所以调用哈希表的begin也是不同的//普通对象走普通的begin,const对象走const的beginiterator begin() const{return _ht.begin();}iterator end() const{return _ht.end();}bool insert(const K& key){return _ht.Insert(key);}private://第二个参数决定是K模型HashTable<K, K, Hash, SetKeyOfT> _ht;};

在这里插入图片描述

但是走const迭代器报了上面的错误。
无构造函数可以接受原类型,或者构造函数重载不明确。这是为什么?
难道哈希表的const迭代器写错了?我们看看库怎么写的。

在这里插入图片描述

注意到库里是把普通迭代器和const迭代器分开写的。并且const迭代器的成员变量都加了const修饰。而且构造的的参数都加上const了。

而我们const迭代器和普通迭代器用的是同一个模板,但是这个模板在调用的是const迭代器构造的时候却没有const修饰参数。
在这里插入图片描述

问题的原因在于
当const对象调用begin的时候走的是下面的begin。而我们在这个begin加了const修饰。这里面隐藏的this指针就变成了 ----> const HashTable* const this。const修饰*this不就是修饰对象本身吗,也就是说对象从一个普通对象变成了const对象。

在这里插入图片描述

再看这个,调用的是vector的[ ]

在这里插入图片描述

你是const对象,那你调用vector的[ ]返回的就是const,所以说vector里面的也是const

在这里插入图片描述

然后调用const_iterator的构造,但是它和普通迭代器用的是同一个构造

在这里插入图片描述

以前说过,指针和引用赋值的权限只能平移或者缩小。但是现在你传过去权限相当于放大了。所以造成了错误!所以const迭代器的构造第一个参数要有const修饰,这样在传过去就是权限平移。不会有问题。并且表的指针也要加上const。

因为this也是const。

在这里插入图片描述

const迭代器成员变量也是const的原因是因为,这里构造的时候也涉及了权限的平移。

在这里插入图片描述

不过这里const迭代器还差一点点东西,我们现在回头把插入写完再说。

template<class K, class T, class Hash, class KeyOfT>
class __HTConstIterator
{typedef HashNode<T> Node;typedef __HTConstIterator<K, T, Hash, KeyOfT> Self;typedef HashTable<K, T, Hash, KeyOfT> HT;const Node* _node;//节点的指针const HT* _ht;//哈希表的指针public:__HTConstIterator(const Node* node, const HT* ht):_node(node), _ht(ht){}const T& operator*(){return _node->_data;}const T* operator->(){return &_node->_data;}Self& operator++(){//_node最开始指向第一个存数据桶的第一个节点if (_node->_next){_node = _node->_next;//遍历当前桶的所有节点}else{//当前桶走完,要找下一个存数据桶的第一个节点Hash hf;KeyOfT kot;size_t hashi = hf(kot(_node->_data)) % _ht->_tables.size();//计算当前在那个桶++hashi;//从下一个桶开始找while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){_node = _ht->_tables[hashi];//找到下一个存数据桶的第一个节点break;}else{++hashi;}}//走到表的结束,没找到下一个桶if (hashi == _ht->_tables.size()){_node = nullptr;}}return *this;}bool operator!=(const Self& s){return _node != s._node;}
};

现在有了迭代器,我们先把哈希表要用的到迭代器的插入和查找改一下

pair<iterator,bool> Insert(const T& data)
{KeyOfT kot;iterator it = Find(kot(data));if (it != end())return make_pair(it, false);//插入失败返回指向这个节点的指针和false//负载因子控制在1,超过就扩容if (_n == _tables.size()){vector<Node*> newtable;//newtable.resize(_tables.size() * 2);newtable.resize(__stl_next_prime(_tables.size()));for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];//头插到新表while (cur){Node* next = cur->_next;size_t hashi = Hash()(kot(data)) % newtable.size();cur->_next = newtable[hashi];newtable[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtable);//旧表和新表交换一下}//匿名对象去调用仿函数,算在第几个桶里面int hashi = Hash()(kot(data)) % _tables.size();//头插Node* newnode = new Node(data);//调用Node的构造,因此Node写个构造newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode,this),true);//插入返回新节点的指针和true
}iterator Find(const K& key)
{size_t hashi = Hash()(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (KeyOfT()(cur->_data) == key)return iterator(cur,this);//找到返回指向这个节点的指针elsecur = cur->_next;}return end();//没找到返回nullptr
}

再把unordered_set和unordered_map的插入修改一下

namespace wdl
{template<class K,class V,class Hash=HashFunc<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:typedef typename HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::iterator iterator;typedef typename HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::const_iterator const_iterator;pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}//。。。private://第二个参数决定是KV模型HashTable<K, pair<const K, V>, Hash, MapKeyOfT> _ht;};
}//UnorderedSet.h
namespace wdl
{template<class K,class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename HashTable<K, K, Hash, SetKeyOfT>::const_iterator iterator;typedef typename HashTable<K, K, Hash, SetKeyOfT>::const_iterator const_iterator;pair<iterator, bool> insert(const K& key){return _ht.Insert(key);}//。。。private://第二个参数决定是K模型HashTable<K, K, Hash, SetKeyOfT> _ht;};

在这里插入图片描述

unordered_set的插入又报了老错误,返回类型不匹配的问题,因为unordered_set的迭代器都是const迭代器,而哈希表插入返回的普通迭代器。这里和库的解决方法一样。在const迭代器写一个拷贝构造函数,把iterator构造成const_iterator。

在这里插入图片描述

template<class K, class T, class Hash, class KeyOfT>
class __HTConstIterator
{typedef HashNode<T> Node;typedef  __HTConstIterator<K, T, Hash, KeyOfT> Self;typedef HashTable<K, T, Hash, KeyOfT> HT;typedef __HTIterator< K, T, Hash, KeyOfT > iterator;const Node* _node;//节点的指针const HT* _ht;//哈希表的指针public:__HTConstIterator(const Node* node, const HT* ht):_node(node), _ht(ht){}//拷贝构造,把iterator构造成const_iterator__HTConstIterator(const iterator& it):_node(it._node),_ht(it._ht){}//。。。
};

自此我们的const彻底写完了。

现在unordered_set的插入就没问题了,顺便也把unordered_map的插入补上

//UnorderedSet.h
namespace wdl
{template<class K,class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename HashTable<K, K, Hash, SetKeyOfT>::const_iterator iterator;typedef typename HashTable<K, K, Hash, SetKeyOfT>::const_iterator const_iterator;pair<iterator, bool> insert(const K& key){//先用同一类型接收pair<typename HashTable<K, K, Hash, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);//在把iterator构造成const_iteratorreturn pair<iterator, bool>(ret.first, ret.second);}//。。。private://第二个参数决定是K模型HashTable<K, K, Hash, SetKeyOfT> _ht;};//UnorderedMap.h
namespace wdl
{template<class K,class V,class Hash=HashFunc<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:typedef typename HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::iterator iterator;typedef typename HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::const_iterator const_iterator;pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}//。。。private://第二个参数决定是KV模型HashTable<K, pair<const K, V>, Hash, MapKeyOfT> _ht;};
}

unordered_map的插入直接返回就可以,因为用的是iterator,并且pair里面的K由const修饰,使用普通迭代器不能改变K。

unordered_map的[ ]接口实现

这个在【C++】map和set的使用及注意事项中map有详细介绍,这里不再叙述

V& operator[](const K& key)
{pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;
}

查找+修改

//UnorderedMap.h
iterator find(const K& key)
{return _ht.Find(key);//这里直接返回
}bool erase(const K& key)
{return _ht.Erase(key);
}//UnorderedSet.h
iterator find(const K& key)
{return _ht.Find(key);//这里在返回的时候调用一下const_iterator的拷贝构造
}bool erase(const K& key)
{return _ht.Erase(key);
}

哈希桶完整代码

template<class T>
struct HashNode
{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data),_next(nullptr){}
};//前置声明
template<class K, class T, class Hash, class KeyOfT>
class HashTable;template<class K, class T,class Hash, class KeyOfT>
class __HTIterator
{typedef HashNode<T> Node;typedef __HTIterator<K, T,Hash, KeyOfT> Self;typedef HashTable<K, T, Hash, KeyOfT> HT;
public:Node* _node;//节点的指针HT* _ht;//哈希表的指针public:__HTIterator(Node* node,HT* ht):_node(node),_ht(ht){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}Self& operator++(){//_node最开始指向第一个存数据桶的第一个节点if (_node->_next){_node = _node->_next;//遍历当前桶的所有节点}else{//当前桶走完,要找下一个存数据桶的第一个节点Hash hf;KeyOfT kot;size_t hashi = hf(kot(_node->_data)) % _ht->_tables.size();//计算当前在那个桶++hashi;//从下一个桶开始找while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){_node = _ht->_tables[hashi];//找到下一个存数据桶的第一个节点break;}else{++hashi;}}//走到表的结束,没找到下一个桶if (hashi == _ht->_tables.size()){_node = nullptr;}}return *this;}bool operator!=(const Self& s){return _node != s._node;}
};template<class K, class T, class Hash, class KeyOfT>
class __HTConstIterator
{typedef HashNode<T> Node;typedef  __HTConstIterator<K, T, Hash, KeyOfT> Self;typedef HashTable<K, T, Hash, KeyOfT> HT;typedef __HTIterator< K, T, Hash, KeyOfT > iterator;const Node* _node;//节点的指针const HT* _ht;//哈希表的指针public:__HTConstIterator(const Node* node, const HT* ht):_node(node), _ht(ht){}__HTConstIterator(const iterator& it):_node(it._node),_ht(it._ht){}const T& operator*(){return _node->_data;}const T* operator->(){return &_node->_data;}Self& operator++(){//_node最开始指向第一个存数据桶的第一个节点if (_node->_next){_node = _node->_next;//遍历当前桶的所有节点}else{//当前桶走完,要找下一个存数据桶的第一个节点Hash hf;KeyOfT kot;size_t hashi = hf(kot(_node->_data)) % _ht->_tables.size();//计算当前在那个桶++hashi;//从下一个桶开始找while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){_node = _ht->_tables[hashi];//找到下一个存数据桶的第一个节点break;}else{++hashi;}}//走到表的结束,没找到下一个桶if (hashi == _ht->_tables.size()){_node = nullptr;}}return *this;}bool operator!=(const Self& s){return _node != s._node;}
};template<class K, class T, class Hash ,class KeyOfT>
class HashTable
{typedef HashNode<T> Node;//友元类template<class K, class T, class Hash, class KeyOfT>friend class __HTIterator;template<class K, class T, class Hash, class KeyOfT>friend class __HTConstIterator;public:typedef __HTIterator<K, T,Hash, KeyOfT> iterator;typedef __HTConstIterator<K, T, Hash, KeyOfT> const_iterator;public:iterator begin(){//找第一个桶for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i])return iterator(_tables[i], this);}return iterator(nullptr, this);}iterator end(){return iterator(nullptr, this);}const_iterator begin() const{//找第一个桶for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i])return const_iterator(_tables[i], this);}return const_iterator(nullptr, this);}const_iterator end() const{return const_iterator(nullptr, this);}HashTable():_n(0)//这里虽然没有明确写调用vector构造,但是编译器会按照声明顺序去调用的,所以会自动调用vecto的构造{//_tables.resize(10);//调用HashNode的构造_tables.resize(__stl_next_prime(0));}~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;}}//走到这里会自动调用vector的析构HashTable(const HashTable<K,T,Hash,KeyOfT>& ht):_n(ht._n){_tables.resize(ht._tables.size());for (size_t i = 0; i < ht._tables.size(); ++i){Node* cur = ht._tables[i];if (cur){Node* copy = new Node(cur->_kv);_tables[i] = copy;while (cur->_next){cur = cur->_next;//尾插copy->_next = new Node(cur->_kv);copy = copy->_next;}}}}//赋值重载现代写法 复用拷贝构造HashTable<K, T, Hash, KeyOfT>& operator=(HashTable<K, T, Hash, KeyOfT> ht){_n = ht._n;_tables.swap(ht._tables);return *this;}pair<iterator,bool> Insert(const T& data){KeyOfT kot;iterator it = Find(kot(data));if (it != end())return make_pair(it, false);//负载因子控制在1,超过就扩容if (_n == _tables.size()){vector<Node*> newtable;//newtable.resize(_tables.size() * 2);newtable.resize(__stl_next_prime(_tables.size()));for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];//头插到新表while (cur){Node* next = cur->_next;size_t hashi = Hash()(kot(data)) % newtable.size();cur->_next = newtable[hashi];newtable[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtable);//旧表和新表交换一下}//匿名对象去调用仿函数,算在第几个桶里面int hashi = Hash()(kot(data)) % _tables.size();//头插Node* newnode = new Node(data);//调用Node的构造,因此Node写个构造newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode,this),true);}iterator Find(const K& key){size_t hashi = Hash()(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (KeyOfT()(cur->_data) == key)return iterator(cur,this);elsecur = cur->_next;}return end();}bool Erase(const K& key){size_t hashi = Hash()(key) % _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;//记录被删节点前一个位置while (cur){if (cur->_kv.first == key){if (cur == _tables[hashi])//被删节点是头一个节点{_tables[hashi] = cur->_next;}else {prev->_next = cur->_next;}delete cur;return true;}else{prev = cur;cur = cur->_next;}}return false;}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};//最大质数取得是靠近整型最大数得质数for (size_t i = 0; i < __stl_num_primes; ++i){if (__stl_prime_list[i] > n)return __stl_prime_list[i];}//不用担心会哈希表会扩到最大质数,因为这时对于整型来说都已经差不多48G了return __stl_prime_list[__stl_num_primes - 1];}private:vector<Node*> _tables;size_t _n;
};

unordered_map完整代码

template<class K>
struct HashFunc
{//凡是能转成整型的就转成整型 如负数,如指针,如浮点数//string不能转size_t operator()(const K& key){return (size_t)key;}
};//模板特化
template<>
struct HashFunc<string>
{//BKDRsize_t operator()(const string& key){size_t num = 0;for (auto& ch : key){num *= 131;num += ch;}return num;}
};namespace wdl
{template<class K,class V,class Hash=HashFunc<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:typedef typename HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::iterator iterator;typedef typename HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::const_iterator const_iterator;pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}const_iterator begin() const{return _ht.begin();}const_iterator end() const{return _ht.end();}private://第二个参数决定是KV模型HashTable<K, pair<const K, V>, Hash, MapKeyOfT> _ht;};
}

unordered_set完整代码

template<class K>
struct HashFunc
{//凡是能转成整型的就转成整型 如负数,如指针,如浮点数//string不能转size_t operator()(const K& key){return (size_t)key;}
};//模板特化
template<>
struct HashFunc<string>
{//BKDRsize_t operator()(const string& key){size_t num = 0;for (auto& ch : key){num *= 131;num += ch;}return num;}
};namespace wdl
{template<class K,class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename HashTable<K, K, Hash, SetKeyOfT>::const_iterator iterator;typedef typename HashTable<K, K, Hash, SetKeyOfT>::const_iterator const_iterator;pair<iterator, bool> insert(const K& key){pair<typename HashTable<K, K, Hash, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);return pair<iterator, bool>(ret.first, ret.second);}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}iterator begin() const{return _ht.begin();}iterator end() const{return _ht.end();}private://第二个参数决定是K模型HashTable<K, K, Hash, SetKeyOfT> _ht;};
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/636815.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

爬虫之Cookie获取:利用浏览器模拟一个cookie出来、面对反爬虫、加密的cookie的应对方法

爬虫之Cookie获取&#xff1a;利用浏览器模拟一个cookie出来、面对反爬虫、加密的cookie的应对方法 在爬虫或模拟请求时&#xff0c;特别是获取验证码的时候&#xff0c;反爬虫的网站的cookie或定期失效&#xff0c;复制出来使用是不行的为了应对这种方式&#xff0c;我们可能…

第十一站:多态练习ODU

实现动态切换 ODU.h #pragma once #include <iostream> using namespace std; #define ODU_TYPE_311_FLAG "311" #define ODU_TYPE_335_FLAG "335" enum class ODU_TYPE {ODU_TYPE_311,ODU_TYPE_335,ODU_TYPE_UNKNOW };class ODU{ public:ODU();//发…

Windows下载安装vcpkg并使用它来安装第三方库(visualstudio)

1.使用Git下载vcpkg仓库&#xff08;下载比较慢&#xff0c;个人比较喜欢打开下面网址然后用迅雷下载&#xff0c;速度飞快&#xff09; git clone "https://github.com/Microsoft/vcpkg.git"2.下载好之后解压打开文件夹&#xff0c;双击bootstrap-vcpkg.bat文件&…

中小企业如何快速融资-----股权融资的四种方式(上)

’在企业融资的多种手段中&#xff0c;股权质押融资、股权交易增值融资、股权增资扩股融资和股权的私募融资&#xff0c;逐渐成为中小企业利用股权实现融资的有效方式。随着市场体系和监管制度的完善&#xff0c;产权市场为投融资者搭建的交易平台日益成熟&#xff0c;越来越多…

ChatGPT 到 Word:使用 Writage 进行复制粘贴魔法

ChatGPT 到 Word&#xff1a;使用 Writage 进行复制粘贴魔法 写在前面Writage的使用 写在前面 随着ChatGPT的日益普及&#xff0c;越来越多的人每天依赖它来完成各种任务。无论是寻找信息、语言翻译、解决数学问题&#xff0c;还是精炼复杂的概念和文本&#xff0c;ChatGPT 都…

React里面table组件的时间轴的位置计算

如上图的时间轴位置计算 计算时间轴位置倒是容易&#xff0c;主要是React里面的antd的table组件怎么监听滚动是个问题 /*** &#xff08;月台/时间&#xff09;为150&#xff0c;时间为100&#xff0c;每个格子为120&#xff0c;120px/30分钟4px/分钟* 00:00分为250px* 00:…

muduo 网络库源码解析和使用

1. base 模块 1.1 API 1.1.1 eventfd int eventfd(unsigned int initval, int flags);&#xff08;1&#xff09;类似信号量&#xff1b;其内部保存了一个 uint64_t 计数器 count&#xff0c;使用 initval 初始化&#xff1b; &#xff08;2&#xff09;read 没有设置 EFD…

Suricata-7.0 源码分析之流表建立FlowWorker

一、什么是Flow&#xff1f; 二、Flow是怎么建立的&#xff1f; 三、Flow建立的具体过程是什么&#xff1f; 一、什么是Flow&#xff1f;   在Suricata 7.0中&#xff0c;流Flow是指所有相同五元组&#xff08;协议&#xff0c;源IP&#xff0c;目的IP&#xff0c;源端口&am…

makefile里面的变量使用,系统变量

文章目录 makefile里面的变量使用 makefile里面的变量使用 calc:add.o sub.o multi.ogcc add.o sub.o multi.o calc.cpp -o calcadd.o:add.cppgcc -c add.cpp -o add.osub.o:sub.cppgcc -c sub.cpp -o sub.omulti.o:multi.cppgcc -c multi.cpp -o multi.oclean:rm -rf *.o cal…

Linux第32步_编译ST公司的TF-A源码

正点原子STM32MP157开发板使用的CPU型号是STM32MP157DAA1&#xff0c;而开发板硬件参考了ST公司的STM32MP157 EVK开发板&#xff0c;因此我们在移植的时候需要关注“stm32mp157d-ev1”。 一、了解SDK 包 ST公司会从ARM官方下载TF-A软件包&#xff0c;然后将STM32MP1 芯片添加…

算法总结——单调栈

纵有疾风起&#xff0c;人生不言弃。本文篇幅较长&#xff0c;如有错误请不吝赐教&#xff0c;感谢支持。 文章目录 一、单调栈的定义二、单调栈的应用&#xff1a;寻找左边第一个比它小的数单调栈的思想&#xff08;重点&#xff09;&#xff1a;寻找左边第一个比它小的数的下…

Unity常用的优化技巧集锦

Unity性能优化是面试的时候经常被问道的一些内容&#xff0c;今天给大家分享一些常用的Unity的优化技巧和思路&#xff0c;方便大家遇到问题时候参考与学习。 对啦&#xff01;这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白&#xff0c;也有一些正在从事游…

(C语言)编译和链接

前言͟͟͞͞&#x1f48c;&#xff1a;对于现在的各种编译器而言许多都是好多个功能的集成&#xff0c;那么我们的代码到底是如何去实现的呢&#xff1f;难道我们的计算机可以直接读懂我们所写的代码&#xff0c;并运行吗&#xff1f;对于很多细心的小伙伴们可能会想这样的问题…

Spring Security 优化鉴权注解:自定义鉴权注解的崭新征程

文章目录 1. 引言2. Spring Security基础2.1 Spring Security概述2.2 PreAuthorize注解 3. 自定义鉴权注解的优势3.1 业务语义更明确3.2 参数化鉴权更灵活3.3 可维护性更好 4. 实现自定义鉴权注解4.1 创建自定义注解4.2 实现鉴权逻辑4.3 注册自定义注解和逻辑4.4 使用自定义注解…

Divisibility Problem-codefordes

题目链接&#xff1a;Problem - A - Codeforces 解题思路&#xff1a; 如果 a 能被 b整除&#xff0c;就不需要进行改变&#xff0c;直接输出0&#xff0c;否则输出((a / b) 1) * b - a&#xff0c;找到最小的能被b整除的数。 下面是c代码&#xff1a; #include<iostrea…

探索JAVA神秘运行机制:揭秘JVM内存区域

目录 1. 前文回顾 2.内存区域的划分 2.1 存放类的方法区 2.2 程序计数器 2.3 Java虚拟机栈 2.4 Java堆内存 2.5 其他内存区域 3. 核心内存区域运行流程 4. 总结 1. 前文回顾 上一篇我们一起探索了Java的整体运行流程&#xff0c;类加载器以及类的加载机制&#xff0…

去了字节跳动,才知道年薪 30w 的测试工程师有这么多?

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

会体言一心文-码代-4202

简明版本 最近感悟就是在“常规赛”中&#xff0c;大部分奇技淫巧远不如官方教程。 我使用大模型工具快一年的时间所积累的经验远不如认真看看官方教程。 官方教程 里面有一点就可以秒99%的工具人&#xff0c;“文心一言7*24小时在线&#xff0c;伴你左右。 ” 结合文心一言…

中仕教育:考上选调生之后能不去吗?选调生和公务员哪个比较好?

选调生&#xff0c;是指经过选拔、培训、考核等一系列程序&#xff0c;选拔出的人才。选调生通常需要在基层锻炼一段时间&#xff0c;然后根据工作表现和能力得到提拔。 考上选调生之后能否不去&#xff0c;有以下两种情况。 1.如果通过选调笔试&#xff0c;但是并未参加后续…

2788.按分隔符拆分字符串

前言 力扣还挺上道&#xff08;bushi&#xff09;&#xff0c;今天第一次写每日一题&#xff0c;给了个简单等级的数组题&#xff0c;我只能说&#xff0c;首战告捷&#xff08;小白的呐喊&#xff09;&#xff0c;看看这每日一题我能坚持一天写出来&#xff0c; ok&#xff…