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

news2024/12/27 13:07:52

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

个人主页:@水墨不写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;
}

 待续~

未经作者同意禁止转载

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

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

相关文章

SD三分钟入门!秋叶大佬24年8月最新的Stable Diffusion整合包V4.9.7来了~

1 什么是 Stable Diffusion&#xff1f; Stable Diffusion&#xff08;简称SD&#xff09;是一种生成式人工智能技术&#xff0c;于2022年推出。它主要用于根据文本描述生成精细图像&#xff0c;同时也可应用于其他任务&#xff0c;如图像修补、扩展&#xff0c;以及在文本提…

圣诞节:白酒与西式料理的异国风情

随着冬日的脚步渐近&#xff0c;圣诞的钟声即将敲响。在这个充满异国情调和温馨氛围的节日里&#xff0c;一场中西合璧的美食盛宴悄然上演。豪迈白酒&#xff08;HOMANLISM&#xff09;与西式料理的碰撞&#xff0c;不仅为圣诞餐桌增添了几分不同的韵味&#xff0c;更让人们在这…

标准库_string的代码实现

前言 string不属于stl&#xff0c;string比stl出现的早的多&#xff0c;所以string属于标准库&#xff0c;不属于stl 大家可以在cplusplus网站看string的内容 string的六个构造函数 构造函数 在写构造函数的时候不管传的字符串里面有没有字符&#xff0c;都必须至少要new c…

zerojs前端面试题系列--vue3篇(4)

1. Vue3中的可选链运算符&#xff08;?.&#xff09;和空值合并运算符&#xff08;??&#xff09;是什么&#xff1f;它们有什么作用&#xff1f; 可选链运算符&#xff08;?.&#xff09;和空值合并运算符&#xff08;??&#xff09;是Vue 3.x中新增的两种运算符。 可选…

点餐小程序实战教程04变量定义和初始化

目录 1 什么是变量2 如何创建变量3 具体该选择什么类型4 变量的初始化5 await/async6 调试我们的程序7 运行我们的代码总结 日常碰到的最多的一句话是&#xff0c;我看到代码就发憷&#xff0c;我一点基础也没有。那低代码开发需要掌握什么样的基础&#xff0c;怎么才算是掌握了…

图像分割像素三分类效果

pets_fg 数据集 训练集: avg_score: 0.8818 - dice: 0.9138 - iou: 0.8520 - loss: 0.1116 - mae: 0.6862 训练集掩码效果 avg_score: 0.8415 - dice: 0.8798 - iou: 0.8065 - loss: 0.1601 - mae: 0.6882 验证集效果展示

提前锁定!2024云栖大会大数据AI参会攻略来啦

2024年9月19日~9月21日&#xff0c;一年一度的云栖大会又要与大家见面了。一文尽览大数据AI精彩预告&#xff0c;赶紧收藏吧&#xff01; 9月20日上午 10:45~11:25 阿里云智能集团副总裁、计算平台事业部负责人汪军华带来大数据AI平台年度发布。揭晓全新的 OpenLake 方案如何为…

力扣题解2181

大家好&#xff0c;欢迎来到无限大的频道&#xff0c;有些日子没更新了&#xff08;其实是因为懒&#xff09; 言归正传&#xff0c;开始分享今日的题解。 题目描述&#xff1a; 合并零之间的节点 给一个链表的头节点 head &#xff0c;该链表包含由 0 分隔开的一连串整数。…

七种方法加密图纸!2024企业要如何对CAD图纸进行加密?

随着企业信息化的发展&#xff0c;CAD图纸作为企业的核心数据之一&#xff0c;其安全性变得尤为重要。如何有效加密CAD图纸&#xff0c;以防止数据泄露&#xff0c;是众多企业关注的重点问题。本文将介绍2024年企业加密CAD图纸的七种有效方法&#xff0c;帮助企业提升图纸安全性…

记一次高版本view-design的组件迁移到自身项目的低版本

背景 npm i -S view-design当前老项目使用view-design这个组件库&#xff0c;但是当我们去官网查看该组件库最新版本&#xff0c;竟然发现没有博主想用的image/ImagePreivew这两个基础组件 说实话&#xff0c;有点离谱了哈&#xff01;&#xff01; 自己造轮子&#xff1f; …

软考基础知识之网络工程

目录 前言 网络工程 网络规划 1、网络需求分析 2、可行性研究 3、对现有网络的分析与描述 网络设计 1、网络逻辑结构设计 2、网络物理结构设计 3、 分层设计 网络实施 1、工程实施计划 2、网络设备到货验收 3、设备安装 4、系统测试 5、系统试运行 6、用户培训…

利用熵权法进行数值评分计算——算法过程

1、概述 在软件系统中&#xff0c;研发人员常常遇上需要对系统内的某种行为/模型进行评分的情况。例如根据系统的各种漏洞情况对系统安全性进行评分、根据业务员最近操作系统的情况对业务员工作状态进行打分等等。显然研发人员了解一种或者几种标准评分算法是非常有利于开展研…

mp4转文字怎么快速转换?5个软件教你快速进行视频提取文字

mp4转文字怎么快速转换&#xff1f;5个软件教你快速进行视频提取文字 将MP4视频转换为文字是一项常见的需求&#xff0c;尤其是在需要将视频中的对话、演讲或字幕转为文本时。通过使用AI转录软件&#xff0c;你可以快速将视频中的音频内容提取并转换为文字。以下介绍5款可以帮…

一个数组向左移动i位(学会分析问题,找出规律,不要小看任何一个小程序;小程序都是实现大的功能的基础--体现问题分解的思想)

学会分析问题&#xff0c;找出规律&#xff1b; 不要小看任何一个小程序&#xff0c;小程序都是实现大的功能的基础--体现问题分解的思想&#xff09; 思想火花--好算法是反复努力和重新修正的结果 解法1:先将数组中的前i个元素存放在一个临时数组中,再将余下的n一i个元素左…

黑马点评15——分布式缓存-Redis分片集群

文章目录 搭建分片集群散列插槽集群伸缩故障转移RedisTemplate访问分片集群 搭建分片集群 参考我前面的文章&#xff1a;单机部署Redis集群 散列插槽 集群伸缩 添加新的节点后要重新分配插槽, 故障转移 这是自动的故障转移&#xff0c;但有时候需要手动的故障转移——比如更…

Hfinger:一款针对恶意软件HTTP请求的指纹识别工具

关于Hfinger Hfinger是一款功能强大的HTTP请求指纹识别工具&#xff0c;该工具使用纯Python开发&#xff0c;基于Tshark实现其功能&#xff0c;可以帮助广大研究人员对恶意软件的 HTTP 请求进行指纹识别。 该工具的主要目标是提供恶意软件请求的唯一表示&#xff08;指纹&…

“CSS 定位”如何工作?(补充)——WEB开发系列34

CSS定位是一个非常重要的布局工具&#xff0c;它允许我们精确地控制元素的位置&#xff0c;从而创建复杂的布局效果。定位允许你从正常的文档流布局中取出元素&#xff0c;并使它们具有不同的行为&#xff0c;例如放在另一个元素的上面&#xff0c;或者始终保持在浏览器视窗内的…

Java代码审计篇 | ofcms系统审计思路讲解 - 篇2 | SQL注入漏洞审计

文章目录 Java代码审计篇 | ofcms系统审计思路讲解 - 篇2 | SQL注入漏洞审计1. 前言2. SQL注入代码审计【有1处】1&#xff09;全局搜索Statement关键字&#xff0c;发现有几处&#xff0c;不过大多都是preparStatement预编译方式执行sql语句。2&#xff09;点进第一个看看3&am…

matlab数据批量保存为excel,文件名,行和列的名称设置

Excel文件内数据保存结果如下&#xff1a; Excel文件保存结果如下&#xff1a; 代码如下&#xff1a; clear;clc; for jjjj1:10 %这个可以改 jname(jjjj-1)*10; %文件名中变数 这是EXCEL文件名字的一部分 根据自己需要改 jkkkk_num2str(jname); for …

物联网之Arduino开发环境的下载与安装、ESP32开发环境的下载与安装、常见环境配置问题的解决办法、COM端口不可用的解决方法

MENU 前言下载和安装Arduino安装ESP32开发环境常见问题JSON下载失败和下载速度慢配置解释器没有发现端口检测到端口&#xff0c;但是有警告图标&#xff0c;端口无法使用 前言 想玩开发板必须得写代码&#xff0c;要不然Arduino不知道怎么运行&#xff0c;Arduino的开发语言是C…