哈希桶封装unordered_map、unordered_set

哈希桶源代码

我们将由下列的哈希桶来模拟封装STL库中的unordered_map和unordered_set

注意:为了实现封装unordered_map和unordered_set,我们需要对下列源码进行优化。

//哈希桶
namespace hashbucket
{template<class K,class V>struct HashNode{HashNode* _next;pair<K, V> _kv;HashNode(const pair<K, V>& kv):_kv(kv),_next(nullptr){}};template<class K,class V>class HashTables{typedef HashNode<K, V> Node;public://构造函数HashTables(){_tables.resize(10);}//析构函数~HashTables(){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 pair<K, V>& kv){if (Find(kv.first)){return false;}//负载因子if (_n == _tables.size())//因子到1开始扩容{//开新表vector<Node*> newtables;newtables.resize(_tables.size() * 2, nullptr);//遍历旧表for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];while (cur){Node* next = cur->_next;//记录下一个的地址size_t hash = cur->_kv.first % newtables.size();//计算哈希值//头插cur->_next = newtables[i];newtables[i] = cur;//更新下一个位置cur = next;}//将表置空_tables[i] = nullptr;}//交换新旧表_tables.swap(newtables);}size_t hash = kv.first % _tables.size();//计算哈希值Node* newnode = new Node(kv);//创建结点//头插newnode->_next = _tables[hash];_tables[hash] = newnode;++_n;return true;}//查找函数Node* Find(const K& key){size_t hash = key % _tables.size();//计算哈希值Node* cur = _tables[hash];//寻找位置while (cur)//cur不为空则继续寻找{if (cur->_kv.first == key)//相同则找到{return cur;//返回找到的地址}//不相同则判断下一个cur = cur->_next;}//出循环还没找到则返回空return NULL;}//删除函数bool Erase(const K& key){size_t hash = key % _tables.size();//计算哈希值Node* prev = nullptr;//记录前地址Node* cur = _tables[hash];//记录当前地址while (cur)//不为空则继续寻找{if (cur->_kv.first == key)//相同则找到{if (prev == nullptr)//如果为头删{_tables[hash] = cur->_next;//将下一个结点地址放到指针数组上}else{prev->_next = cur->_next;//将前一个结点连接后一个地址}delete cur;//删除找到的结点return true;}prev = cur;cur = cur->_next;}//出循环还没找到则删除失败return false;}private:vector<Node*> _tables;size_t _n = 0;};
}

哈希桶的模板参数

这是原始模板:

    template<class K, class V>class HashTables

这是优化后的模板:

    template<class K, class T,class KeyofT>class HashTables

可以看到,这将V变为T,然后多出了KeyofT,这是什么意思呢? 

(V->T请看下面,KeyofT请看仿函数阶段)

class V    --->    class T

首先最基本的:set是K模型,map是KV模型

在set容器中,T是对应着key:

    template<class K>class unordered_set{public://...private:hashbucket::HashTables<K, K, SetKeyofT> _ht;};

 在map容器中,T是对应着key和value组成的键值对:

    template<class K,class V>class unordered_map{public://...private:hashbucket::HashTables<K, pair<const K, V>, MapKeyofT> _ht;};

所以模板T实际的类型是取决于上层使用的是K还是pair<K,V> 

这一切的一切都是为了让哈希桶能够适配两种不同的容器。

 

所以,哈希桶的模板参数改变后,那么结点类的模板参数也需要跟着改变了。(看下面标题) 

结点类的模板参数实现

优化前:

    template<class K,class V>struct HashNode{HashNode* _next;pair<K, V> _kv;HashNode(const pair<K, V>& kv):_kv(kv),_next(nullptr){}};

优化后: 

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

可以看到,这里的_data就是原本的kv键值对数据,而T对应set中的key,map中的kv键值对。 

那么,class KeyofT呢?这里就要说到仿函数了

unordered_map、unordered_set中的仿函数

在unordered_map和unordered_set容器中各自的私有函数分别有着:

它们分别传入底层哈希桶时,T传入的可能是key,也可能是key和value的键值对,如果是键值对,那么就需要将键值对的key提取出来再进行比较,那么此时就需要用到仿函数来提取key。

        //map容器struct MapKeyofT{const K& operator()(const pair<K,V>& kv){return kv.first;}};//set容器struct SetKeyofT{const K& operator()(const K& key){return key;}};

可以看到,我们在这个仿函数中重载了operator(), 这个operator()在map中用来提取kv.first,也就是key值,为了能统一map和set,我们在set也重载了operator()。

所以set传入底层哈希桶就是set的仿函数,map传入底层哈希桶就是map的仿函数。

迭代器类的实现

先查看下列代码:

    //解决冲突的前置声明template<class K, class T, class KeyofT>class HashTables;//迭代器template<class K,class T,class Ref, class Ptr, class KeyofT>struct HTiterator{typedef HashNode<T> Node;//哈希结点的类型typedef HTiterator<K, T, Ref, Ptr, KeyofT> Self;//迭代器类型Node* _node;//结点指针const HashTables<K, T, KeyofT>* _pht;//迭代器要哈希表,哈希表要迭代器,冲突//vector<Node*>* _ptb;//直接使用私有类,就不会冲突了size_t _hash;//用来计算哈希值};

可以看到这里有一个用来解决冲突的前置声明,因为在后续使用迭代器时,我们需要用到哈希表类型,但是这个迭代器类是放在哈希表上面,编译器会往上寻找,找不到,那么就会报错,此时这种情况就是,哈希表需要用到迭代器,迭代器需要用到哈希表,两者冲突了,为了解决这种情况,我们加了个前置声明哈希表,告诉编译器是存在的,往下找就好了。 

构造函数

        //构造函数HTiterator(Node* node, HashTables<K, T, KeyofT>* pht, size_t hash):_node(node), _pht(pht), _hash(hash){}//const构造函数HTiterator(Node* node, const HashTables<K, T, KeyofT>* pht, size_t hash):_node(node), _pht(pht), _hash(hash){}

 *函数重载

        Ref operator*(){return _node->_data;//对地址的解引用,返回对应数据即可}

->函数重载 

        Ptr operator->(){return &_node->_data;//返回数据地址的引用}

!=函数重载 

        bool operator!=(const Self& s){return _node != s._node;//判断两个结点的地址是否不同}

==函数重载 

bool operator==(const Self& s) const
{return _node == s._node; //判断两个结点的地址是否相同
}

 ++函数重载

        Self& operator++(){if (_node->_next)//如果结点的下一个位置不为空{_node = _node->_next;//继续往下走}else//如果结点的下一个位置为空{//开始重新寻找下一个桶++_hash;//哈希值++往后寻找while (_hash < _pht->_tables.size())//当哈希值不超过表的大小的话循环{//如果哈希值对应的位置不为空,那么就找到了if (_pht->_tables[_hash]){_node = _pht->_tables[_hash];//更新结点位置break;//停止循环}//如果为空,出了判定条件,那么哈希值继续自增++_hash;}//如果哈希值超过了表的大小,那么说明没有了,让结点置空if (_hash == _pht->_tables.size()){_node = nullptr;}}return *this;}

迭代器函数的实现

        typedef HTiterator<K, T, T&, T*, KeyofT> iterator;typedef HTiterator<K, T, const T&, const T*, KeyofT> const_iterator;iterator begin(){从表头开始寻找,直到找到第一个不为空的位置,返回该迭代器for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]){return iterator(_tables[i], this, i);}}//如果没找到那么就直接返回空,调用end()即可return end();}iterator end(){//返回nullptrreturn iterator(nullptr, this, -1);}const_iterator begin() const{for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]){return const_iterator(_tables[i], this, i);}}return end();}const_iterator end() const{return const_iterator(nullptr, this, -1);}

优化之后的哈希桶代码

//哈希桶
namespace hashbucket
{//结点template<class T>struct HashNode{HashNode* _next;T _data;HashNode(const T& data):_data(data), _next(nullptr){}};//解决冲突的前置声明template<class K, class T, class KeyofT>class HashTables;//迭代器template<class K,class T,class Ref, class Ptr, class KeyofT>struct HTiterator{typedef HashNode<T> Node;typedef HTiterator<K, T, Ref, Ptr, KeyofT> Self;Node* _node;const HashTables<K, T, KeyofT>* _pht;//迭代器要哈希表,哈希表要迭代器,冲突//vector<Node*>* _ptb;//直接使用私有类,就不会冲突了size_t _hash;HTiterator(Node* node,HashTables<K,T,KeyofT>* pht,size_t hash):_node(node),_pht(pht),_hash(hash){}HTiterator(Node* node, const HashTables<K, T, KeyofT>* pht, size_t hash):_node(node), _pht(pht), _hash(hash){}Self& operator++(){if (_node->_next){_node = _node->_next;}else{++_hash;while (_hash < _pht->_tables.size()){if (_pht->_tables[_hash]){_node = _pht->_tables[_hash];break;}++_hash;}if (_hash == _pht->_tables.size()){_node = nullptr;}}return *this;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}};//哈希表template<class K, class T,class KeyofT>class HashTables{typedef HashNode<T> Node;//友元函数,让外部类能访问私有成员template<class K, class T, class Ref, class Ptr, class KeyofT>friend struct HTiterator;public:typedef HTiterator<K, T, T&, T*, KeyofT> iterator;typedef HTiterator<K, T, const T&, const T*, KeyofT> const_iterator;iterator begin(){for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]){return iterator(_tables[i], this, i);}}return end();}iterator end(){return iterator(nullptr, this, -1);}const_iterator begin() const{for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]){return const_iterator(_tables[i], this, i);}}return end();}const_iterator end() const{return const_iterator(nullptr, this, -1);}//构造函数HashTables(){_tables.resize(10);}//析构函数~HashTables(){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);}//负载因子if (_n == _tables.size())//因子到1开始扩容{//开新表vector<Node*> newtables;newtables.resize(_tables.size() * 2, nullptr);//遍历旧表for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];while (cur){Node* next = cur->_next;//记录下一个的地址size_t hash = kot(cur->_data) % newtables.size();//计算哈希值//头插cur->_next = newtables[i];newtables[i] = cur;//更新下一个位置cur = next;}//将表置空_tables[i] = nullptr;}//交换新旧表_tables.swap(newtables);}size_t hash = kot(data) % _tables.size();//计算哈希值Node* newnode = new Node(data);//创建结点//头插newnode->_next = _tables[hash];_tables[hash] = newnode;++_n;return make_pair(iterator(newnode,this,hash), true);}//查找函数iterator Find(const K& key){KeyofT kot;size_t hash = key % _tables.size();//计算哈希值Node* cur = _tables[hash];//寻找位置while (cur)//cur不为空则继续寻找{if (kot(cur->_data) == key)//相同则找到{return iterator(cur,this,hash);//返回找到的地址}//不相同则判断下一个cur = cur->_next;}//出循环还没找到则返回空return end();}//删除函数bool Erase(const K& key){KeyofT kot;size_t hash = key % _tables.size();//计算哈希值Node* prev = nullptr;//记录前地址Node* cur = _tables[hash];//记录当前地址while (cur)//不为空则继续寻找{if (kot(cur->_data) == key)//相同则找到{if (prev == nullptr)//如果为头删{_tables[hash] = cur->_next;//将下一个结点地址放到指针数组上}else{prev->_next = cur->_next;//将前一个结点连接后一个地址}delete cur;//删除找到的结点return true;}prev = cur;cur = cur->_next;}//出循环还没找到则删除失败return false;}private:vector<Node*> _tables;size_t _n = 0;};}

用哈希桶封装unordered_map的代码

#pragma once
#include"hashtable.h"namespace bear
{template<class K,class V>class unordered_map{struct MapKeyofT{const K& operator()(const pair<K,V>& kv){return kv.first;}};public:typedef typename hashbucket::HashTables<K, pair<const K, V>, MapKeyofT>::iterator iterator;typedef typename hashbucket::HashTables<K, pair<const K, V>, MapKeyofT>::const_iterator 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 = _ht.Insert(make_pair(key, V()));return ret.first->second;}V& operator[](const K& key) const{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);}private:hashbucket::HashTables<K, pair<const K, V>, MapKeyofT> _ht;};
}

用哈希桶封装unordered_set的代码

#pragma once
#include"hashtable.h"namespace bear
{template<class K>class unordered_set{struct SetKeyofT{const K& operator()(const K& key){return key;}};public:typedef typename hashbucket::HashTables<K, K, SetKeyofT>::const_iterator iterator;typedef typename hashbucket::HashTables<K, K, SetKeyofT>::const_iterator 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<const_iterator,bool> Insert(const K& key){auto ret = _ht.Insert(key);return pair<const_iterator, bool>(const_iterator(ret.first._node,ret.first._pht,ret.first._hash),ret.second);}iterator Find(const K& key){return _ht.Find(key);}bool Erase(const K& key){return _ht.Erase(key);}private:hashbucket::HashTables<K, K, SetKeyofT> _ht;};
}

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

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

相关文章

高考作文:时光之河,逐梦前行

时光之河&#xff0c;奔流不息&#xff0c;如同我们的人生旅途&#xff0c;充满了未知与挑战。站在2024年的高考门槛前&#xff0c;我们回望过去&#xff0c;展望未来&#xff0c;心中充满了期待与憧憬。 首先&#xff0c;让我们回顾一下这条时光之河中的点滴。过去的岁月里&am…

区间预测 | Matlab实现QRCNN-BiGRU-Attention分位数回归卷积双向门控循环单元注意力机制时序区间预测

区间预测 | Matlab实现QRCNN-BiGRU-Attention分位数回归卷积双向门控循环单元注意力机制时序区间预测 目录 区间预测 | Matlab实现QRCNN-BiGRU-Attention分位数回归卷积双向门控循环单元注意力机制时序区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实…

针对业务系统的主备容灾实战原理-基础版

1、前言 本文主要在于介绍&#xff1a;通过系统的实时容灾功能模块&#xff0c;针对用户云计算中关键业务系统的主备容灾方案原理。 涉及到的技术能力、运维能力要求偏高&#xff0c;遂本文尽量将容灾原理讲解清楚。需要用到的云计算能力包括&#xff1a;计算机操作系统(Linu…

基于深度学习的红外船舶检测识别分类完整实现数据集8000+张

随着遥感技术的快速发展&#xff0c;包括无人机、卫星等&#xff0c;红外图像在船舶检测识别中的作用日益凸显。相对于可见光图像&#xff0c;红外图像具有在夜晚和恶劣天气条件下高效检测识别船舶的天然优势。近年来&#xff0c;深度学习作为一种强大的图像处理技术&#xff0…

问题:下列可以作为机组投运凝结水精处理系统的指标为()。 #学习方法#经验分享#微信

问题&#xff1a;下列可以作为机组投运凝结水精处理系统的指标为&#xff08;&#xff09;。 A.启动分离器出水含铁量小于1000ug/L B.启动分离器出水含铁量大于1000ug/L C.启动分离器出水含铁量等于1000ug/L D.以上都不是 参考答案如图所示

PowerDesigner遍历导出所有表结构到Excel

PowerDesigner遍历导出所有表到Excel 1.打开需要导出表结构到Excel的pdm文件 2.点击Tools|Execute Commands|Edit/Run Script菜单或按下快捷键Ctrl Shift X打开脚本窗口&#xff0c;输入示例VBScript脚本&#xff0c;修改其中的Excel模板路径及工作薄页签&#xff0c;点Run…

Edge浏览器十大常见问题,一次性解决!

Edge曾被称为最好用的浏览器&#xff0c;拳打Chrome脚踢firefox, 可如今却隐藏着像是播放卡顿、下载缓慢、广告繁多等诸多问题&#xff0c;不知道各位还在用吗&#xff1f; 今天小编收集整理了Edge浏览器十大烦人问题&#xff0c;并提供简单有效的解决办法&#xff0c;让你的E…

springboot+websocket+vue聊天室

目录 一、项目实现内容二、websocket三、实现过程java后端vue前端源代码 WebSocketServer调用spring容器注意事项扩展 一、项目实现内容 http://localhost:8080/websocket?uid1 http://localhost:8080/websocket?uid2 http://localhost:8080/websocket?uid3 二、websocket …

crossover软件安装程序怎么安装 Crossover for Mac切换Windows系统 crossover软件怎么样

CrossOver Mac版是专为苹果电脑用户打造的一款实用工具&#xff0c;这款工具主要方便用户在Mac上运行windows系列的应用程序&#xff0c;用户不需要安装虚拟机就可以实现各种应用程序的直接应用&#xff0c;并且可以实现无缝集成&#xff0c;实现跨平台的复制粘贴和文件互通等&…

YOLOv10开源,高效轻量实时端到端目标检测新标准,速度提升46%

前言 实时目标检测在自动驾驶、机器人导航、物体追踪等领域应用广泛&#xff0c;近年来&#xff0c;YOLO 系列模型凭借其高效的性能和实时性&#xff0c;成为了该领域的主流方法。但传统的 YOLO 模型通常采用非极大值抑制 (NMS) 进行后处理&#xff0c;这会增加推理延迟&#…

【经验分享】不同内网服务器之间利用webdav互传文件

目录 0、前言1、授权webdav应用2、下载webdavclient33、替换相关代码 0、前言 最近&#xff0c;我在处理两台服务器间的文件传输问题时遇到了不少难题。这两台服务器并不处于同一内网环境&#xff0c;导致无法通过SFTP进行文件传输。由于这些服务器属于局域网&#xff0c;并且…

高效文件传输攻略:利用局域网共享实现极速数据同步

最近&#xff0c;我换了一台新电脑&#xff0c;面对两个电脑之间文件备份和传输的问题&#xff0c;感到十分头疼。经过多方了解&#xff0c;我发现可以在原电脑上设置共享文件&#xff0c;然后接收方从共享文件中接受即可&#xff0c;这样可以将局域网的带宽拉满&#xff0c;比…

✔️Vue基础+

✔️Vue基础 文章目录 ✔️Vue基础computed methods watchcomputed计算属性methods计算属性computed计算属性 VS methods方法计算属性的完整写法 watch侦听器&#xff08;监视器&#xff09;watch侦听器 Vue生命周期Vue生命周期钩子 工程化开发和脚手架脚手架Vue CLI 项目目录介…

Nvidia/算能 +FPGA+AI大算力边缘计算盒子:隧道和矿井绘图设备

RockMass 正在努力打入采矿业和隧道工程利基市场。 这家位于多伦多的初创公司正在利用 NVIDIA AI 开发一款绘图平台&#xff0c;帮助工程师评估矿井和施工中的隧道稳定性。 目前&#xff0c;作为安全预防措施&#xff0c;地质学家和工程师会站在离岩石五米远的地方&#xff0…

Lua移植到标准ANSI C环境

本文目录 1、引言2、环境准备2.1 源码下载2.2 项目构建环境准备 3、项目编译3.1 添加main.c3.2 Kconfig选择模块3.3 项目构建3.4 项目编译 4、运行 文章对应视频教程&#xff1a; 在下方喔 ~~~ 欢迎关注 点击图片或链接访问我的B站主页~~~ lau解释器移植与功能验证 1、引言 本…

01Linux的安装,时区,固定IP的配置

Linux系统的简介与安装 Linux简介 计算机是由硬件和软件所组成 硬件&#xff1a;计算机系统中由电子,机械和光电元件等组成的各种物理装置的总称软件&#xff1a;是用户和计算机硬件之间的接口和桥梁&#xff0c;用户通过软件与计算机进行交流(操作系统) 操作系统作为用户和…

WEB漏洞服务能提供哪些帮助

在数字化浪潮的推动下&#xff0c;Web应用程序已成为企业展示形象、提供服务、与用户进行交互的重要平台。然而&#xff0c;随着技术的飞速发展&#xff0c;Web应用程序中的安全漏洞也日益显现&#xff0c;成为网络安全的重大隐患。这些漏洞一旦被恶意攻击者利用&#xff0c;可…

Java 数据库连接(JDBC)的使用,包括连接数据库、执行SQL语句等

一、简介 Java Database Connectivity&#xff08;JDBC&#xff09;是Java应用程序与关系数据库进行交互的一种API。它提供了一组用于访问和操作数据库的标准接口&#xff0c;使开发人员能够使用Java代码执行数据库操作&#xff0c;如查询、插入、更新和删除等。 二、JDBC架构…

gbase 扩容 集群数据同步 主备切换

问题&#xff1a; 问题1磁盘满 1.原本是100G的大小&#xff0c;我们实际还没接入真正业务&#xff0c;昨日空间满了&#xff0c;需要帮忙看下是什么原因导致磁盘满的吗 数据库是每天备份一次&#xff0c;是不是备份的太频繁&#xff0c;还是数据量的问题导致&#xff0c;需要…

[工具探索]富士mini90拍立得使用指南

文章目录 1. 基本功能介绍1.1 相机外观1.2 电池与胶片 2. 设置相机2.1 装入电池2.2 装入胶片 3. 拍摄模式3.1 标准模式3.2 儿童模式3.3 远景模式3.4 双重曝光模式3.5 Bulb&#xff08;B&#xff09;模式3.6 **派对模式**3.7 微距模式3.8 **亮度模式**3.9 **定时拍摄模式**3.10 …