哈希表的底层实现(1)---C++版
目录
哈希表的基本原理
哈希表的优点
哈希表的缺点
应用场景
闭散列法
开散列法
开放定值法Open Addressing——线性探测的模拟实现
超大重点部分评析
链地址法Separate Chaining——哈希桶的模拟实现
哈希表(Hash Table)是一种数据结构,它通过将键(Key)映射到值(Value)的方式来实现快速的数据存储与查找。哈希表的核心概念是哈希函数,该函数用于将输入的键转换为哈希值(通常是一个整数),并根据这个哈希值将数据存储在数组中的特定位置。
哈希表的基本原理
-
哈希函数:这是哈希表的关键部分,它接收一个键并返回一个整数(哈希值)。理想情况下,哈希函数会将不同的键均匀地分布到数组的不同位置上。
-
数组(桶,Bucket):哈希表通常是基于数组实现的,哈希函数的输出决定了键值对应该存储在数组中的哪个位置。
-
处理冲突:由于不同的键可能会生成相同的哈希值(称为哈希冲突),需要有机制来处理这些冲突。常见的处理方式包括:
- 链地址法(Separate Chaining):每个数组位置存储一个链表,所有哈希值相同的键值对都存储在同一个链表中。
- 开放地址法(Open Addressing):当冲突发生时,查找数组中的下一个空闲位置存储键值对,常见的方式包括线性探测、二次探测和双重散列。
-
查找和插入:哈希表允许以 O(1) 的时间复杂度进行查找和插入操作(在理想情况下),即只需一次哈希函数计算和一次数组访问即可找到或插入数据。
哈希表的优点
- 快速的查找、插入和删除:在平均情况下,哈希表的查找、插入和删除操作都是常数时间(O(1)),这使其非常高效。
- 简单实现:哈希表相对容易实现,且不需要复杂的数据结构操作。
哈希表的缺点
- 空间浪费:如果哈希表的负载因子(存储的元素数量与数组大小的比值)较高,容易导致冲突增加,从而降低性能。为避免冲突,通常会使用更大的数组,这会导致空间浪费。
- 无法有序遍历:哈希表中的数据是无序的,如果需要顺序访问数据,需要额外的处理。
- 哈希函数质量:哈希表的性能高度依赖于哈希函数的质量,差劲的哈希函数可能导致较多的冲突。
应用场景
哈希表在许多场景中广泛使用,包括但不限于:
- 字典实现:例如,Python 中的字典(dict)就是通过哈希表实现的。
- 缓存:哈希表常用于实现缓存机制,如 LRU 缓存。
- 集合操作:例如,在查找某个元素是否在集合中时,哈希表可以显著提高效率。
- 位图:略
- 布隆过滤器:略
通过哈希表,可以在大型数据集上快速执行查找、插入和删除操作,这使得它在实际应用中非常实用。
所以依照哈希表的基本原理可以看出哈希表的底层结构可以分为以下两种,这两种方法的本质都是除留余数法:
闭散列法
闭散列法也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有 空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置 呢? 比如2.1中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4, 因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
插入: 通过哈希函数获取待插入元素在哈希表中的位置 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突, 使用线性探测找到下一个空位置,插入新元素。
删除:采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素 会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影 响。因此线性探测采用标记的伪删除法来删除一个元素。
上面有一个名词为线性探测,就是当待插入元素出现哈希冲突时,一个一个往下寻找空位置称为线性探测,每次n的固定次方倍往下找称为二次探测,两种探测的本质解决的问题完全一致,所有我们就只使用线性探测进行实现。
开散列法
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地 址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链 接起来,各链表的头结点存储在哈希表中。代码总体实现的功能和上面的闭散列法差不多。
从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。
开放定值法Open Addressing——线性探测的模拟实现
#include<iostream>
#include<vector>
using namespace std;
//namespace hashbucket//包一个命名空间就可以调用了
//{
template<class K>
struct Hashfunc
{
size_t operator()(const K& key)
{
return (size_t)key;//由于像当K为string时不可以直接强制转换成整型,所以如果哈希表需要插入string类型的数据就需要写string的特化版本,如下
}
};
//struct StringHashFunc
//{
// size_t operator()(const string& s)
// {
// size_t hash = 0;
// for (auto e : s)
// {
// hash *= 31;
// hash += e;
// }
//
// return hash;
// }
//};
template<>
struct Hashfunc<string>//可以直接在这里写,模板的特化前提是前面有写过了,且名称一样,遇到和特化吻合的会优先匹配特化版本
{
size_t operator()(const string& key)
{
size_t hash = 0;
for (auto e : key)
{
hash += e;
hash *= 31;//根据某篇文章可得多乘31可以有效避免很多重复
}
return hash;
}
};
enum status
{
EXIST,
EMPTY,
DELETE//三个状态,这里定义了哈希表中的数据的多个状态,在红黑树的模拟实现中有用过,这样方便判断此位置的数据状态,方便以后的线性探测和删除数据等
};
template<class K, class V>
struct Hashdata
{
pair<K, V> _kv;
status s = EMPTY;//刚开始时所有空位的状态都是EMPTY
};
template<class K, class V, class Hash = Hashfunc<K>>
class Hashtable
{
public:
Hashtable()
{
_table.resize(10);//直接在初始化时开10个空间
}
Hash ha;//仿函数
bool insert(const pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
if (10 * n / _table.size() >= 7)
{
//vector<HashData<K, V>> newTables(_tables.size() * 2);
遍历旧表, 将所有数据映射到新表
...
//_tables.swap(newTables);
//以上方法无法复用insert
Hashtable<K, V, Hash> newht;//是KV
newht._table.resize(2 * _table.size());//_table肯定是类里的,不然要指定的
for (int i = 0; i < _table.size(); i++)
{
newht.insert(_table[i]._kv);//复用
}
_table.swap(newht._table);//交换
}
size_t hashi = ha(kv.first) % _table.size();//非整型不能取%,所以使用仿函树转换一下
while (_table[hashi].s == EXIST)
{
hashi++;
hashi %= _table.size();//hashi不断的++的时候有可能会超出整个vector的数据的长度,所以需要再%_table.size()使其处在最后时下一步回到数据的开头,所以可以看出hashi是不断循环的。
}
_table[hashi]._kv = kv;
_table[hashi].s = EXIST;
n++;//每成功插入一个数据,其负载因子就加1
return true;
}
vector<Hashdata<K, V>>* begin() const
{
return &_table[0];
}
vector<Hashdata<K, V>>* end() const
{
return &_table[_table.size()];
}
Hashdata<K, V>* Find(const K& key)
{
size_t hashi = ha(key) % _table.size();
size_t n = hashi;
while (_table[hashi].s != DELETE)
{
if (_table[hashi]._kv.first == key && _table[hashi].s == EXIST)//由于下面的删除逻辑为不删除数据只改名数据的状态所以当找到相等时,这个数据有可能会有两种状态:EXIST和DELETE
{
return &_table[hashi];
}
hashi++;
hashi = hashi % _table.size();
if (hashi == n)
{
return nullptr;
}
}
return nullptr;
}
bool Erase(const K& key)//只要传一个K就可以了
{
Hashdata<K, V>* ret = Find(key);
if (ret)
{
ret->s == DELETE;
return true;
}
return false;
}
private:
vector<Hashdata<K, V>> _table;
size_t n = 0;//表中存储数据个数, n为负载因子
};
//}
超大重点部分评析
这边说一下扩容的逻辑,由于插入逻辑里面hashi是不断循环的,所以当哈希表里面数据都占满时,hashi会陷入死循环,但是就算没有全部占满,如果已占数据过多就会使得下一个数据在插入时哈希冲突过多使时间复杂度变大,所以为了避免这种情况,引入负载因子,当插入数据占比>=百分之70时,也就是负载因子/空间数据容纳最大个数 == 0.7时,将容器的容量扩大成原来的双倍。
再观察上面的扩容代码,为什么不能直接再原空间之间扩容呢,需要另开一个空间再交换回去,因为如果之间在原空间扩容,会使得size(数据最大个数)变大使得原本的数据的映射乱了。像现在这种扩容写法交换swap之后,由于新创建的临时变量newht出函数体就会销毁了,也就会顺带释放掉swap交给它的原空间,这样扩容之后的空间就保留下来了,所以不需要考虑旧表没有被释放的问题。
最后注意一下这个Erase的写法,很多人可能会认为删除某个元素需要像之前顺序表的那种写法(毕竟每个数据就在顺序表里面),删除了这个数据,让这个数据后面的数据依次往前移,时间复杂度就是O(n),这里不是不可以,但是如果你这么写就麻烦了,因为每个数据都外带了一个数据状态,按顺序表那么写的话就忽略的状态这个东西。
链地址法Separate Chaining——哈希桶的模拟实现
预知后事如何,请持续关注本系列内容!!!
相关文章:

哈希表的底层实现(1)---C++版
目录 哈希表的基本原理 哈希表的优点 哈希表的缺点 应用场景 闭散列法 开散列法 开放定值法Open Addressing——线性探测的模拟实现 超大重点部分评析 链地址法Separate Chaining——哈希桶的模拟实现 哈希表(Hash Table)是一种数据结构&#x…...

如何使用PTK一键安装opengaussdb 5.0
1、关于PTK工具 MogDB数据库是云和恩墨基于openGauss开源数据库打造,安稳易用的企业级关系型数据库。 PTK是云和恩墨出品的一款工具,帮助用户更便捷地部署管理MogDB数据库。 1.1 使用场景 开发人员快速启动多个本地 MogDB 环境用户通过 PTK 快速安装…...

跟李沐学AI:长短期记忆网络LSTM
输入们、遗忘门和输出门 LSTM引入输入门、忘记门和输出门 输入门计算公式为:。 遗忘门计算公式为:。 输出门计算公式为:。 它们由三个具有sigmoid激活函数的全连接层处理, 以计算输入门、遗忘门和输出门的值。 因此,…...
【BIM模型数据】BIM模型的数据如何存储,BIM大模型数据云端存储,需要考虑哪些因素,BIM模型数据存储和获取
【BIM模型数据】BIM模型的数据如何存储,BIM大模型数据云端存储,需要考虑哪些因素,BIM模型数据存储和获取 BIM文件的结构化数据和非结构化数据的存储方式,需要根据数据的特性和使用需求来选择。以下是一些推荐的存储策略࿱…...

【LLM大模型】大模型架构:layer\_normalization
2.layer_normalization 1.Normalization 1.1 Batch Norm 为什么要进行BN呢? 在深度神经网络训练的过程中,通常以输入网络的每一个mini-batch进行训练,这样每个batch具有不同的分布,使模型训练起来特别困难。Internal Covariat…...

PON光模块的独特类型和特性
在当前互联网需求快速增长的背景下,PON光模块已成为实现光纤网络高速数据传输的重要组成部分。从住宅宽带到各种企业应用程序解决方案,PON光模块始终致力于实现高质量的数据传输与无缝通信。了解PON光模块的类型和特性对于深入理解现代网络基础设施至关重…...

架构与业务的一致性应用:实现企业战略目标和合规管理的全面指南
在当今快速变化的数字经济中,信息架构已成为企业实现其业务目标、优化运营效率和确保数据安全的关键工具。 一个成功的信息架构不仅要与企业的战略目标紧密对齐,还必须遵循日益严格的合规性要求,以保护敏感数据并满足法规规定。《信息架构&a…...

时尚穿搭想换就换,各种风格一键完美搭配!亲测在线虚拟试衣换装平台效果超赞!
随着科技的发展,时尚领域也迎来了新的革命。传统的试衣方式逐渐被现代科技所取代,虚拟试衣间的出现使得用户可以在舒适的家中轻松体验不同的服装风格。 先前给大家也介绍过一些虚拟试衣的技术,例如AnyFit或者OutfitAnyone等,今天…...
【C++】C++ 标准库string类介绍(超详细解析,小白必看系列)
C 标准库中的 std::string 类是一个非常强大的工具,用于处理和操作字符串。它属于 <string> 头文件,并提供了一套丰富的功能和方法。以下是 std::string 类的一些主要特性和常用操作: 1 string简介 字符串是表示字符序列的类 标准的字…...

若依RuoYi项目环境搭建教程(RuoYi-Vue + RuoYi-Vue3版本)
文章目录 一、开发脚手架选择二、RuoYi框架1、介绍2、版本发展3、为什么选择若依4、优缺点5、项目内置功能 三、后端项目部署1、拉取源码2、环境要求3、Maven构建4、MySQL相关(1)导入SQL脚本(2)配置信息 5、Redis相关(…...

Java 后端接口入参 - 联合前端VUE 使用AES完成入参出参加密解密
加密效果: 解密后的数据就是正常数据: 后端:使用的是spring-cloud框架,在gateway模块进行操作 <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30…...

HarmonyOS开发之使用PhotoViewPicker(图库选择器)保存图片
一:效果图 二:添加依赖 import fs from ohos.file.fs;//文件管理 import picker from ohos.file.picker//选择器 三:下载,保存图片的实现 // 下载图片imgUrldownloadAndSaveImage(imgUrl: string) {http.createHttp().request(…...

跨境独立站支付收款常见问题排雷篇1.0丨出海笔记
最近小伙伴们在社群讨论挺多关于独立站支付问题的,鉴于不少朋友刚接触独立站,我整理了一些独立站支付相关的问题和解决方案,供大家参考,百度网上一堆媒体的那些软文大家就别看了,都是软广或者抄来抄去,让大…...

uni-app实现web-view和App之间的相互通信
双向实时 如果app端部署成网站,则web-view就是iframe,使用也可以双向通讯 https://uniapp.dcloud.net.cn/component/web-view.html APP端代码 index.vue: <template><web-viewid"m-webview":fullscreen"true":src"…...

HTB-Vaccine(suid提权、sqlmap、john2zip)
前言 各位师傅大家好,我是qmx_07,今天来为大家讲解Vaccine靶机 渗透过程 信息搜集 服务器开放了 21FTP服务、22SSH服务、80HTTP服务 通过匿名登录FTP服务器 通过匿名登录到服务器,发现backup.zip文件,可能存在账号密码 发现b…...
【达梦数据库】异构数据库迁移到达梦
目录 1、迁移准备2、正式迁移3、问题处理3.1、return附近出现错误3.1.1、排查过程3.1.2、问题原因3.1.2、解决方法 3.2、对象[XXX]处于无效状态-类型13.2.1、排查过程3.2.2、问题原因3.2.3、解决方法 3.3、对象[XXX]处于无效状态-类型23.3.1、排查过程3.3.2、问题原因3.3.3、解…...

抽象类和接口(1)
抽象类: 什么是抽象类: 听着就很抽象,确实挺抽象,先来写一个抽象类感觉一下: 这就是抽象类! 在 Java 中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法…...
epoll内核原理与实现详解
目录 1 epoll相关理论基础 1.1 I/O多路复用技术 1.2 事件驱动模型 1.2.1 基本概念 1.2.2 优缺点分析 1.2.3 与epoll的关联 1.3 epoll机制简介 1.3.1 核心原理 1.3.2 优点 2 epoll内核原理 2.1 epoll数据结构 2.1.1 主要数据结构 2.1.2 数据结构关系 2.2 epoll工作…...

被低估的SQL
SQL是现代数据库管理系统中不可或缺的一部分。尽管它的使用已十分普遍,但在数据处理领域,SQL的某些功能和潜力仍然被许多人低估。接下来,小编将与您一起,探讨SQL的一些被忽视的特性,揭示它在数据管理中的真正实力。 1.…...
数字证书、数字签名及其关系
一.数字证书与数字签名 1.数字证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。简单地说,数字证书是一段包含用户身份信息、用户公钥信息以及份验证机构数字签名的数据。 通俗理解:数字证书相当于【身份证】 —— 确认你…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...