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

哈希表封装实现unordered_set unordered_map

本文用哈希表来实现unordered_set及unordered_map 这里实现的逻辑和大框架和之前的红黑树封装实现map和set非常类似 

和之前的set及map一样 源码中的命名风格是比较乱的 这里就统一 节点存储的类型为T  key对象的类型为K value对象的类型为V 

这里直接把上篇哈希的一些东西拿过来然后简单修改下

HashTable的前三个模版参数和红黑树封装实现set map完全是一样的

第二个模版参数T是存储的类型 

unordered_set传的是K   里面存的就是K类型的对象key

unordered_map传的pair<K,V> 里面存的就是pair类型的对象 

第一个模版参数是因为find erase的功能需要直接用到key 统一额外传过去一个key

第三个参数是可以把unordered_map里面存储的pair类型转换为key  为了统一兼容unordered_set会把key转换为key

第四 第五个模版参数就是上一篇哈希表的内容了 hash是可以把不支持转换为整形的类型如string自定义类型Date等通过一些方式来转换为整形  第五个模版参数是为了支持自定义类型对象==的比较

构造函数初始化空间大小 及插入的扩容还用下面质数表的方式

上篇实现的哈希表是以里面存pair类型的对象实现的 

这里为了统一兼容把insert find erase里面用Rekey类型的对象把之前kv.first直接给变为 tokey(data) 这样不管data是unordered_set的K类型的key还是unordered_map的pair<K,V>的对象都会同样转换为key 

bool insert(T& data)
{Hash toint;Rekey tokey;if (find(tokey(data)))      //新插入元素已经存在 则插入插入失败return false;if (_n / _table.size() == 1)  // 负载因子为1 扩容{HashTable<K, T, Rekey, Hash, HashSame> newhas;newhas._table.resize(__stl_next_prime(_table.size() + 1));//扩容后size改变了 此时需要把原来的数据重新映射到新的vector类型的对象中for (auto& x : _table)  //每一个x是一个节点指针 直接复用insert{Node* head = x;while (x){                                       //把旧表每一个值在新表映射的位置算出来 然后直接把节点移动到新表正确的位置Node* next = x->_next;size_t hashi2 = toint(x->_kv.first) % newhas._table.size();x->_next = newhas._table[hashi2];newhas._table[hashi2] = x;x = next;}}_table.swap(newhas._table);}size_t hashi = toint(tokey(data)) % _table.size();Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;_n++;return true;
}Node* find(K key){Hash toint;Rekey tokey;HashSame ifsa;size_t hashi = toint(key) % _table.size();Node* cur = _table[hashi];while (cur){if (ifsa(tokey(cur->_data), key)){return cur;}elsecur = cur->_next;}return nullptr;
}bool erase(K key){Hash toint;Rekey tokey;HashSame ifsa;size_t hashi = toint(key) % _table.size();Node* cur = _table[hashi];Node* pre = nullptr;while (cur){if (ifsa(tokey(cur->_data), key)){if (_table[hashi] == cur)  // 头节点{_table[hashi] = cur->_next;}else   //中间节点{pre->_next = cur->_next;}delete cur;return true;}else{pre = cur;cur = cur->_next;}}return false;}

insert erase find先不管了 先来实现一下迭代器

这里迭代器的实现和之前的list map set的实现一样 为了实现普通迭代器和const类型迭代器 在原有模版参数的基础上再加两个模版参数Ref Ptr分别作为重载*和重载->的返回值

这样这两个模版参数传的是普通的 T& T*这个迭代器就是普通迭代器

传的是const T& const T*这个迭代器就是const类型的迭代器

先实现* -> != ==的重载 

然后实现一下++的重载

先来分析一下 ++应该是要到下一个节点的位置 如果当前哈希桶的下一个位置还有值的话即当前节点的next指针不为空 下一个节点就应该到next指针指向的位置 如果下一个位置为空即next指针为空了说明当前的哈希桶已经走完了  此时++应该是到下一个不为空的哈希桶的头节点 

所以这里要分两种情况

①当前节点的next指针为空 此时++就是到next指针所指向的位置

②当前节点的next指针为空 要找下一个哈希桶不为空的头节点

对于第一种情况++很容易实现  但是第二种需要找下一个不为空的哈希表 但是这里我们迭代器里面只有一个当前位置的指针  不能往回走所以无法满足我们的需求

所以在迭代器类中我们还需要一个哈希表对象的指针 然后就按照我们分析的实现

struct Hashtable_iterator
{typedef HashNode<T> Node;typedef Hashtable_iterator<K,T,Ref,Ptr,Rekey,Hash,HashSame> Self;typedef HashTable<K, T, Rekey, Hash, HashSame> Ht;Ht* _ht;Node* _node;Hashtable_iterator(Node* node, const Ht* ht):_node(node), _ht(ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& node){return _node != node;}bool operator==(const Self& node){return _node == node;}Rekey tokey;Hash toint;Self& operator++(){size_t hashi;if (_node->_next)   //当前桶下一个位置还有值{_node = _node->next;}else     //当前桶为空  找新的不为空的桶{hashi=toint(tokey(_node->_data)) % _ht->_table.size();  //当前节点所在桶的位置++hashi;while (hashi < _ht->_table.size()){if (_ht->_table[hashi])   //找到新的不为空的哈希桶了{_node = _ht->_table[hashi];}elsehashi++;}//到了最后还没有找到_node = nullptr;}return *this;}};

然后在HashTable里面重命名一下两个不同的迭代器

此时还有两个问题

①在迭代器里面需要用到HashTable里面的_table 但它是私有的 

②HashTable里面要用迭代器的东西 迭代器里面也要用到HashTable的东西 但是他们只会向上找 所以在上面的那一个就会找不到另一个

对于第一种解决方式就是在HashTable做迭代器的友元声明

对于第二个解决方式是把在上面的那一个前面加上另一个的声明 (我这里是迭代器在上面  所以需要在迭代器的前面加上HashTable前置的声明)

这里要注意不管是友元的声明 还是前置声明 模版里面的缺省值都不写

然后再unorderen_set unordered_map里面再重命名一层  这里需要注意需要在前面加typename来告诉编译器我们要取的是类模版里面的类型

然后在HashTable里面实现一下Begin和End

Begin要返回第一个不为空的桶的位置 End就直接返回空的位置  this就是HashTable对象的地址  迭代器用到的是两个参数 直接用两个参数用隐式类型转换的方式来构造迭代器类型

在unordered_set unordered_map里面直接复用HashTable的begin和end就可以了

接下来测试一下写的

在下面测试中才发现有一些细节写错了 通过报错信息及调试修改之后就可以正常运行了

迭代器打印的方式 范围for打印的方式及const类型对象的打印都可以了( 第三个打印调用的print函数 里面的形参是const类型 里面的范围for会调用const类型的迭代器)

map的使用也可以了

和之前map set一样这里的key应该是不支持修改的 所以给K前面都加上const

然后实现一下[]的重载 

在这之前先把insert find一些地方改一下 find返回值变为迭代器类型 insert的返回值变为pair<Iterator,bool>  然后相应return的地方都改一下

在unordered_map里面实现一下[]重载  同样和之前实现map的方式一模一样

[]就可以正常使用了

用哈希表来实现unorder_set unordered_map基本上就完成了

这里把类型转换为key的和支持==的模版参数是写在哈希表里面的 但是平时使用的时候应该是往unorder_set unordered_map里面传

所以这里再改一下 把哈希表里面的缺省值给去掉  然后给unorder_set unordered_map里面的模版参数加上这些模版参数然后给上缺省值

现在哈希表里面的模版参数都必须要传了 unorder_set unordered_map往里面传的后两个就是之前的HashTable的缺省值

这样unorder_set unordered_map的使用就和库中的是一样的了 

完整代码

Hash.h

# include <string>
# include<vector>
# include <iostream>
using namespace std;
namespace xx
{template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};template<class K>class intoint{public:size_t operator()(K x){return (size_t)x;}};template<>class intoint<string>{public:int operator()(string s){int add = 0;for (char x : s){add *= 131;add += x;}return add;}};template <class K>class ifsame{public:bool operator()(K key1, K key2){return key1 == key2;}};inline unsigned long __stl_next_prime(unsigned long n){// Note: assumes long is at least 32 bits.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);return pos == last ? *(last - 1) : *pos;}template<class K, class T, class Rekey, class Hash, class HashSame>class HashTable;template<class K, class T,class Ref,class Ptr, class Rekey, class Hash = intoint<K>, class HashSame = ifsame<K>>struct Hashtable_iterator{typedef HashNode<T> Node;typedef Hashtable_iterator<K,T,Ref,Ptr,Rekey,Hash,HashSame> Self;typedef HashTable<K, T, Rekey, Hash, HashSame> Ht;const Ht* _ht;Node* _node;Hashtable_iterator(Node* node, const Ht* ht):_node(node), _ht(ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& it){return _node !=it._node ;}bool operator==(const Self& it){return _node ==it._node ;}Rekey tokey;Hash toint;Self& operator++(){size_t hashi;if (_node->_next)   //当前桶下一个位置还有值{_node = _node->_next;}else     //当前桶为空  找新的不为空的桶{hashi=toint(tokey(_node->_data)) % _ht->_table.size();  //当前节点所在桶的位置++hashi;while (hashi < _ht->_table.size()){if (_ht->_table[hashi])   //找到新的不为空的哈希桶了{_node = _ht->_table[hashi];break;}elsehashi++;}if (hashi == _ht->_table.size()){//到了最后还没有找到_node = nullptr;}}return *this;}};template<class K, class T, class Rekey, class Hash , class HashSame>class HashTable{public:template<class K, class T, class Ref, class Ptr, class Rekey, class Hash, class HashSame>friend struct Hashtable_iterator;typedef Hashtable_iterator<K, T, T&, T*, Rekey, Hash, HashSame> Iterator;typedef Hashtable_iterator<K, T, const T&, const T*, Rekey, Hash, HashSame> const_Iterator;typedef HashNode<T> Node;HashTable():_table(__stl_next_prime(0)), _n(0){}Iterator Begin(){if (_n == 0)return End();for (auto x : _table){if (x)return { x,this };}return { nullptr,this };}const_Iterator Begin() const{if (_n == 0)return End();for (auto x : _table){if (x)return { x,this };}return { nullptr,this };}Iterator End(){return { nullptr,this };}const_Iterator End() const{return { nullptr,this };}pair<Iterator,bool> insert(T& data){Hash toint;Rekey tokey;Iterator ii = find(tokey(data));if (ii!=End())      //新插入元素已经存在 则插入插入失败return { ii, false };if (_n / _table.size() == 1)  // 负载因子为1 扩容{HashTable<K, T, Rekey, Hash, HashSame> newhas;newhas._table.resize(__stl_next_prime(_table.size() + 1));//扩容后size改变了 此时需要把原来的数据重新映射到新的vector类型的对象中for (auto& x : _table)  //每一个x是一个节点指针 直接复用insert{Node* head = x;while (x){                                       //把旧表每一个值在新表映射的位置算出来 然后直接把节点移动到新表正确的位置Node* next = x->_next;size_t hashi2 = toint(tokey(x->_data)) % newhas._table.size();x->_next = newhas._table[hashi2];newhas._table[hashi2] = x;x = next;}}_table.swap(newhas._table);}size_t hashi = toint(tokey(data)) % _table.size();Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;_n++;return { {newnode,this},true };}Iterator find(K key){Hash toint;Rekey tokey;HashSame ifsa;size_t hashi = toint(key) % _table.size();Node* cur = _table[hashi];while (cur){if (ifsa(tokey(cur->_data), key)){return {cur,this};}elsecur = cur->_next;}return {nullptr,this};}bool erase(K key){Hash toint;Rekey tokey;HashSame ifsa;size_t hashi = toint(key) % _table.size();Node* cur = _table[hashi];Node* pre = nullptr;while (cur){if (ifsa(tokey(cur->_data), key)){if (_table[hashi] == cur)  // 头节点{_table[hashi] = cur->_next;}else   //中间节点{pre->_next = cur->_next;}delete cur;return true;}else{pre = cur;cur = cur->_next;}}return false;}private:vector<Node*> _table;  // 指针数组size_t _n;};
}

unordered_set.h

# include "Hash.h"namespace xx
{template<class K, class Hash = intoint<K>, class HashSame = ifsame<K>>class unordered_set{struct unsetRekey{K operator()(K key){return key;}};public:typename typedef HashTable<K,const K, unsetRekey, intoint<K>, ifsame<K>>::Iterator iterator;typename typedef HashTable<K,const K, unsetRekey, intoint<K>, ifsame<K>>::const_Iterator const_iterator;iterator begin(){return _ht.Begin();}const_iterator begin() const{return _ht.Begin();}iterator end(){return _ht.End();}const_iterator end() const{return _ht.End();}pair<iterator,bool> insert(K& key){return _ht.insert(key);}iterator find(K key){return _ht.find(key);}bool erase(K key){return _ht.erase(key);}private:HashTable<K,const K, unsetRekey, intoint<K>, ifsame<K>> _ht;};void print(const unordered_set<int>& s){for (auto e :s ){cout << e << " ";}cout << endl;}
}

unordered_map.h

# include "Hash.h"namespace xx
{template<class K,class V, class Hash = intoint<K>, class HashSame = ifsame<K>>class unordered_map{ struct unmapRekey{K operator()(pair<K, V> kv){return kv.first;}};public:typename typedef  HashTable<K, pair<const K, V>, unmapRekey, intoint<K>, ifsame<K>>::Iterator iterator;typename typedef  HashTable<K, pair<const K, V>, unmapRekey, intoint<K>, ifsame<K>>::const_Iterator const_iterator;iterator Begin(){return _ht.begin();}const_iterator Begin() const{return _ht.begin();}iterator end(){return _ht.End();}const_iterator end() const{return _ht.End();}pair<iterator,bool> insert(pair<K, V>& kv){return _ht.insert(kv);}iterator find(K key){return _ht.find(key);}bool erase(K key){return _ht.erase(key);}V& operator[](K key){pair<iterator,bool>is= insert({key,V()});is.first->second;}private:HashTable<K, pair<const K, V>, unmapRekey, intoint<K>, ifsame<K>> _ht;};
}

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

相关文章:

  • 网站开发的可行性报告有没有做那个的视频网站
  • RNN基础:序列数据处理与RNN原理(附Python代码)
  • 西安网站建设哪家强怎么做网站优
  • PHP文件与本地及外部资源的深度交互指南
  • Go基础:用Go语言操作redis详解
  • 网站切图怎么切一家三口的室内设计方案
  • AI面试经历与公司情况和职业发展分析与建议
  • 一个网站建设哪家快上海市普陀区建设规划局网站
  • HTTP相关
  • 【代码随想录day 35】 力扣 01背包问题 一维
  • 湖南网站开发公司电话江门网站
  • 适用于自动化脚本的PDF查看器?
  • 高校网站首页设计wordpress 自动保存
  • 机器学习中的决策树
  • 洛谷 - dp 题目详解 4(超详细版)
  • Weight decay 和 L2 Regularization
  • 游戏源码网站免费企业网站建设教程 pdf
  • 全网品牌营销泰安搜索引擎优化招聘
  • Win11上VS Code免输密码连接Ubuntu的正确设置方法
  • 江西建设推广网站百度seo培训课程
  • 基于RT-Thread的STM32开发第十讲——CAN通讯
  • Transformer时序预测模型对比传统LSTM的优劣
  • 随机试验中异质性处理效应的通用机器学习因果推断
  • ClaudeCode真经第七章:未来发展与技术展望
  • 利用DeepSeek辅助给duckdb_pgwire插件添加psql终端输出int128功能
  • 做网站在百度云盘登录
  • 亿企邦网站建设服务器租用免费试用
  • Coze源码分析-资源库-编辑知识库-后端源码-应用/领域/数据访问层
  • 做移动网站点击软件吗网站后台管理系统安装
  • 网站统计排名哪家网站雅虎全球购做的好