[数据结构] 开散列法 闭散列法 模拟实现哈希结构(一)

标题:[数据结构] 开散列法 && 闭散列法 模拟实现哈希结构

个人主页:@水墨不写bug



目录

一、闭散列法

核心逻辑的解决

        i、为什么要设置位置状态?(伪删除法的使用)

        ii、哈希函数的设计

接口的实现 

1、插入(Insert)

仿函数取数值域的key

插入的 扩容逻辑

2、删除(Erase)

3、查找(Find)


正文开始:

        在之前的讲解中,我们已经了解到哈希结构的基本实现思路,具体讲解见这一篇:

《[数据结构] 哈希结构的哈希冲突&&解决哈希冲突》。实现哈希的方法主要区别在与哈希冲突的解决不同,我们知道常见的解决哈希冲突的方法有:

        闭散列法:找到下一个空位置存储;

        开散列法(链地址法、开链法):通过一个链表(哈希桶)来解决哈希冲突的问题。

        本文将分别实现上述两种方法,以及其各自的实现思路,主要实现哈希表的主要功能:插入(Insert)、删除(Erase)、查找(Find)等的主要逻辑。


一、闭散列法

        闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。


核心逻辑的解决

        i、为什么要设置位置状态?(伪删除法的使用)

        对于一个位置,如果发生哈希冲突,就需要把数据存放到下一个空位置中去,当我们下一次查找这个数据的时候,就需要走一遍和存储时一样的操作。

        比如,我们要在下面的这样的一个哈希表中存储一个数据:

        14%10 = 4:

        向后探测的条件可以猜测为:

        如果表中的不为空,就向后探测。

        但是当我们删除一个元素:这个元素是探测路径上的一个元素,那么下一次当我们查找14的时候,就找不到了。

        所以,我们在设计哈希数据的时候,就需要在每一个数据结构中设置一个状态值:

//哈希表位置的状态
enum State{EXIST,EMPTY,DELETE};

        这样,我们的状态体系就实现了:哈希表一个位置的默认状态为“EMPTY”;如果插入一个数据,那么这个位置的状态就需要更新为“EXIST”,在删除了一个数据之后,需要将这个位置的状态设置为“DELETE”。

        这样,我们就完美解决了删除数据后导致查找数据无法找到的问题。

        实现闭散列哈希需要一个顺序表,这里用vector即可。每一个数据位置存储的是一个自定义类型哈希数据(HashData),这个结构内部存储:

数据域、状态域

         数据域根据传入的模板参数决定:(比如传入的是int,或者pair<int,int>)

    //哈希表的数据
	template<class T>
	struct HashData
	{
		//默认构造,一般不会用到
		HashData()
			:_state(EMPTY)
			,_data(T())
		{}
		//插入数据时的构造函数
		HashData(const T& data)
			:_state(EXIST)
			,_data(data)
		{}
		T _data;
		State _state;
	};

        ii、哈希函数的设计

        对于一般的自定义类型(整形,浮点型,指针类型)可以强制转换为size_t类型,我们可以将默认的哈希函数设计为直接强制类型转换为size_t;

        对于常用自定义类型string, 我们设计如下的哈希类仿函数的特化版本:

//特化string版本
template<>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (const auto& e : s)
		{
			hash += e;
			hash *= 31;
		}
		return hash;
	}
};

接口的实现 

1、插入(Insert)

仿函数取数值域的key

        在插入逻辑之前,我们首先需要知道 插入的数据类型V是不确定的,是通过类模板的参数决定的。(这涉及到了unordered_map和unordered_set的需要复用同一个哈希的封装)

        我们暂时不直接看底层实现,只需要知道有一种仿函数,可以取得数据域的key:

/*模板参数介绍
 *关键值:K 
 *数值域:V
 *得到Key的仿函数:KeyOfV 
 *hash值计算仿函数:Hash
 */
template<class K,class V,class KeyOfV,class Hash = HashFunc<K>>
class HashTable
{
    // 具体实现  
};

        由于插入的逻辑比较简单,这里不再解释,直接给出插入的核心逻辑:

bool Insert(const V& data)
{    
    //仿函数,得到数据的关键值
    KeyOfV kov;
    Hash hs;

    //如果有重复数据,插入失败
    //Find是通过key来查找的,如果插入的时候哈希表中已经存在,则返回false,插入失败
    if (Find(kov(data)) != nullptr)
	    return false;

    //...

    size_t hashi = hs(kov(data)) % _table.size();
    while (_table[hashi]._state == EXIST)
    {
	    ++hashi;
	    hashi %= _table.size();
    }
    _table[hashi]._data = data;
    _table[hashi]._state = EXIST;
    _n++;
    
    return 0;
}

插入的 扩容逻辑

        《[数据结构] 哈希结构的哈希冲突&&解决哈希冲突》讲解中,我们了解到载荷因子是反应哈希表装载程度的量:用载荷因子和size()的比值来决定什么时候要扩容。

        我们界定:载荷因子*10/size() >= 0.7的时候需要扩容。

        扩容就需要进行数据的转移。

        为了体现复用(实际上是懒),我们可以new一个新的表,再遍历旧表的数据,一个一个插入新表即可,最终交换新表和旧表的vector即可:

//检查是否需要扩容
if (_n * 10 / _table.size() >= 7)
{
	//新建哈希表,容量为2倍
	HashTable<K,V,KeyOfV> newHT;
	newHT._table.resize(_table.size() * 2);

	//将旧表的数据移动到新表
	for (int i = 0; i < _table.size(); ++i)
	{
		if (_table[i]._state == EXIST)
		{
			newHT.Insert(_table[i]._data);
		}
	}
	_table.swap(newHT._table);
}

插入:

//插入数据
bool Insert(const V& data)
{
	//仿函数,得到数据的关键值
	KeyOfV kov;
	Hash hs;

	//如果有重复数据,插入失败
	if (Find(kov(data)) != nullptr)
		return false;

	//检查是否需要扩容
	if (_n * 10 / _table.size() >= 7)
	{
		//新建哈希表,容量为2倍
		HashTable<K,V,KeyOfV> newHT;
		newHT._table.resize(_table.size() * 2);

		//将旧表的数据移动到新表
		for (int i = 0; i < _table.size(); ++i)
		{
			if (_table[i]._state == EXIST)
			{
				newHT.Insert(_table[i]._data);
			}
		}
		_table.swap(newHT._table);
	}

	size_t hashi = hs(kov(data)) % _table.size();
	while (_table[hashi]._state == EXIST)
	{
		++hashi;
		hashi %= _table.size();
	}
	_table[hashi]._data = data;
	_table[hashi]._state = EXIST;
	_n++;

	return true;
}

2、删除(Erase)

         删除的逻辑比较简单,我们需要先借助find根据key找到需要删除的数据的应该存在的哈希表的位置,再一个一个向后限行探测即可;如果找到,伪删除,修改状态为DELETE,如果没有找到,返回nullptr:

bool Erase(const K key)
{
	HashData<V>* ret = Find(key);
	if (ret == nullptr)
		return false;
	else
	{//修改数据状态表示删除
		ret->_state = DELETE;
		return true;
	}
}

3、查找(Find)

         查找的逻辑也是不复杂的;首先找到数据应该存在的表中的位置,如果一个一个向后限行探测即可;唯一需要注意的是状态为DELETE的时候,需要继续往后查找:

//根据key查找数据
HashData<V>* Find(const K key)
{
	KeyOfV kov;//得到data的关键值
	Hash hs;//哈希函数

	size_t hashi = hs(key) % _table.size();
	while (_table[hashi]._state != EMPTY)
	{
		if (_table[hashi]._state == EXIST && kov(_table[hashi]._data) == key)
			return &_table[hashi];
		hashi++;
		hashi %= _table.size();
	}
	return nullptr;
}

 待续~

未经作者同意禁止转载

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32 RTC实时时钟

RTC实时时钟 BKP可以在VBAT维持供电时&#xff0c;完成主电源掉电时&#xff0c;保存少量数据的任务。备份寄存器和VBAT引脚同时存在&#xff0c;更多是为了服务RTC的。 目前&#xff0c;Linux、Windows、安卓这些系统&#xff0c;底层的计时系统都是使用的Unix时间戳&#xf…

网络编程(UDP)

UDP编程 UDP&#xff1a;全双工通信、面向无连接、不可靠 UDP&#xff08;User Datagram Protocol&#xff09;用户数据报协议&#xff0c;是不可靠的无连接的协议。在数据发送前&#xff0c;因为不需要进行连接&#xff0c;所以可以进行高效率的数据传输。 适用场景 发送小尺寸…

Anaconda安装和环境配置教程(深度学习准备)

目录 1.下载选择 2.prompt配置 3.虚拟环境配置 4.检查是不是安装成功 5.安装jupter 6.关闭anaconda重新进入 7.总结 1.下载选择 我第一次使用的这个官网上面的邮箱的方式下载的&#xff0c;但是这个方式真的特别慢&#xff0c;于是用了这个清华的镜像网站&#xff0c;网…

基于JAVA+SpringBoot+Vue的工程教育认证的计算机课程管理平台

基于JAVASpringBootVue的工程教育认证的计算机课程管理平台 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接…

简单比较 http https http2,我们要如何把http升级为https

&#x1f9d1;‍&#x1f4bb; 写在开头 点赞 收藏 学会&#x1f923;&#x1f923;&#x1f923; 什么是HTTP 超文本传输​​协议&#xff08;HTTP&#xff09;是用于传输诸如HTML的超媒体文档的应用层协议。它被设计用于Web浏览器和Web服务器之间的通信&#xff0c;但它也…

合宙LuatOS开发板Core_Air780EP使用说明

Core-Air780EP 开发板是合宙通信推出的基于 Air780EP 模组所开发的&#xff0c; 包含电源&#xff0c;SIM卡&#xff0c;USB&#xff0c;天线&#xff0c;音频等必要功能的最小硬件系统。 以方便用户在设计前期对 Air780EP模块进行性能评估&#xff0c;功能调试&#xff0c;软…

精益生产现场管理和改善的实战路径

精益生产&#xff0c;作为制造业的革新利器&#xff0c;不仅能够帮助企业降低成本、提升质量&#xff0c;还能大幅度提高生产效率。但如何将这一理念从理论转化为实际行动&#xff0c;真正落地于生产现场&#xff0c;成为许多管理者面临的难题。今天&#xff0c;就让天行健咨询…

AXI4主机测试

前面对AXI4协议进行了比较详细的分析&#xff0c;本篇文章将会写一个主机代码来实现AXI4协议的时序。 设计思路&#xff1a;本次设计的主要目的是验证AXI4_FULL总线的时序&#xff0c;并且提升对AXI4_FULL总线协议的理解&#xff0c;因此可以采用状态机来控制&#xff0c;先向…

免费SSL证书正在逐渐被淘汰,证书部署自动化的发展趋势即将到来!

目录 背景解决方案。1.使用自签证书&#xff08;浏览器报警、免费&#xff09;2.更换支持自签自续的CA机构&#xff08;免费&#xff09;3.付费选择CA机构 免费SSL证书正在逐渐被淘汰&#xff0c;证书部署自动化的发展趋势即将到来免费的SSL证书有以下弊端1.有效期短&#xff1…

【数据结构(初阶)】——二叉树

【数据结构】——二叉树 文章目录 【数据结构】——二叉树前言1. 树的概念及结构1.1 树的概念1.2 树的结构 2. 二叉树的概念及结构2.1 二叉树的概念2.2 二叉树的结构2.3 二叉树的性质 3. 二叉树顺序结构及概念3.1 二叉树的顺序结构3.2 堆的概念及结构3.3 堆的实现3.3.1 堆的基本…

探索全球实时云渲染与XR技术的多平台兼容性:谁是行业引领者?

在扩展现实&#xff08;XR&#xff09;技术与实时云渲染技术的飞速发展中&#xff0c;多平台兼容性已经成为行业技术竞争的关键要素。能够在不同的平台和设备上高效运行的解决方案&#xff0c;不仅关系到开发效率和场景多样性&#xff0c;还直接影响到用户体验和市场占有率。Pa…

在桌面商业分析应用程序中启用高级 Web UI

挑战 Mercur Business Control 应用程序在企业界&#xff0c;尤其是金融领域&#xff0c;拥有悠久的应用历史。它帮助企业处理、可视化和分析海量数据&#xff0c;从而做出明智的商业决策。 随着产品的不断演进和现代化&#xff0c;Mercur Solutions AB 为该应用创建了 Web 客…

基于SpringBoot的教师人事档案管理系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBootMyBatis 工具&#xff1a;IDEA/Eclipse、Navicat、Maven 系统展示 首页 教师管理 奖惩…

音频-语言大模型原理

重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…

[数据集][目标检测]汽油检泄漏检测数据集VOC+YOLO格式237张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;237 标注数量(xml文件个数)&#xff1a;237 标注数量(txt文件个数)&#xff1a;237 标注类别…

开放式耳机具备什么特点?2024排行前十的四款百元蓝牙耳机推荐

开放式耳机具有以下特点&#xff1a; 佩戴舒适&#xff1a; 开放式耳机通常不需要插入耳道&#xff0c;能减少对耳道的压迫和摩擦&#xff0c;长时间佩戴也不易产生闷热、疼痛或瘙痒等不适&#xff0c;对于耳道敏感或不喜欢入耳式耳机压迫感的人来说是很好的选择。 这类耳机…

​​MEPA(Maximum Efficiency Per Ampere)控制

一.控制目的 与MTPA控制相比&#xff0c;没有忽略电机的铁耗&#xff0c;以电能损耗最小为目的优化电流。 分析思路与MTPA控制类似&#xff0c;在此省略。 二. 推导过程

JavaScript案例---求质数

n等于19&#xff0c;是质数 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"wid…

哈希表 和 算法

1.哈希表的作用&#xff1a;将我们要存储的数据&#xff0c;通过关键字与位置的关系函数&#xff0c;来确定具体的位置。 2.写哈希表时常出现的问题&#xff1a;哈希冲突/矛盾&#xff1a;当多个数据满足哈希函数的映射时出现 解决的方法为&#xff1a; 1&#xff09;开放地址…

VScode:前端开发中的常用快捷键和技巧

1.菜单栏 2.内容相关&#xff1a; 格式化文档 搜索文件名 代码双开对比 右上角选择拆分