哈希的基本概念(开散列和闭散列)(附代码)

news2024/9/28 9:31:21

哈希

  • 哈希概念
    • 哈希冲突
    • 哈希函数
      • 常见的哈希函数
    • 哈希冲突的解决
      • 闭散列
      • 开散列

哈希概念

传统的查找函数,搜索的效率取决于比较的次数。而hash算法:在理想情况下,可以不经过任何比较,一次就能得到要搜索的结果。
存储结构:通过某种(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,所以在查找时,通过该映射函数可以很快找到该元素。

  • 插入元素
    根据待插入元素的关键字,以此函数计算出该元素的存储位置并按此位置进行存放。
  • 搜索元素
    对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。

该方法就是哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表。
大致结构如下图所示:

在这里插入图片描述

哈希冲突

不同的关键词通过相同的哈希函数映射到同一个地址,该现象叫做哈希冲突/哈希碰撞。
如:元素11再插入上图中,就会和元素1产生碰撞。
1和11就是同义词

哈希函数

引起哈希冲突的一个原因可能是:哈希函数设置不合理。
哈希函数设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应比较简单

常见的哈希函数

  1. 直接定址法(常用)
    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key+B
    优点:简单、均匀
    缺点:需要事先知道关键字的分布情况
    使用场景:适合查找比较小的连续的情况

  2. 除留余数法(常用)
    设散列表中允许的地址数为m,取一个不大于m,但最接近或等于m的质数p作为除数,按照哈希函数:Hash(key) = key % p (p<=m),将关键码转成哈希地址

  3. 平方取中法
    假设关键字是1234,对它的平方就是1522756,抽取中间3位227作为哈希地址;
    再比如关键字4321,平方是18671041,抽取中间的三位671或者710作为哈希地址。
    平方取中法适合:不知道关键字分布,而位数又不是很大的情况。

  4. 折叠法
    折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。

  5. 随机数法
    选择一个随机函数,去关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数函数。
    通常应用于关键字不等时采用此法。

  6. 数学分析法
    在这里插入图片描述
    在这里插入图片描述

哈希冲突的解决

解决哈希冲突的两种常见方法是:闭散列和开散列。

闭散列

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

  1. 线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
  2. 闭散列处理冲突时,不能随便删除物理哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。

闭散列线性探测实现:

	enum class State
	{
		EMPTY,
		EXIST,
		DELETE
	};

	template<class K, class V>
	struct Elem   //数据类型
	{
		pair<K, V> _data;
		State _state=State::EMPTY;
	};

	template<class K, class V>
	class Hashtables
	{
	public:
		bool insert(const pair<K, V>& data)
		{
			if (Find(data.first))
				return false;
			//如果不满足负载,就扩容
			CheckSize();
			
			//求地址
			size_t hashi = data.first % _tables.size(); //不能是capacity,vector天然不支持


			//插入
 			while(_tables[hashi]._state == State::EXIST)
			{
				hashi++;   //线性探测法
				hashi %= _tables.size();
			}
			_tables[hashi]._data = data;
			_tables[hashi]._state = State::EXIST;
			n++;

			return true;
		}

		不方便删除接口
		//pair<K, V>* Find(const K& key)
		//{
		//	if (_tables.size() == 0)
		//	{
		//		return nullptr;
		//	}

		//	size_t hashi = key % _tables.size();
		//	size_t i = 1;
		//	while (_tables[hashi]._state==State::EXIST||_tables[hashi]._state==State::DELETE)  //存在或删除,不影响探测
		//	{
		//		if (_tables[hashi]._state == State::EXIST
		//			&& _tables[hashi]._data.first != key)
		//		{
		//			hashi = hashi + i;
		//			hashi %= _tables.size();
		//			i++;
		//		}
		//		else
		//		{
		//			return &_tables[hashi]._data;
		//		}
		//		if (hashi == key % _tables.size())  //转一圈也没找到
		//		{
		//			return nullptr;
		//		}

		//	}
		//	
		//	return nullptr;
		//}


		Elem<K,V>* Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return nullptr;
			}

			size_t hashi = key % _tables.size();
			size_t i = 1;

			//逻辑有问题
			while (_tables[hashi]._state == State::EXIST || _tables[hashi]._state == State::DELETE)  //存在或删除,不影响探测
			{
				if (_tables[hashi]._state == State::EXIST && _tables[hashi]._data.first == key)
				{
					return &_tables[hashi];

				}
				else    //接着查找
				{
					hashi = hashi + i;
					hashi %= _tables.size();
				}
				if (hashi == key % _tables.size())  //转一圈也没找到,说明全是存在+删除
				{
					return nullptr;
				}

			}

			return nullptr;
		}


		bool erase(const K& key)
		{
			auto it = Find(key);
			if (!it)
			{
				return false;
			}
			//错了!!!!!!!!!!!!!!!
			//it._state == State::DELETE;
			it->_state = State::DELETE;

			n--;   //别忘记计数减一
			return true;
		}

	private:
		void CheckSize()
		{
 			if (_tables.size()==0 ||(double)n / (double)_tables.size() > 0.7)
			{
				int newcapacity = (_tables.size() == 0 ? 10 : _tables.size() * 2);

				vector<Elem<K, V>> newTables(newcapacity);

				//再次重新插入
				for (auto& e : _tables)
				{
					if (e._state == State::EXIST)
					{
						size_t hashi = e._data.first % newTables.size();
						while (newTables[hashi]._state == State::EXIST)
						{
							hashi++;
							hashi %= newTables.size();
						}

						newTables[hashi]._data = e._data;
						newTables[hashi]._state = State::EXIST;
					}
				}

				swap(_tables, newTables);
			}

		}

	private:
		vector<Elem<K, V>> _tables;   //哈希表
		size_t n=0;    //表示插入数据的大小
	};

线性探测优点:实现简单
缺点:一旦发生冲突,所有冲突连在一起,容易产生“堆积”数据。

也可以用二次探测解决即:线性探测由hashi = hashi+i -> hashi + i^2。


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

因此闭散列最大的缺陷:空间利用率低,这也是哈希的缺陷。

开散列

开散列又叫链地址法,首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各个链表的第一个结点存在哈希表中。

namespace HashBucket
{
	template<class K, class V>
	struct Elem
	{
		Elem(const pair<K, V>& data)
			:_data(data)
			,_next(nullptr)
		{}


		pair<K, V> _data;
		Elem<K, V> *_next;
	};

	template<class K, class V>
	class HashTables
	{
		typedef Elem<K, V> Node;
		typedef pair<K, V> Data;
		//typedef HashTables<K, V> HashTables;
	public:
		bool insert(const Data& data)
		{
			//负载因子过大就扩容
			CheckCapacity();

			//散列地址
			size_t hashi = data.first % _tables.size();

			//直接插入头插
			Node* newNode = new Node(data);
			newNode->_next = _tables[hashi];
			_tables[hashi] = newNode;
			n++;
			return true;

		}

		Node* find(const K& key)
		{
			size_t hashi = key % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_data.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}


		bool erase(const K& key)
		{
			size_t hashi = key % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (cur->_data.first == key)
				{
					if (!prev)   //prev为空,说明是在表上
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;

		}
	private:
		//扩容有问题,不应该插入一个就扩容,插入一个就扩容!!!!!!!!!!!!
		void CheckCapacity()
		{
			//表为空或者负载因子大于0.7了就扩容
			//if (!_tables.size() || ((n * 10) / (_tables.size() * 10) > 7))
			/*if (!_tables.size() || ((n * 10) / _tables.size() > 7))*/
			if(n==_tables.size())
			{
				size_t newCapacity = (_tables.size() == 0 ? 10 : _tables.size() * 2);
				HashTables* newHashTable = new HashTables;
				newHashTable->_tables.resize(newCapacity);
				
				//将旧表的数据重新映射到新表
				//遍历旧表
				for (auto& e : _tables)
				{
					if (e)  //不空,就有数据
					{
						Node* cur = e;  
						Node* next;   //下一个指针
						while (cur)
						{
							size_t hashi = cur->_data.first % newCapacity;
							next = cur->_next;  //暂存下一个
							cur->_next = newHashTable->_tables[hashi];
							newHashTable->_tables[hashi] = cur;     //头插
							//newHashTable->n++;
							cur = next;

						}
					}
				}
				
				//swap(*newHashTable, *this);
				_tables.swap(newHashTable->_tables);

			}
		}

	private:
		vector<Node*> _tables;
		size_t n = 0;
	};

}

哈希桶的增容问题:
在这里插入图片描述

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

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

相关文章

四、MyBatis 动态语句

本章概要 动态语句需求和简介if 和 where 标签set 标签trim 标签(了解)choose/when/otherwise 标签foreach 标签sql 片段 4.1 动态语句需求和简介 经常遇到很多按照很多查询条件进行查询的情况&#xff0c;比如智联招聘的职位搜索等。其中经常出现很多条件不取值的情况&#…

电脑监控系统:企业网络安全解决方案

在当今数字化的世界里&#xff0c;企业的网络安全已经成为一项至关重要的任务。电脑监控系统作为一种有效的解决方案&#xff0c;正在被越来越多的企业所采用。 电脑监控系统是一种集成了多种安全功能的综合性解决方案&#xff0c;旨在为企业提供全面的网络安全防护。该系统能够…

【操作系统】实验七 显示进程列表

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

Java 报错java.Net.UnknownHostException:raw.githubusercontent.com

1.问题 今天在vscode 学习如何使用 plantUML生成图片的时候&#xff0c;发生错误 java.util.concurrent.ExecutionException: java.net.UnknownHostException: raw.githubusercontent.com issue raw.githubusercontent.com java.util.concurrent.ExecutionException: java.n…

手写一个图形验证码

文章目录 需求分析 需求 使用 JS 写一个验证码&#xff0c;并在前端进行校验 分析 新建文件 VueImageVerify.vue <template><div class"img-verify"><canvas ref"verify" :width"state.width" :height"state.height&qu…

“研学测”好帮手,三步带你安装体验TDH社区开发版

星环科技TDH社区开发版&#xff0c;作为一款单机可部署、开箱即用的大数据基础平台产品&#xff0c;大幅降低了用户的资源成本和使用门槛。与此同时&#xff0c;TDH社区开发版兼顾此前TDH社区版&#xff08;分布式&#xff09;组件成熟、简单易用、易运维等特点&#xff0c;可以…

《剑指 Offer》专项突破版 - 面试题 28 : 展平多级双向链表(C++ 实现)

题目连接&#xff1a;LCR 028. 扁平化多级双向链表 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 在一个多级双向链表中&#xff0c;节点除了有两个指针分别指向前后两个节点&#xff0c;还有一个指针指向它的子链表&#xff0c;并且子链表也是一个双向链表&…

深入理解MySQL InnoDB线程模型

当我们谈论数据库性能时&#xff0c;存储引擎的线程模型是一个不可忽视的方面。MySQL的InnoDB存储引擎&#xff0c;作为目前最受欢迎的存储引擎之一&#xff0c;其线程模型的设计对于实现高并发、高性能的数据操作至关重要。在本文中&#xff0c;我们将深入探讨MySQL InnoDB线程…

day31_CSS

今日内容 CSS概述引入方式 (where)选择器(how)属性(how) 1 CSS介绍 层叠样式表&#xff08;cascading style sheet&#xff09; CSS 用来美化HTML页面,可以让页面更好看,还可以布局页面. 好处 美化页面,布局页面使用外部css文件,可以实现样式文件和html文件分离,便于维护使用外…

JS进阶-内置构造函数(二)

小提示&#xff1a;这些内置函数在开发使用的频率非常的频繁&#xff0c;建议认真看一下&#xff0c;并背一下 目录 知识回顾&#xff1a; • Object 三个常用静态方法&#xff08;静态方法就是只有构造函数Object可以调用的&#xff09; Object.keys Object.values Obj…

【2024】下载安装Cisco Packet Tracer 8.2.1

一、注册账号 进入www.cisco.com 点击右上角的Log in 点击注册 之后输入邮箱和其他相关信息&#xff0c;正常注册即可 唯一注意的点&#xff1a;国家或地区 选项中别选China&#xff0c;否则之后登录软件时会有问题 二、下载安装包 进入packet-tracer下载&#xff0c;下…

【Tailwind】各种样式的进度条

基本样式进度条&#xff1a; <div class"mb-5 h-2 rounded-full bg-gray-200"><div class"h-2 rounded-full bg-orange-500" style"width: 50%"></div> </div>带文字的进度条&#xff1a; <div class"relativ…

品优购项目规划

1&#xff0c;网站favicon图标 favicon.ico 一般用于作为缩略的网站标注&#xff0c;它显示在浏览器的地址栏或者标签上 1&#xff0c;制作favicon图标 ①把品优购图标切成png图片 ②把png图片转换为ico图标&#xff0c;这需要借助于第三方转换网站&#xff0c;比如 比特虫…

基于移动边缘计算 (MEC) 的资源调度分配优化研究(提供MATLAB代码)

一、优化模型简介 边缘计算资源调度优化模型是为了解决边缘计算场景下的资源分配和任务调度问题而提出的一种数学模型。该模型旨在通过优化算法来实现资源的有效利用和任务的高效执行&#xff0c;以提高边缘计算系统的性能和用户的服务体验。 在边缘计算资源调度优化模型中&a…

基于vue实现计数器案例

一、需求 页面显示两个按钮&#xff0c;分别为&#xff1a;增加 和 减少&#xff1b;显示加减后的数字&#xff1b;加到10提示不能再加&#xff0c;减到0提示不能再减&#xff1b; 二、代码演示 1、实现步骤 data中定义数据: 比如 num 值为1methods中添加两个方法: 比如add…

普通人如何打造自己人生的护城河?

哈喽&#xff0c;大家好啊&#xff0c;我是雷工。 今天在看《张一鸣管理日志》时看到这么一句话&#xff1a; 今日头条不断吸引更优秀的工程师&#xff0c;不断更新算法&#xff0c;才有了当前今日头条的算法护城河。 头条的算法有多牛&#xff0c;看你周边就知道了。 越来越多…

《WebKit 技术内幕》学习之十一(2):多媒体

2 视频 2.1 HTML5视频 在HTML5规范定义中&#xff0c;Web开发者可以使用“video”元素来播放视频资源。视频中有个重要的问题就是视频编码格式&#xff0c;对此&#xff0c;目前标准中包含了三种编码格式&#xff0c;它们分别是Ogg、MPEG4和WebM。其中Ogg是由Xiph.org组织开…

鼠标移入/点击子组件,获取选中子组件事件

不管是移入&#xff0c;或者是点击事件 都要知道是触发的哪个组件 这里子组件是个通用小标题title 所以&#xff0c;通过标题内容&#xff0c;获取触发的哪个子组件子组件 <template><div mouseover"tMouseover" mouseleave"tMouseLeave" class&…

如何使用iPhone或iPad上的二维码共享Wi-Fi密码?这里有详细步骤

你有没有想过在不泄露网络密码的情况下与客人共享你的家庭或工作Wi-Fi?你肯定不是第一个这样想的人,我们很高兴地通知你,多亏了以下这个的变通方法,你现在可以使用iPhone或iPad做到这一点。 通常,如果你想让其他人访问网络,你需要共享你的Wi-Fi密码。苹果通过引入与任何…