『数据结构与算法』散列表(哈希表)

news2024/11/25 2:22:36

1. 什么是散列表

散列表(Hash Table)也叫哈希表,是根据给定关键字(Key)来计算出该关键字在表中存储地址的数据结构。也就是说,散列表建立了关键字与存储地址之间的一种直接映射关系,将关键字映射到表中记录的地址,这加快了查找速度。

使用函数表达式来表示,应为:hash(key)=v,其中key为关键字,hash()为散列函数,v为散列地址。

1.1 相关术语

  • 关键字(Key):关键字是散列表的查找对象,也是散列函数的参数。其可以是记录本身,也可以是记录的唯一索引,这取决于散列表的设计。
  • 散列函数(Hash Function):将关键字映射为散列表中适当存储地址的函数称为散列函数,也叫做哈希函数。
  • 散列地址(Hash Address):将关键字通过散列函数计算出的地址称为散列地址,也叫做散列值、哈希值。
  • 散列表(Hash Table):这种使用散列技术来存储数据记录的表称为散列表。
  • 散列冲突(Hash Collision):当不同的关键字具有相同的散列地址,这种现象称为冲突,也叫做哈希冲突。而这些关键字对该散列函数来说是同义词。
  • 表长:散列表的大小,即能够存储散列地址的数量。用m表示。

1.2 举个例子

一个比较通俗的例子,就是我们手机中的通讯录。通讯录用于存储联系人的姓名及电话号码信息,它是一个按照姓名首字母进行顺序排列的表。比如我们找“嬴政”就可以通过字母“Y”进行快速定位,并找到“嬴政”。

  • 在该例子中,姓名“嬴政”便是关键字
  • 取姓名的首字母的算法便是散列函数。即:hash(嬴政) = Y。
  • 通过
  • 当我们定位到“Y”地址后,不仅有“赢政”,还有“赢异人”,也就是说,通过关键字“赢异人”也能找到“Y”的地址,这种现象便是冲突。即:hash(嬴政) = hash(嬴异人) = Y。
  • 而“赢政”与“赢异人”对该散列函数来说是同义词
  • 这个用于存储联系人的通讯录便是散列表

1.3 小结

本章节介绍了散列表的概念及相关术语,并以一个通讯录的例子来加深对散列表的了解。但还有一些问题等待我们解决,那就是“散列冲突”。我们一方面可以通过精心设计散列函数来尽量减少冲突的次数,另一方面仍需要提供解决冲突的方法。后面章节会详细介绍散列函数的设计和解决冲突的方法。

2. 散列函数的设计

散列函数的设计原则应该遵循计算简单、散列地址分布均匀。下面我们介绍几种常用的散列函数的构造方法。

2.1 直接定址法

取关键字或关键字的某个线性函数值为散列地址。即hash(key) = key 或 hash(key) = a * key + b,其中a、b 为常数。这种散列函数也叫做自身函数。

如下图中,我们要获取某个岗位级别的信息,就可以使用级别减去1来作为地址,即:hash(key) = key - 1

直接定址法的优点是简单、均匀也不会产生冲突。但由于这是一种拿空间换时间的方式,所以适合查找表较小且连续的情况。比如年龄、岗位级别等。

2.2 数字分析法

通过分析一组数据,这些数据中可能出现的关键字都是事先知道的,则可以取关键字的若干数位组成散列地址。取出的这部分数据要在其数位上出现的频率小,这样出现冲突的几率就会很小。

比如手机号码,其前3位为网络识别号,中间4位为地区编码,而后4位才是真正的用户号码。那么在某个公司中,其员工的手机号码前7位出现冲突的概率会很大,而后4位出现冲突的概率会很小,则可以取后四位做为散列地址。

2.3 平方取中法

取关键字平方后的中间几位作为散列地址。该方法是一个产生伪随机数的方法,由冯·诺伊曼在1946年提出。

2.4 折叠法

将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为散列地址。这种方法比较适用于关键字位数很多的情况。

比如关键字为 1234567890,散列表的表长为3位: (1)将关键字分为4组: 123|456|789|0 (2)将它们叠加求和:123 + 456 + 789 + 0 = 1368 (3)舍去进位,得:368 ,即为最终散列地址

2.5 除留余数法

假定散列表的表长为m,取一个不大于m但最接近或等于m的质数p,通过关键字对p取模运算来转换为散列地址。散列函数为:hash(key) = key % p,其中p ≤ m。

不仅可以对关键字直接取模,也可以在折叠法、平方取中法等运算后取模。对p的选择很重要,若选择不好很容易产生冲突。

3. 散列冲突的处理方法

解决冲突的方法有几种,这里我们将讨论其中最简单的两种:开放定址法和链地址法(拉链法)。

3.1 开放定址法

将产生冲突的散列地址做为自变量,通过某种冲突解决函数得到一个新的空闲散列地址。即当产生冲突后,寻找下一个空闲的散列地址。根据寻找的方式不同又分为几种方法,下面来介绍。

3.1.1 线性探测法

当产生冲突时, 顺序探测表中下一地址,直探测到一个空闲(自由)的地址,将记录插入。若一直探测到表尾地址m-1,则下一探测地址为表首地址0。当表满的时候,则探测失败。

比如下图散列表的散列函数使用除留余数法(m=10, p=10)。那么我们顺序插入12、13、24时顺利插入到对应地址中,再插入34、44时会产生冲突,使用线发探测法,继续探测下一地址,将插入到空闲地址上。

线性探测法会造成大量元素在相邻的散列地址上“聚集”起来,使得我们要不断处理冲突,这大大降低查找和存入效率。

3.1.2 平方探测法

平方探测法是消除线性探测法中的聚集问题的一种冲突解决方法。设发生冲突的地址为d,那么使用平方探测法得到的新的地址序列为d ± 1^2、d ± 22、d ± 32...d ± ���2(key ≤ m/2)。

还是刚才那个例子,当插入34时,在下标为4的位置产生冲突,我们先计算一下在该位置探测的序列: 4 + 12 = 5、4 - 12 = 3、4 + 22 = 8、4 - 22 = 0,即:5、2、8、0,所以34应该被放到5的位置。而44应该放到8的位置。

平方探测法的缺点是不能探测到散列表上所有的地址,但至少能探测到一半地址。

3.1.3 伪随机探测法

伪随机探测法是当发生地址冲突时,地址增量为伪随机数序列。

伪随机数是说,如果我们设置随机种子相同,则不断调用随机函数可以生成不会重复的数列,我们在查找时,用同样的随机种子,它每次得到的数列是相同的,相同的地址当然可以得到相同的散列地址。

3.1.4 双散列

双散列顾名思义是使用了两个散列函数,当执行第一个散列函数得到的地址发生冲突时,则执行第二个散列函数来计算该关键字的地址增量

一种常见的算法是:(hash1(key) + i * hash2(key)) mod m,其中i为冲突次数,hash1()为第一个散列函数,hash2()为第二个散列函数,m为散列表大小。当发生冲突后,我们通过重复增加步长i来寻址。

还是以上面的例子为例。第一个散列函数为hash1(key) = key mod 10,第二个散列函数设为hash2(key) = key mod 3。

通过上面公式,可以计算出关键字34的散列地址: (34 mod 10 + 1 * (34 mod 3)) mod 10 = (4 + 1 * 1) mod 10 = 5。而关键字44的散列地址为:(44 mod 10 + 2 * (44 mod 3)) mod 10 = (4 + 2 * 2) mod 10 = 8

3.2 链地址法

链地址法又称拉链法,是利用链表来解决冲突的一种方法,即把具有相同散列地址的关键字记录存入到同一个单链表中,该链表称为同义词链表。每一个散列地址都有一个对应的链表。

还是上面的例子,使用链地址法的存储模型如下图所示。

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

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

相关文章

【电路笔记】-诺顿定理(Norton‘s Theorem)

诺顿定理(Norton’s Theorem) 文章目录 诺顿定理(Nortons Theorem)1、概述与定义2、诺顿模型确定3、一些线性电路的诺顿模型3.1 单电压源3.2 单电流源3.3 多电流/电压源 5、总结 本文是我们上一篇有关戴维南定理的文章的延续。 在…

[文件读取]lanproxy 文件读取 (CVE-2021-3019)

1.1漏洞描述 漏洞编号CVE-2021-3019漏洞类型文件读取漏洞等级⭐漏洞环境VULFOCUS攻击方式 描述: Lanproxy 路径遍历漏洞通过../绕过读取任意文件。该漏洞允许目录遍历读取/../conf/config.properties来获取到内部网连接的凭据。 1.2漏洞等级 高危 1.3影响版本 Lanproxy 1.4漏洞…

酷柚易汛ERP-购货订单操作指南

1、应用场景 先下购货订单,收货入库后生成购货单。 2、主要操作 2.1 新增购货订单 打开【购货】-【购货订单】新增购货订单。(*为必填项,其他为选填) ① 录入供应商:点击供应商字段框的 ,在弹框中选择供…

漏洞-任意账号注册

一漏洞介绍 1.未验证邮箱/手机号 情景:应用为了方便用户记录用户名,使用邮箱和手机号作为用户名(因此很多应用在注册的时候就要求用户填写,多数时候都会给用户发送激活信息,激活后才能登录) 缺陷&#xff…

Linux Mint 21.3 将搭载 Cinnamon 6.0 和实验性 Wayland 支持

导读Wayland 会话可能在 Linux Mint 23 系列中成为默认选项,预计将在 2026 年实现。 Linux Mint 项目今天在他们的每月新闻通讯中 宣布,他们已经开始着手在未来的 Linux Mint 发行版中实施 Wayland 会话,最初将在 Linux Mint 21.3 中提供。 …

3DMAX建模基础教程:常用工具补充

在本篇3DMAX建模基础教程中,我们将为您介绍一些常用的工具及其功能。熟练掌握这些工具将大大提高您的建模效率。 1️⃣ 选择与变换工具 选择工具:帮助您选择对象,可以通过单击对象或按组选择。 变换工具:对选定的对象进行移动、…

【milkv】0、duo编译环境搭建

一、开发资料整理 Docker https://hub.docker.com/repository/docker/dreamcmi/cv1800-docker/general GitHub https://github.com/milkv-duo/duo-buildroot-sdk CV181x/CV180x MMF SDK 开发文档汇总 https://developer.sophgo.com/thread/471.html cv181x芯片使用的交叉…

【计算机网络】UDP协议

UDP的结构 我们学习一个协议最主要的就是理解它的报文格式,对于UDP协议来说 我们看下面的这张图。 16位UDP长度,表示整个数据报(UDP首部UDP数据)的最大长度。UDP报文长度占两个字节,16位表示的数据范围(0-…

新版本Idea设置启动参数

1.进入配置页面 2.点击下图红框的部分,会看到有很多操作可选 3.选择添加VM参数即可 此时就会多出一个可以输入参数的框了,如下:

一个破单机,也要用远程缓存?

大家好,豆小匠终于开始Coding了,这期来聊聊实战相关的杂谈。 正文开始! 作为编程萌新的时候,总想着把程序做复杂,堆技术栈。 但是程序是为场景服务的,比如,我想提高接口的响应速度&#xff0c…

酷柚易汛ERP- 组装单与拆卸单操作

1、功能介绍 组装单用来处理企业组装等加工业务,拆卸单用来处理企业拆卸等加工业务,支持一对多的产品加工业务。 2、主要操作 2.1 新增组装单 打开【仓库】-【组装单】新增组装单。 录入组合件与子件,单据审核后,系统根据存货…

如何在Qemu上跑Milk-duo开发板

前言 (1)PLCT实验室实习生长期招聘:招聘信息链接 (2)学习本文之前,要求先看一下Milk-V Duo快速上手的环境搭建部分,创建好镜像文件。 正文 编译milk-duo qemu (1)下面步…

【STM32/FreeRTOS】SysTick定时器及FreeRTOS系统节拍

目录 一、SysTick定时器 1、SysTick寄存器介绍 (1)控制及状态寄存器 (2)重装载数值寄存器 (3)当前数值寄存器 2、SysTick寄存器配置函数 二、FreeRTOS中的SysTick定时器 1、SysTick配置函数及分析 …

传输层协议-UDP协议

目录 传输层再谈端口号端口号范围划分认识知名端口号 UDP协议UDP协议格式UDP数据封装UDP数据分用 UDP协议的特点面向数据报 UDP缓冲区UDP使用注意事项基于UDP的应用层协议 传输层 实际上我们应用层的数据并不是直接发给网络的,而是需要先将数据发送给传输层&#xf…

网工内推 | 网工校招,金融、软件行业,HCIE认证优先,最高15薪

01 长威信息科技 招聘岗位:网络工程师(24届校招) 职责描述: 1、负责网络类、安全类产品的安装部署、调试和运行维护,以及网络故障分析、定位和处理; 2、负责实施项目各类文档编制工作,包括技术…

MySQL主从复制、读写分离(利用Amoeba和Mycat)、完全同步

目录 一、主从复制 1、概念 1.1主从复制延迟问题: 1.2、MySQL安全和性能配置: 1.3、主从复制的工作过程: 1.4、mysql主从复制注意点: 1.5、MySQL的主从复制的模式: 2、主从复制实验: 二、读写分离&…

springcloud旅游网站源码

开发技术: jdk1.8,mysql5.7,idea,nodejs,vscode springcloud springboot mybatis vue 功能介绍: 用户端: 登录注册 首页显示搜索景区,轮播图,旅游攻略列表 点击攻…

Pytorch数据集读出到transform全过程

最近写代码又遇见了这个问题,又忘记了,于是写一篇博客记录一下。 一般我们使用pytorch获取CIFAR10数据集,一般这样写: mean [0.4914, 0.4822, 0.4465] std [0.2023, 0.1994, 0.2010] transform transforms.Compose([transform…

【动手学大模型】(通俗易懂 快速上手) Task1 大模型简介

1.发展历程 语言建模始于20世纪90年代,采用的是统计学习方法. 2003年,深度学习的思想融入到语言模型中,相较于上个时代,该方法可以更好地捕捉语言中的复杂关系. 2018年左右,Transformer架构的神经网络模型被引入,通过大量文本训练模型,使它对语言有了更深的理解. 最近,模型规模…