C++:哈希表的实现

一、哈希表的基本概念

   1、负载因子:假设哈希表中已经映射存储了N个值,哈希表的大小为M,那么负载因子 = N / M,负载因子有些地⽅也翻译为载荷因子/装载因子等,他的英文为load/factor。负载因子越大,哈希冲突的概率越高,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低

   2、哈希函数:这是哈希表的核心,它将键(Key)映射为一个数组索引(通常称为哈希值或哈希码)。哈希函数的设计至关重要,因为它直接影响到哈希表的性能。一个好的哈希函数应该能够均匀地分布键的哈希值,以减少哈希冲突。

   3、哈希表(数组):哈希表本质上是一个数组,它的大小(即数组的长度)是预先确定的。数组的每个位置都对应一个哈希值,该位置用于存储键和值的映射关系。

   4、哈希冲突:由于哈希函数可能会将不同的键映射到相同的哈希值,因此需要一种机制来处理这种情况。常见的处理哈希冲突的方法有开放寻址法(如线性探测、二次探测等)和链地址法(也称为哈希链法)


二、开放定址法代码实现一个哈希表

总代码

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct HashFunc<string>
{size_t operator()(const string& s){// BKDRsize_t hash = 0;for (auto ch : s){hash += ch;hash *= 131;}return hash;}
};struct MapKeyOfT
{const K& operator()(const pair<K, V>& kv){return kv.first;}
};template<class K, class V, class MapKeyOfT, class HashFunc>
class Hashtable
{
public:Hashtable():_table(__stl_next_prime(0)),_n(0){}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;}bool Insert(const pair<K, V>& kv){if (_n * 10 / _table.size() >= 7){Hashtable<K, V> newt;newt._table.resize(__stl_next_prime(_table.size() + 1));for (auto e : _table){if (e._state == EXIST){newt.Insert(e._kv);}}_table.swap(newt._table);}MapKeyOfT kot;HashFunc hash;size_t hash0 = hash(kot(kv) % _table.size();size_t hash1 = hash0;size_t i = 1;while (_table[hash1]._state != EMPTY){hash1 = (hash0 + i) % _table.size();i++;}_table[hash1]._kv = kv;_table[hash1]._state = EXIST;_n++;return true;}bool Erase(const K& key){Hashtable<K, V, MapKeyOfT, HashFunc>* pos = Find(key);if (pos){pos->_state = DELETE;_n--;return true;}elsereturn false;}Hashtable<K, V>* Find(const K& key){MapKeyOfT kot;HashFunc hash; size_t hash0 = hash(kot(key)) % _tables.size();size_t hashi = hash0;size_t i = 1;while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state == EXIST&& _tables[hashi]._kv.first == key){return &_tables[hashi];}// 线性探测hashi = (hash0 + i) % _tables.size();++i;}return nullptr;}private:vector<HashtableDate<K, V>> _table;size_t _n;
};

        其基本思想是,当发生哈希冲突(即两个不同的键被映射到哈希表的同一位置)时,通过一定的探测方法找到下一个可用的哈希地址。

原理

哈希函数:首先,需要一个哈希函数将键映射到哈希表的索引。常见的哈希函数包括取模运算(例如,hash_value = key % _table.size()),其中_table.size()是哈希表的大小。

冲突处理:当发生冲突时,开放定址法通过探测序列来找到下一个可用的位置。探测序列可以是线性的、二次的或基于另一个哈希函数的。

线性探测:每次冲突时,按顺序检查下一个位置,直到找到一个空位置或回到起始位置。


        开放定址法的哈希表结构里应有一个枚举类型来记录每个位置的状态(存在、空和删除)

用一个 HashData 结构体来存放节点的数据和状态,最后用 HashTable 类里用一个vector对象来存放一个一个的节点。

开放定址法的哈希表结构:

enum State
{EXIST,EMPTY,DELETE
};template<class K, class V>
struct HashData
{pair<K, V> _kv;State _state = EMPTY;
};template<class K, class V,class>
class HashTable
{ 
private:vector<HashData<K, V>> _tables;size_t _n = 0; // 表中存储数据个数
};

哈希表的插入

         如果我们vector开11个空间,当我们要插入数据的时候,将插入的值 % 容器的容量得到要映射的位置,然后将值插入在这个位置上。

例如:插入{19 , 30 , 52 , 63 , 11 , 22},当插入 19 的时候,先用 19 % 11 得到 8,将 19 插入在 8这个位置上,但当插入 30 的时候,映射的位置也是 8 这时候就发生了哈希冲突, 我们需要判断一下下一个位置能否插入,遇到状态为空的时候就可以插入了,重复存在,直至负载因子 >= 0.7

bool Insert(const pair<K, V>& kv)
{size_t hash0 = kv.first % _table.size();size_t hash1 = hash0;size_t i = 1;while (_table[hash1]._state != EMPTY){hash1 = (hash0 + i) % _table.size();i++;}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;
}

        为了方便取到 pair <K, V> 里的K的数据或类型,我们可以写一个仿函数来得到。

struct MapKeyOfT
{const K& operator()(const pair<K, V>& kv){return kv.first;}
};

        但这里的类型刚刚好是整形,可以用 % ,如果我们要插入字符类型的数据怎么办,它不能映射到对应的值,这时候我们可以写一个仿函数来使得 字符类型的数据变为整形类型。

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct HashFunc<string>
{size_t operator()(const string& s){// BKDRsize_t hash = 0;for (auto ch : s){hash += ch;hash *= 131;}return hash;}
};

        这时候我们的插入代码就变成这样了

bool Insert(const pair<K, V>& kv)
{HashFunc hash;MapKeyOfT kot;size_t hash0 = hash(kot(kv) % _table.size();size_t hash1 = hash0;size_t i = 1;while (_table[hash1]._state != EMPTY){hash1 = (hash0 + i) % _table.size();i++;}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;
}

        但当 负载因子 >= 0.7 的时候,我们需要扩容,确保 负载因子 <= 0.7

	bool Insert(const pair<K, V>& kv){if (_n * 10 / _table.size() >= 7){Hashtable<K, V> newt;newt._table.resize(__stl_next_prime(_table.size() + 1));for (auto e : _table){if (e._state == EXIST){newt.Insert(e._kv);}}_table.swap(newt._table);}MapKeyOfT kot;HashFunc hash;size_t hash0 = hash(kot(kv) % _table.size();size_t hash1 = hash0;size_t i = 1;while (_table[hash1]._state != EMPTY){hash1 = (hash0 + i) % _table.size();i++;}_table[hash1]._kv = kv;_table[hash1]._state = EXIST;_n++;return true;}
}

哈希表的查找

        我们从要查找的值的映射开始寻找,一个接一个的寻找,若都找到状态为空的位置都没有找到,那就查找失败,返回nullptr。

Hashtable<K, V>* Find(const K& key)
{MapKeyOfT kot;HashFunc hash; size_t hash0 = hash(kot(key)) % _tables.size();size_t hashi = hash0;size_t i = 1;while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state == EXIST&& _tables[hashi]._kv.first == key){return &_tables[hashi];}// 线性探测hashi = (hash0 + i) % _tables.size();++i;}return nullptr;
}

哈希表的删除

        哈希表的删除,我们只需要将找到位置的状态设为空状态即可。


bool Erase(const K& key)
{Hashtable<K, V, MapKeyOfT, HashFunc>* pos = Find(key);if (pos){pos->_state = DELETE;_n--;return true;}elsereturn false;
}

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

2024年11月软考考前注意事项

一、重要时间节点 准考证打印时间&#xff1a; 大部分省市的准考证打印时间从11月4日起开始&#xff0c;但上海、甘肃等地区则稍晚&#xff0c;从11月6日起开放打印。 请务必注意所在地区的具体打印时间&#xff0c;并尽早打印准考证&#xff0c;以免因错过时间而影响考试。…

书生大模型实战营Linux+InternStudio 关卡任务

一、端口映射 使用以下命令进行端口映射 ssh -p {YOUR_PORT} rootssh.intern-ai.org.cn -CNg -L 7860:127.0.0.1:7860 -o StrictHostKeyCheckingno 命令解释&#xff1a; -p 37367&#xff1a;是指定 SSH 连接的端口为 37367。rootssh.intern-ai.org.cn&#xff1a;表示要以…

道品科技智能水肥一体化技术要点及实施效果

## 一、引言 水肥一体化技术是现代农业中一种重要的耕作方式&#xff0c;旨在通过合理配置水资源与肥料&#xff0c;提高作物产量和质量&#xff0c;达到节水、增效和环保的目的。随着全球人口的增加和耕地资源的减少&#xff0c;水肥一体化技术在农业生产中的应用愈加重要。 …

sqlserver使用bak文件恢复数据库

进入数据库 sqlcmd -S localhost -U SA -P password备份文件 #备份格式BACKUP DATABASE your_database_name TO DISK path_to_backup_file.bak;#举例 1> BACKUP DATABASE XJZDataTest TO DISK /root/mssql.bak; 2> go使用备份文件恢复数据库 1、查询备份文件中的数据…

CSP/信奥赛C++刷题训练:经典深搜例题(1):洛谷1605 :迷宫

CSP/信奥赛C刷题训练&#xff1a;经典深搜例题&#xff08;1&#xff09;&#xff1a;洛谷1605 &#xff1a;迷宫 题目描述 给定一个 N M N \times M NM 方格的迷宫&#xff0c;迷宫里有 T T T 处障碍&#xff0c;障碍处不可通过。 在迷宫中移动有上下左右四种方式&#x…

yolov8涨点系列之Concat模块改进

文章目录 Concat模块修改步骤(1) BiFPN_Concat3模块编辑(2)在__init_.pyconv.py中声明&#xff08;3&#xff09;在task.py中声明yolov8引入BiFPN_Concat3模块yolov8.yamlyolov8.yaml引入C2f_up模块 在YOLOv8中&#xff0c; concat模块主要用于将多个特征图连接在一起。其具体…

越权访问漏洞

V2Board Admin.php 越权访问漏洞 ## 漏洞描述 V2board面板 Admin.php 存在越权访问漏洞&#xff0c;由于部分鉴权代码于v1.6.1版本进行了修改&#xff0c;鉴权方式变为从Redis中获取缓存判定是否存在可以调用… V2Board Admin.php 越权访问漏洞 漏洞描述 V2board面板 Admin.ph…

安装和运行开发微信小程序

下载HBuilder uniapp官网 uni-app官网 微信开发者工具 安装 微信小程序 微信小程序 官网 微信小程序 配置 运行 注意&#xff1a;运行前需要开启服务端口 如果运行看不到效果&#xff0c;设置下基础库选别的版本 配置

小檗碱和卤代苄基异喹啉生物碱的代谢工程合成-文献精读79

De novo biosynthesis of berberine and halogenated benzylisoquinoline alkaloids in Saccharomyces cerevisiae 在 酿酒酵母&#xff08;Saccharomyces cerevisiae&#xff09;中从头合成小檗碱和卤代苄基异喹啉生物碱 小檗碱的酵母代谢工程生物合成-文献精读78 苄基异喹…

鸿蒙开发案例:七巧板

【1】引言&#xff08;完整代码在最后面&#xff09; 本文介绍的拖动七巧板游戏是一个简单的益智游戏&#xff0c;用户可以通过拖动和旋转不同形状的七巧板块来完成拼图任务。整个游戏使用鸿蒙Next框架开发&#xff0c;利用其强大的UI构建能力和数据响应机制&#xff0c;实现了…

【TS】九天学会TS语法——1.TypeScript 是什么

今天学习的是TypeScript 基础&#xff0c;目标是了解 TypeScript 的基本概念&#xff0c;安装 TypeScript&#xff0c;编写第一个 TypeScript 程序。 TypeScript 简介安装 TypeScriptTypeScript 编译过程编写第一个 TypeScript 程序 随着前端开发的不断发展&#xff0c;TypeScr…

Docker学习—Docker的安装与使用

Docker安装 1.卸载旧版 首先如果系统中已经存在旧的Docker&#xff0c;则先卸载&#xff1a; yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine2.配置Docker的yum库 首先…

69.ov5640摄像头HDMI灰度显示

&#xff08;1&#xff09;理论学习 灰度像素&#xff1a;在 RGB 颜色模型下&#xff0c;图像中每个像素颜色的 R、G、B 三种基色的分量值相等的像素。由灰度像素组成的灰度图像只能表现256中颜色&#xff08;或亮度&#xff09;&#xff0c;通常把灰度图像中像素的亮度称为灰…

Star Tower:开启数据存储新纪元

在科技飞速发展的当今时代&#xff0c;数据如同璀璨的星辰&#xff0c;闪耀着无尽的价值。而数据存储系统&#xff0c;则是承载这些星辰的浩瀚宇宙。Star Tower 以其卓越的性能和创新的理念&#xff0c;开启了数据存储的新纪元。 Star Tower 的分布式存储架构&#xff0c;是一…

基于SSM的企业管理系统(源码+lw+调试+技术指导)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

鸿蒙应用App测试-通用测试

注意&#xff1a;大家记得学完通用测试记得再学鸿蒙专项测试 鸿蒙应用App测试-专项测试&#xff08;DevEco Testing&#xff09;-CSDN博客 注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得…

掌握Qt调试技术

文章目录 前言一、Qt调试的基本概念二、Qt调试工具三、Qt调试实践四、Q调试技巧五、总结前言 在软件开发中,调试是一个至关重要的环节。Qt作为一个广泛使用的跨平台C++图形用户界面应用程序开发框架,其调试技术也显得尤为重要。本文将深入探讨Qt调试技术,帮助读者更好地掌握…

Qt中时间戳转化为时间

QT中时间和时间戳互相转化_currentsecssinceepoch-CSDN博客 qDebug()<<QDateTime::currentMSecsSinceEpoch(); 1730838034770 时间戳(Unix timestamp)转换工具 - 在线工具 (tool.lu) [static] qint64 QDateTime::currentMSecsSinceEpoch() Returns the number of milli…

势不可挡 创新引领 | 生信科技SOLIDWORKS 2025新品发布会·苏州站精彩回顾

2024年11月01日&#xff0c;由生信科技举办的SOLIDWORKS 2025新产品发布会在江苏苏州圆满落幕。现场邀请到制造业的专家学者们一同感受SOLIDWORKS 2025最新功能&#xff0c;探索制造业数字化转型之路。 在苏州站活动开场&#xff0c;达索系统专业客户事业部华东区渠道经理马腾飞…

ArcGIS006:ArcMap常用操作151-200例动图演示

摘要&#xff1a;本文介绍了ArcMap邻域分析、栅格表面分析、水文分析、区域分析、提取分析等工具箱中的工具功能。包括计算图层间点、线、面要素间的距离、位置和角度&#xff0c;创建缓冲区&#xff0c;添加计算信息到属性表&#xff0c;分割面要素&#xff0c;编号&#xff0…