数据结构--哈希表(Hash Table)

news2024/11/27 3:50:57

1. 哈希表简介

哈希表(Hash Table):也叫做散列表。是根据关键码值(Key Value)直接进行访问的数据结构。
哈希表通过「键 key 」和「映射函数 Hash(key) 」计算出对应的「值 value」,把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做「哈希函数(散列函数)」,存放记录的数组叫做「哈希表(散列表)」。

哈希表的关键思想是使用哈希函数,将键 key 映射到对应表的某个区块中。我们可以将算法思想分为两个部分:

  • 向哈希表中插入一个关键码值:哈希函数决定该关键字的对应值应该存放到表中的哪个区块,并将对应值存放到该区块中。
  • 在哈希表中搜索一个关键码值:使用相同的哈希函数从哈希表中查找对应的区块,并在特定的区块搜索该关键字对应的值。

哈希表的原理示例图如下所示:
在这里插入图片描述
在上图例子中,我们使用 value = Hash(key) = key // 1000 作为哈希函数。// 符号代表整除。我们以这个例子来说明一下哈希表的插入和查找策略。

  • 向哈希表中插入一个关键码值:通过哈希函数解析关键字,并将对应值存放到该区块中。
    • 比如:0138 通过哈希函数 Hash(key) = 0138 // 100 = 0,得出应将 0138 分配到0 所在的区块中。
  • 在哈希表中搜索一个关键码值:通过哈希函数解析关键字,并在特定的区块搜索该关键字对应的值。
    • 比如:查找 2321,通过哈希函数,得出 2321 应该在 2 所对应的区块中。然后我们从 2 对应的区块中继续搜索,并在 2 对应的区块中成功找到了 2321。
    • 比如:查找 3214,通过哈希函数,得出 3214 应该在 3 所对应的区块中。然后我们从 3 对应的区块中继续搜索,但并没有找到对应值,则说明 3214 不在哈希表中。

哈希表在生活中的应用也很广泛,其中一个常见例子就是「查字典」。
比如为了查找 赞 这个字的具体意思,我们在字典中根据这个字的拼音索引 zan,查找到对应的页码为 599。然后我们就可以翻到字典的第 599 页查看 赞 字相关的解释了。

在这里插入图片描述
在这个例子中:

  • 存放所有拼音和对应地址的表可以看做是 「哈希表」
  • 赞 字的拼音索引 zan 可以看做是哈希表中的 「关键字 key」
  • 根据拼音索引 zan 来确定字对应页码的过程可以看做是哈希表中的 「哈希函数 Hash(key)」
  • 查找到的对应页码 599 可以看做是哈希表中的 「哈希地址 value」

2. 哈希函数

哈希函数(Hash Function):将哈希表中元素的关键键值映射为元素存储位置的函数。

哈希函数是哈希表中最重要的部分。一般来说,哈希函数会满足以下几个条件:

  • 哈希函数应该易于计算,并且尽量使计算出来的索引值均匀分布。
  • 哈希函数计算得到的哈希值是一个固定长度的输出值。
  • 如果 Hash(key1) 不等于 Hash(key2),那么 key1、key2 一定不相等。
  • 如果 Hash(key1) 等于 Hash(key2),那么 key1、key2 可能相等,也可能不相等(会发生哈希碰撞)。

哈希表的实际应用中,关键字的类型除了数字类,还有可能是字符串类型、浮点数类型、大整数类型,甚至还有可能是几种类型的组合。一般我们会将各种类型的关键字先转换为整数类型,再通过哈希函数,将其映射到哈希表中。

而关于整数类型的关键字,通常用到的哈希函数方法有:直接定址法、除留余数法、平方取中法、基数转换法、数字分析法、折叠法、随机数法、乘积法、点积法等。下面我们介绍几个常用的哈希函数方法。

2.1 直接定址法
  • 直接定址法:取关键字本身 / 关键字的某个线性函数值 作为哈希地址。即:Hash(key) = key 或者 Hash(key) = a * key + b,其中 a 和 b 为常数。

这种方法计算最简单,且不会产生冲突。适合于关键字分布基本连续的情况,如果关键字分布不连续,空位较多,则会造成存储空间的浪费。

举一个例子,假设我们有一个记录了从 1 岁到 100 岁的人口数字统计表。其中年龄为关键字,哈希函数取关键字自身,如下表所示。
在这里插入图片描述
比如我们想要查询 25 岁的人有多少,则只要查询表中第 25 项即可。

2.2 除留余数法
  • 除留余数法:假设哈希表的表长为 m,取一个不大于 m 但接近或等于 m 的质数 p,利用取模运算,将关键字转换为哈希地址。即:Hash(key) = key % p,其中 p 为不大于 m 的质数。

这也是一种简单且常用的哈希函数方法。其关键点在于 p 的选择。根据经验而言,一般 p 取素数或者 m,这样可以尽可能的减少冲突。

比如我们需要将 7 个数 [432, 5, 128, 193, 92, 111, 88] 存储在 11 个区块中(长度为 11 的数组),通过除留余数法将这 7 个数应分别位于如下地址:
在这里插入图片描述

2.3 平方取中法
  • 平方取中法:先通过求关键字平方值的方式扩大相近数之间的差别,然后根据表长度取关键字平方值的中间几位数为哈希地址。
    • 比如:Hash(key) = (key * key) // 100 % 1000,先计算平方,去除末尾的 2 位数,再取中间 3 位数作为哈希地址。

这种方法因为关键字平方值的中间几位数和原关键字的每一位数都相关,所以产生的哈希地址也比较均匀,有利于减少冲突的发生。

2.4 基数转换法
  • 基数转换法:将关键字看成另一种进制的数再转换成原来进制的数,然后选其中几位作为哈希地址。
    • 比如,将关键字看做是 13 进制的数,再将其转变为 10 进制的数,将其作为哈希地址。

以 343246 为例,哈希地址计算方式如下:
34324613 = 3 × 1 3 5 + 4 × 1 3 4 + 3 × 1 3 3 + 2 × 1 3 2 + 4 × 1 3 1 + 6 × 1 3 0 = 123511010 343246{13} = 3 \times 13^5 + 4 \times 13^4 + 3 \times 13^3 + 2 \times 13^2 + 4 \times 13^1 + 6 \times 13^0 = 1235110{10} 34324613=3×135+4×134+3×133+2×132+4×131+6×130=123511010

3. 哈希冲突

哈希冲突(Hash Collision):不同的关键字通过同一个哈希函数可能得到同一哈希地址,即 key1 ≠ key2,而 Hash(key1) = Hash(key2),这种现象称为哈希冲突。

理想状态下,我们的哈希函数是完美的一对一映射,即一个关键字(key)对应一个值(value),不需要处理冲突。但是一般情况下,不同的关键字 key 可能对应了同一个值 value,这就发生了哈希冲突。

设计再好的哈希函数也无法完全避免哈希冲突。所以就需要通过一定的方法来解决哈希冲突问题。常用的哈希冲突解决方法主要是两类:「开放地址法(Open Addressing)」「链地址法(Chaining)」

3.1 开放地址法

开放地址法(Open Addressing):指的是将哈希表中的「空地址」向处理冲突开放。当哈希表未满时,处理冲突时需要尝试另外的单元,直到找到空的单元为止。

当发生冲突时,开放地址法按照下面的方法求得后继哈希地址:H(i) = (Hash(key) + F(i)) % m,i = 1, 2, 3, …, n (n ≤ m - 1)。

  • H(i) 是在处理冲突中得到的地址序列。即在第 1 次冲突(i = 1)时经过处理得到一个新地址 H(1),如果在 H(1) 处仍然发生冲突(i = 2)时经过处理时得到另一个新地址 H(2) …… 如此下去,直到求得的 H(n) 不再发生冲突。
  • Hash(key) 是哈希函数,m 是哈希表表长,对哈希表长取余的目的是为了使得到的下一个地址一定落在哈希表中。
  • F(i) 是冲突解决方法,取法可以有以下几种:
    • 线性探测法: F ( i ) = 1 , 2 , 3 , . . . , m − 1 F(i) = 1, 2, 3, ..., m - 1 F(i)=1,2,3,...,m1
    • 二次探测法: F ( i ) = 1 2 , − 1 2 , 2 2 , − 2 2 , . . . , ± n 2 ( n ≤ m / 2 ) F(i) = 1^2, -1^2, 2^2, -2^2, ..., \pm n^2(n \le m / 2) F(i)=12,12,22,22,...,±n2(nm/2)
    • 伪随机数序列: F ( i ) = 伪随机数序列 F(i) = 伪随机数序列 F(i)=伪随机数序列

举个例子说说明一下如何用以上三种冲突解决方法处理冲突,并得到新地址 H(i)。例如,在长度为 11 的哈希表中已经填有关键字分别为 28、49、18 的记录(哈希函数为 Hash(key) = key % 11)。现在将插入关键字为 38 的新纪录。根据哈希函数得到的哈希地址为 5,产生冲突。接下来分别使用这三种冲突解决方法处理冲突。

  • 使用线性探测法:得到下一个地址 H(1) = (5 + 1) % 11 = 6,仍然冲突;继续求出 H(2) = (5 + 2) % 11 = 7,仍然冲突;继续求出 H(3) = (5 + 3) % 11 = 8,8 对应的地址为空,处理冲突过程结束,记录填入哈希表中序号为 8 的位置。
  • 使用二次探测法:得到下一个地址 H(1) = (5 + 11) % 11 = 6,仍然冲突;继续求出 H(2) = (5 - 11) % 11 = 4,4 对应的地址为空,处理冲突过程结束,记录填入哈希表中序号为 4 的位置。
  • 使用伪随机数序列:假设伪随机数为 9,则得到下一个地址 H(1) = (9 + 5) % 11 = 3,3 对应的地址为空,处理冲突过程结束,记录填入哈希表中序号为 3 的位置。

使用这三种方法处理冲突的结果如下图所示:
在这里插入图片描述

3.2 链地址法

链地址法(Chaining):将具有相同哈希地址的元素(或记录)存储在同一个线性链表中。

链地址法是一种更加常用的哈希冲突解决方法。相比于开放地址法,链地址法更加简单。

我们假设哈希函数产生的哈希地址区间为 [0, m - 1],哈希表的表长为 m。则可以将哈希表定义为一个有 m 个头节点组成的链表指针数组 T。

  • 这样在插入关键字的时候,我们只需要通过哈希函数 Hash(key) 计算出对应的哈希地址 i,然后将其以链表节点的形式插入到以 T[i] 为头节点的单链表中。在链表中插入位置可以在表头或表尾,也可以在中间。如果每次插入位置为表头,则插入操作的时间复杂度为 O ( 1 ) O(1) O(1)
  • 而在在查询关键字的时候,我们只需要通过哈希函数 Hash(key) 计算出对应的哈希地址 i,然后将对应位置上的链表整个扫描一遍,比较链表中每个链节点的键值与查询的键值是否一致。查询操作的时间复杂度跟链表的长度 k 成正比,也就是 O ( k ) O(k) O(k)。对于哈希地址比较均匀的哈希函数来说,理论上讲,k = n // m,其中 n 为关键字的个数,m 为哈希表的表长。
  • 举个例子来说明如何使用链地址法处理冲突。假设现在要存入的关键字集合 keys = [88, 60, 65, 69, 90, 39, 07, 06, 14, 44, 52, 70, 21, 45, 19, 32]。再假定哈希函数为 Hash(key) = key % 13,哈希表的表长 m = 13,哈希地址范围为 [0, m - 1]。将这些关键字使用链地址法处理冲突,并按顺序加入哈希表中(图示为插入链表表尾位置),最终得到的哈希表如下图所示。

在这里插入图片描述
相对于开放地址法,采用链地址法处理冲突要多占用一些存储空间(主要是链节点占用空间)。但它可以减少在进行插入和查找具有相同哈希地址的关键字的操作过程中的平均查找长度。这是因为在链地址法中,待比较的关键字都是具有相同哈希地址的元素,而在开放地址法中,待比较的关键字不仅包含具有相同哈希地址的元素,而且还包含哈希地址不相同的元素。

4. 哈希表总结

本文讲解了一些比较基础、偏理论的哈希表知识。包含哈希表的定义,哈希函数、哈希冲突以及哈希冲突的解决方法。

  • 哈希表(Hash Table):通过键 key 和一个映射函数 Hash(key) 计算出对应的值 value,把关键码值映射到表中一个位置来访问记录,以加快查找的速度。
  • 哈希函数(Hash Function):将哈希表中元素的关键键值映射为元素存储位置的函数。
  • 哈希冲突(Hash Collision):不同的关键字通过同一个哈希函数可能得到同一哈希地址。

哈希表的两个核心问题是:「哈希函数的构建」「哈希冲突的解决方法」

  • 常用的哈希函数方法有:直接定址法、除留余数法、平方取中法、基数转换法、数字分析法、折叠法、随机数法、乘积法、点积法等。
  • 常用的哈希冲突的解决方法有两种:开放地址法和链地址法。

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

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

相关文章

阿里企业邮箱能发开发信吗?群发邮件技巧?

阿里企业邮箱如何群发营销邮件?邮箱发开发信的方法? 对于许多企业而言,开发信是一种重要的沟通工具,用于与客户、合作伙伴和供应商建立联系。在本文中,蜂邮EDM将探讨阿里企业邮箱是否支持发送开发信,以及如…

通讯网关软件025——利用CommGate X2Modbus实现Modbus RTU访问DDE数据源

本文介绍利用CommGate X2Modbus实现Modbus RTU访问DDE数据源。CommGate X2MODBUS是宁波科安网信开发的网关软件,软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示,实现上位机通过Modbus RTU来获取DDE数据源的数据。 【解决方…

推荐5款各种原因导致很少人知道的小软件

​ 很多软件用起来很好用,但是由于这样那样的原因,一直没什么知名度,但是不代表它们不好用,我的任务就是把这些宝藏分享给大家。 1.图像编辑——GIMP ​ GIMP是一款免费的开源图像编辑器,可以用于创建和修改图像&…

word写论文遇到数字和文字中间空格删不掉

一、如何删除? 1、选中需要有数字和汉字那段文字 2、点击段落下拉 3、找到中文版式 4、将【自动调整中文与数字的间距】取消勾选(不要勾选) 5、点击确定即可删除啦

众和策略:小盘和大盘的关系?

在股票商场上,股票能够被分为小盘股和大盘股两类。那么,二者之间有什么联络呢?这篇文章将从多个视点来分析小盘和大盘的联络。 商场表现 首要,让我们看看两者在商场表现上的差异。大盘股是市值较大的股票,一般在首要股…

探寻蓝牙的未来:从蓝牙1.0到蓝牙5.4,如何引领无线连接革命?

►►►蓝牙名字的来源 这要源于一个小故事,公元940-985年,哈洛德布美塔特(Harald Blatand),后人称Harald Bluetooth,统一了整个丹麦。他的名字“Blatand”可能取自两个古老的丹麦词语。“bla”意思是黑皮肤的,而“tan…

基于SSM的小区疫情防控管理系统设计与实现

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用JSP技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…

东风新能源电动汽车E60/E70在驾培驾考领域的CAN数据应用

最近几年,我国驾培行业新增学员数量保持了三年的持续下降,与此同时,教学车辆和驾驶培训机构数量则在持续上升,由此可见驾培市场呈现出不平衡的状态,供大于求已经成为常态。 现在的年轻人,并不把开车作为职…

Elucidating the Design Space of Diffusion-Based Generative Models 阅读笔记

文章使用模块化(modular)的思想,分别从采样、训练、score network设计三个方面分析和改进diffusion-based models。 之前的工作1已经把diffusion-based models统一到SDE或者ODE框架下了,这篇文章的作者同样也从SDE和ODE的角度出发…

NewStarCTF2023week2-base!(base低位隐写)

附件内容是很多的base64编码的字符串 常见的Base64隐写一般会给一个txt文本文档,内含多个经过base64编码的字符串。解码规则是将所有被修改过的base64字符串结尾的二进制值提取出来组成一个二进制串,以8位分割并转为十进制值,最终十进制对应的…

scitb5函数1.7版本(交互效应函数P for interaction)发布----用于一键生成交互效应表、森林图

在SCI文章中,交互效应表格(通常是表五)能为文章锦上添花,增加文章的信服力,增加结果的可信程度,还能进行数据挖掘。 交互效应表我在既往文章《R语言手把手教你制作一个交互效应表》已经介绍怎么制作了&…

用例图中的各种关系

一、用例图中的各种关系 a)参与者与用例间的关联关系:参与者与用例之间的通信,也成为关联或通信关系。 b)用例与用例之间的关系:包含关系(include)、扩展关系(extend)、泛…

ND协议——无状态地址自动配置 (SLAAC)

参考学习:计算机网络 | 思科网络 | 无状态地址自动配置 (SLAAC) | 什么是SLAAC_瘦弱的皮卡丘的博客-CSDN博客 与 IPv4 类似,可以手动或动态配置 IPv6 全局单播地址。但是,动态分配 IPv6 全局单播地址有两种方法: 如图所示&#…

内存占用问题

虚拟内存介绍 虚拟内存就是将部分磁盘变成内存的拓展,用上去就好像是将内存变大了一样。 比如同样是16G的物理内存,有人能比你多开几个应用,你开两三个就要黑屏,然后浏览器说你内存不够。 打开任务管理器,内存也没有…

盛元广通矿企煤炭检测实验室信息管理系统3.0

系统概述: 为更好的为委托方提供准确可靠的检测数据和检测结果,全方位提升实验室形象和客户满意度、提高实验室整体经济效益;确保煤炭检测实验室数据的完整性、合法性、可追溯性以及提升实验室的技术和管理水平,盛元广通矿企煤炭…

ROS系列(二):rosbag 中提取视频数据

一、环境安装 当前环境在上一篇文章的基础上进行配置。 ROS系列(一):【环境配置】rosbag 包安装_安装rosbag-CSDN博客 继续安装 sudo apt install ffmpeg python 包如下 pip install sensor_msgs --extra-index-url https://rospypi.gi…

ASP.NET LIMS系统全套源码(演示+自主版权+项目使用)

基于ASP.NET Dotnet 3.5 EXT.NETMSSQL 2018技术架构开发的LIMS系统全套源码(演示自主版权项目使用) LIMS是为检测组织全流程业务设计的。以实验室为中心,将实验室的业务流程、环境、人员、仪器设备、标物标液、化学试剂、规范办法、图书资料、…

[C++]:1.初识C++和C语言缺陷补充。

初识C和C语言缺陷补充 一.主要内容:二.具体内容:一: 作用域1.命名空间:2.函数声明和定义:3.不存在命名冲突的情况: 二.输入输出:1.基本输入输出:2.关于std的展开: 三.函数…

Docker逃逸---SYS_PTRACE浅析

一、产生原因 用户授予了容器SYS_PTRACE权限,并且与宿主机共享一个进程命名空间(--pidhost),使得容器内可以查看到宿主机的进程,攻击者可以利用进程注入,反弹shell,从而实现逃逸 二、利用条件 1、容器有SYS_PTRACE权…

解决安装nvm以后windows cmd无法找到npm/yarn命令的问题

安装了nodejs多版本管理工具nvm以后,会出现windows cmd无法找到npm/yarn命令的问题 只要一运行npm/yarn就会提示:不是内部命令,找不到运行路径之类的。 解决办法:首先打开windows环境变量的配置,查看NVM_SYMLINK指向…