哈希的开放定址法的实现【C++】

news2024/11/28 16:04:53

哈希的开放定址法的实现【C++】

  • 1 概述
  • 2 线性探测
    • 2.1 插入
    • 2.2 查找
    • 2.3 删除
    • 2.6 完整代码
    • 2.5 线性探测的优缺点
  • 3. 二次探测

1 概述

  开放定址法也叫闭散列,是解决哈希冲突的一种方法,当发生哈希冲突之后,如果哈希表没有被装满(正常情况哈希表不会被装满的),那就向后移动,寻找一个没有元素的地址,然后插入。下面介绍移动寻找合适地址的方法:线性探测

2 线性探测

  线性探测: 当发生哈希冲突时,依次向后探测,直到寻找到下一个空位置为止。

2.1 插入

  通过代码来实现插入:

  1. 首先定义哈希表单个数据的结构体:

enum State
{
	EMPTY,
	EXIST,
	DELETE
};
template<class K, class V>
struct Hash
{
	pair<K, V> _kv;
	State _state = EMPTY;
};

  •   引入一个状态值的含义是,当删除一个元素以后,如果没有状态值,无法赋予被删除元素的地址处一个此处被删除的标识(赋予0,-1,都无法合理的标识此处被删除,没有元素),且状态值也可以标识无元素地址处,使得迭代时能够选择性的跳过DELETE和EMPTY处。
  1. 创建哈希表:
template<class K, class V>
class hashTable
{
public:
	bool Insert(const pair<K, V>& val)
	{
	
	}

private:
	vector<Hash<K, V>> _tables;
	int _size = 0;

};
  1. 实现插入:
template<class K, class V>
class hashTable
{
public:
	bool Insert(const pair<K, V>& val)
	{
		//需要注意的是,一定是除余size,而不是capacity
		size_t hashi = val.first % _tables.size();
		size_t j = 1;
		size_t index = hashi;
		//如果发生了哈希冲突,也就是此处地址上存在元素,那就向后探测
		while (_tables[index]._state == EXIST)
		{
			index = hashi + j;//每次都是向后探测一位,j++.
			index = index % _tables.size();//保证了向后探测时如果到了空间末尾,那就从空间开
											//头处继续探测,保证了能够探测到一个合适的位置
			j++;
		}
		//此时index处不存在元素:
		_tables[index]._kv = val;
		_tables[index]._state = EXIST;
		_size++;

		return true;
	}

private:
	vector<Hash<K, V>> _tables;
	int _size = 0;

};

写完上面代码会发现以下问题:

  1. _tables没有初始化,没有开辟空间,所以后面的插入元素也不可能完成。
  2. 如果空间用完了,需要进行扩容
  3. 效率问题,哈希表最重要的问题就是哈希冲突,使用线性探测法可以发现:哈希表存储数据的空间存储数据时越密集,发生哈希冲突的概率就越大,整个哈希表的效率就会越低,这是我们不愿意看见的,解决这个问题的话,就需要引入载荷因子(负载因子)
    在这里插入图片描述
    将代码进行补充:
template<class K, class V>
class hashTable
{
public:
	bool Insert(const pair<K, V>& val)
	{
		//负载因子超过0.7就扩容
		if (_tables.size() == 0 || _size * 10 / _tables.size() >= 7)
		{
			size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
			hashTable<K, V> newTables;
			newTables._tables.resize(newSize);
			
			//扩容以后由于size的改变,部分元素的位置也会改变,所以需要重新计算位置并插入
			//将原数组中的数组重新插入新数组
			if (_tables.size())
			{
				int i = 0;
				while (_tables[i]._state == EXIST)
				{
					newTables.Insert(_tables[i]._kv);
					i++;
				}
			}
			//重新插入新数组也可以这么实现
			/*for (auto& data : _tables)
			{
				if (data._state == EXIST)
				{
					newTables.Insert(data._kv);
				}
			}*/
			
			_tables.swap(newTables._tables);
		}
		
		//需要注意的是,一定是除余size,而不是capacity
		size_t hashi = val.first % _tables.size();
		size_t j = 1;
		size_t index = hashi;
		//如果发生了哈希冲突,也就是此处地址上存在元素,那就向后探测
		while (_tables[index]._state == EXIST)
		{
			index = hashi + j;//每次都是向后探测一位,j++.
			index = index % _tables.size();//保证了向后探测时如果到了空间末尾,那就从空间开
											//头处继续探测,保证了能够探测到一个合适的位置
			j++;
		}
		//此时index处不存在元素:
		_tables[index]._kv = val;
		_tables[index]._state = EXIST;
		_size++;

		return true;
	}

private:
	vector<Hash<K, V>> _tables;
	int _size = 0;

};

2.2 查找

Hash<K, V>* Find(const K& key)
{
	//如果数组为空就没必要查找了
	if (_tables.size() == 0)
	{
		return nullptr;
	}
	 
	size_t hashi = key % _tables.size();
	size_t i = 1;
	size_t index = hashi;
	
	//之前考虑过如果数组没有装满,元素之间肯定有很多的空隙会为EMPTY,
	//如果这么写的话会不会明明目标位置就在后面,但是早早停下了,但是
	//仔细一想,插入的时候,就是向后探测,只要遇到EMPTY就插入了,所以
	//绝对不会出现这种情况。
	while (_tables[index]._state != EMPTY)
	{
		if (_tables[index]._kv.first == key
			&& _tables[index]._state == EXIST)
		{
			return &_tables[index];
		}
		index = hashi + i;
		index %= _tables.size();
		i++;

		//查找了一圈还是没有找到,那就是没有这个元素
		if (i == _tables.size())
		{
			break;
		}
	}
	return nullptr;
}

2.3 删除

bool Erase(const K& key)
{
	Hash<K, V>* Eindex = Find(key);
	
	
	if (Eindex)
	{
		//注意的是Eindex是一个Hash类型的指针,而不是下标
		Eindex->_state = DELETE;
		_size--;
	}
	else
	{
		return false;
	}
	return true;
}

2.6 完整代码

#pragma once
#include <iostream>
#include <vector>

using namespace std;
enum State
{
	EMPTY,
	EXIST,
	DELETE
};
template<class K, class V>
struct Hash
{
	pair<K, V> _kv;
	State _state = EMPTY;
};


template<class K, class V>
class hashTable
{
public:
	bool Insert(const pair<K, V>& val)
	{
		//负载因子超过0.7就扩容
		if (_tables.size() == 0 || _size * 10 / _tables.size() >= 7
)
		{
			size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
			hashTable<K, V> newTables;
			newTables._tables.resize(newSize);

			//将原数组中的数组重新插入新数组
			/*for (auto& data : _tables)
			{
				if (data._state == EXIST)
				{
					newTables.Insert(data._kv);
				}
			}*/
			if (_tables.size())
			{
				int i = 0;
				while (_tables[i]._state == EXIST)
				{
					newTables.Insert(_tables[i]._kv);
					i++;
				}
			}
			
			_tables.swap(newTables._tables);
		}
		//插入val
		size_t hashi = val.first % _tables.size();
		size_t j = 1;
		size_t index = hashi;
		while (_tables[index]._state == EXIST)
		{
			index = hashi + j;
			index = index % _tables.size();
			j++;
		}
		_tables[index]._kv = val;
		_tables[index]._state = EXIST;
		_size++;

		return true;
	}

	Hash<K, V>* Find(const K& key)
	{
		if (_tables.size() == 0)
		{
			return nullptr;
		}
		size_t hashi = key % _tables.size();
		size_t i = 1;
		size_t index = hashi;
		while (_tables[index]._state != EMPTY)
		{
			if (_tables[index]._kv.first == key
				&& _tables[index]._state == EXIST)
			{
				return &_tables[index];
			}
			index = hashi + i;
			index %= _tables.size();
			i++;

			if (i == _tables.size())
			{
				break;
			}
		}
		return nullptr;
	}

	bool Erase(const K& key)
	{
		Hash<K, V>* Eindex = Find(key);

		if (Eindex)
		{
			Eindex->_state = DELETE;
			_size--;
		}
		else
		{
			return false;
		}
		return true;
	}
private:
	vector<Hash<K, V>> _tables;
	int _size = 0;

};

2.5 线性探测的优缺点

  • 线性探测优点:实现非常简单,
  • 线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同
    关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降
    低。

可以通过二次探测缓解。

3. 二次探测

  线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题.

  二次探测是向后探测是一次向后移动两个单位,这能够缓解很多哈希冲突都发生在邻近的一片位置时效率低下的问题。

  研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。


    😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄

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

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

相关文章

饥荒联机版 Don‘t Starve Together(WinMac)最新中文学习版

《饥荒联机版》是由Klei自主开发的开放世界冒险游戏。在这个游戏中&#xff0c;玩家将扮演各种各样的人物&#xff0c;这些人物不幸来到了一个神秘的异世界。在旅行中&#xff0c;玩家会邂逅性格各异、能力独特的同伴们&#xff0c;并和他们一起生存下去并征服异世界。游戏中的…

每日一题 117. 填充每个节点的下一个右侧节点指针 II (中等,树)

BFS&#xff0c;一层层去搜索整棵树&#xff0c;然后建立next关系即可&#xff0c;下面给出的代码的空间复杂度是O(n)的O(1) 的做法&#xff0c;当构建完上一层的next关系后&#xff0c;我们就可以像链表一样从左到右访问上一层的节点&#xff0c;显然在访问的过程中&#xff0…

N-132基于springboot,vue人事管理系统

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 系统分前后台&#xff0c;项目采用前后端分离 前端技术&#xff1a;vueelementUI 服务端技术&#xff1a;springbootmybatis-plus 本项…

自定义类型结构体(下)

目录 结构体传参结构体实现位段什么是位段位段的内存分配位段的跨平台问题总结&#xff1a; 位段的应用位段使用的注意事项** 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412; 个人主页 &#x1f978;&#x1f978;&a…

DataCastle企业风险算法赛实战(进阶难度)

目录 一、数据读取及分析 1、数据读取 2、数据分析 二、数据挖掘 三、模型构建及评估 四、划重点 推荐相关文章 去年在DataCastle上参加了华录杯算法赛&#xff0c;初赛前10、进复赛就没打了。相比于之前文章 kaggle风控建模实战&#xff08;文末附链接&#xff09;&…

智慧财务的未来

信息化时代&#xff0c;财务管理不再局限于传统的手工操作&#xff0c;而是借助RPA技术实现了自动化、智能化的转型。智慧财务作为财务管理的一种新模式&#xff0c;将为企业提供更加高效、便捷的服务&#xff0c;使企业能够更好地适应市场需求的变化&#xff0c;在瞬息万变的市…

批量删除文件名中的某些文字

怎么批量删除文件名中的某些文字&#xff1f;在整理电脑文件的时候&#xff0c;我们经常需要处理大量文件的重命名工作。当你的文件名称包含不必要或重复的字符时&#xff0c;可以进行批量删除&#xff0c;以使文件名称更简洁、清晰&#xff0c;提高可读性和识别性。例如&#…

【蓝桥杯软件赛 零基础备赛20周】第2周——常考知识点+判题

文章目录 0. 第1周答疑1. 常考知识点2. 蓝桥杯怎么判题2.1 判题系统如何判题2.2 测试数据和得分的关系2.3 自己做测试数据 3. 备赛计划4. 本周刷题 0. 第1周答疑 问题1&#xff1a;蓝桥杯怎么报名&#xff0c;什么时候报名&#xff1f; 答&#xff1a;集体报名或个人报名。大…

【word技巧】ABCD选项如何对齐?

使用word文件制作试卷&#xff0c;如何将ABCD选项全部设置对齐&#xff1f;除了一直按空格或者Tab键以外&#xff0c;还有其他方法吗&#xff1f;今天分享如何将ABCD选项对齐。 首先&#xff0c;我们打开【替换和查找】&#xff0c;在查找内容输入空格&#xff0c;然后点击全部…

利用关键字批量整理文件:用关键字轻松移动多个文件到指定文件夹

在日常生活和工作中&#xff0c;我们经常需要处理大量的文件&#xff0c;随着时间的推移&#xff0c;电脑中的文件越来越多&#xff0c;而文件的命名可能并不规范&#xff0c;导致查找和整理变得非常困难。因此&#xff0c;我们需要一种高效的文件管理方法&#xff0c;以方便我…

一种用醋酸刻蚀氧化铜的新方法

引言 由于乙酸不会氧化铜表面&#xff0c;因此乙酸常用于去除氧化铜而不侵蚀铜膜。乙酸还具有低表面张力&#xff0c;易于从表面移除。因此&#xff0c;不需要去离子(DI)水冲洗来移除残留的乙酸&#xff0c;可以防止水冲洗导致的铜再氧化。 英思特研究了在低温下用乙酸去除氧…

SSE加速随笔

Intel Intrinsics Guide 搞懂SSE 寄存器与指令数据细节 SSE指令集推出时&#xff0c;Intel公司在Pentium III CPU中增加了8个128位的SSE指令专用寄存器&#xff0c;称作XMM0到XMM7。这些XMM寄存器用于4个单精度浮点数运算的SIMD执行&#xff0c;并可以与MMX整数运算或x87浮点运…

01、SpringBoot + MyBaits-Plus 集成微信支付 -->项目搭建

目录 SpringBoot MyBaits-Plus 集成微信支付 之 项目搭建1、创建boot项目2、引入Swagger作用&#xff1a;2-1、引入依赖2-2、写配置文件进行测试2-3、访问Swagger页面2-4、注解优化显示 3、定义统一结果作用&#xff1a;3-1、引入lombok依赖3-2、写个统一结果的类-->RR类的…

Windows、程序员必装的工具

一、Typora 啥也不说了直接上图 Markdown语法 Typora免费版 提取码&#xff1a;av01 二维码&#xff1a; 1&#xff09;页面展示 2&#xff09;主题 3&#xff09;偏好设置 4&#xff09;Markdown语法设置偏好 5&#xff09;编辑器 6&#xff09;系统 二、Snipaste Snipaste…

Java反射详解:入门+使用+原理+应用场景

反射非常强大和有用&#xff0c;现在市面上绝大部分框架(spring、mybatis、rocketmq等等)中都有反射的影子&#xff0c;反射机制在框架设计中占有举足轻重的作用。 所以&#xff0c;在你Java进阶的道路上&#xff0c;你需要掌握好反射。 怎么才能学好反射&#xff0c;我们需要…

STM32CubeIDE安装中文语言包

软件包地址&#xff1a;https://archive.eclipse.org/technology/babel/update-site/R0.16.1/2018-12/ 打开IDE&#xff1a;Help--->Install New Software--->Add Locations&#xff1a;输入软件包地址 等待搜索结束&#xff1a;选择中文语言包&#xff0c;单击next进行安…

QQ邮箱发送验证码源码/API+HTML源码/支持API接口、自定义地址和内容/简单易用

源码简介&#xff1a; 一款支持API接口、自定义QQ邮箱地址、自定义邮箱标题和内容的源码。 近来我正在开发一款新的软件&#xff0c;但在注册环节中遇到了一个问题&#xff1a;需要使用QQ邮箱验证码。因此&#xff0c;我积极寻找相关的API接口或开发文档&#xff0c;但遗憾的…

学习小结,学而时习之,坚持学习之,温顾学习之

学习python一个多月了&#xff0c;之前也有接触过&#xff0c;还花了不少钱报班&#xff0c;看了看入门的头两节课&#xff0c;就止步了。每一种编程语言的入门感觉都差不多&#xff0c;学到现在&#xff0c;我对python的基本数据类型还是没掌握好啊&#xff0c;每次列表字典怎…

面向萌新的数学建模入门指南

时间飞逝&#xff0c;我的大一建模生涯也告一段落。感谢建模路上帮助过我的学长和学姐们&#xff0c;滴水之恩当涌泉相报&#xff0c;写下这篇感想&#xff0c;希望可以给学弟学妹们一丝启发&#xff0c;也就完成我的想法了。拙劣的文笔&#xff0c;也不知道写些啥&#xff0c;…

二叉树采用二叉链表存储:编写计算整个二叉树高度的算法

二叉树采用二叉链表存储&#xff1a;编写计算整个二叉树高度的算法 (二叉树的高度也叫二叉树的深度) 代码思路&#xff1a; 首先你要明白什么是树的高度&#xff0c;简言之就是树有多少层&#xff0c;如下图&#xff1a; 下面这棵树的高度就是4 首先我们观察根节点&#xff0…