Java学数据结构(4)——散列表Hash table 散列函数 哈希冲突

news2024/11/23 10:35:12

目录

  • 引出
  • 散列表Hash table
  • 关键字Key和散列函数(hash function)
  • 散列函数
  • 解决collision哈希冲突(碰撞)
    • 分离链接法(separate chaining)
    • 探测散列表(probing hash table)
    • 双散列(double hashing)
  • Java标准库中的散列表
  • 总结

引出


1.散列表,key,散列函数;
2.哈希冲突的解决;
3.string中的hashCode;

在这里插入图片描述

散列表Hash table

查找树ADT,它允许对元素的集合进行各种操作。本章讨论散列表(hash table)ADT,不过它只支持二叉查找树所允许的一部分操作。散列表的实现常常叫作散列(hashing)。散列是一种用于以常数平均时间执行插入、删除和查找的技术。但是,那些需要元素间任何排序信息的树操作将不会得到有效的支持。因此,诸如findMin、findMax以及以线性时间将排过序的整个表进行打印的操作都是散列所不支持的。

  • 看到实现散列表的几种方法。
  • 解析地比较这些方法。
  • 介绍散列的多种应用。
  • 将散列表和二叉查找树进行比较。

关键字Key和散列函数(hash function)

理想的散列表数据结构只不过是一个包含一些项(itm)的具有固定大小的数组。通常查找是对项的某个部分(即数据域)进行的。这部分就叫作关键字(key)

例如,项可以由一个字符串(它可以作为关键字)和其他一些数据域组成(例如,姓名是大型雇员结构的一部分)。我们把表的大小记作TableSize,并将其理解为散列数据结构的一部分,而不仅仅是浮动于全局的某个变量。通常的习惯是让表从0到TableSize-1变化;稍后我们就会明白为什么要这样做。

每个关键字被映射到从0到TableSize-1这个范围中的某个数,并且被放到适当的单元中。这个映射就叫作散列函数(hash function),理想情况下它应该计算起来简单,并且应该保证任何两个不同的关键字映射到不同的单元。不过,这是不可能的,因为单元的数目是有限的,而关键字实际上是用不完的。因此,我们寻找一个散列函数,该函数要在单元之间均匀地分配关键字。

图5-1是完美情况的一个典型。在这个例子中,john散列到3,phil散列到4,dave散列到6,mary散列到7。

在这里插入图片描述

这就是散列的基本想法。剩下的问题就是要选择一个函数,决定当两个关键字散列到同一个值的时候(这叫作冲突(collision))应该做什么以及如何确定散列表的大小。

散列函数

在这里插入图片描述

这个散列函数利用到事实:允许溢出。这可能会引进负的数,因此在末尾有附加的测试。图5-4所描述的散列函数就表的分布而言未必是最好的,但确实具有极其简单的优点而且速度也很快。如果关键字特别长,那么该散列函数计算起来将会花费过多的时间。在这种情况下通常的经验是不使用所有的字符。此时关键字的长度和性质将影响选择。例如,关键字可能是完整的街道地址,散列函数可以包括街道地址的几个字符,也许还有城市名和邮政编码的几个字符。有些程序设计人员通过只使用奇数位置上的字符来实现他们的散列函数,这里有这么一层想法:用计算散列函数节省下的时间来补偿由此产生的对均匀地分布的函数的轻微干扰。

在这里插入图片描述

解决collision哈希冲突(碰撞)

剩下的主要编程细节是解决冲突的消除问题。如果当一个元素被插入时与一个已经插入的元素散列到相同的值,那么就产生一个冲突,这个冲突需要消除。解决这种冲突的方法有几种,我们将讨论其中最简单的两种:分离链接法和开放定址法。

分离链接法(separate chaining)

分离链接法(separate chaining)

解决冲突的第一种方法通常叫作分离链接法(separate chaining),其做法是将散列到同一个值的所有元素保留到一个表中。我们可以使用标准库表的实现方法。如果空间很紧,则更可取的方法是避免使用它们(因为这些表是双向链接的并且浪费空间)。本节我们假设关键字是前10个完全平方数并设散列函数就是hash(x)=xmod10(表的大小不是素数,用在这里是为了简单)。

在这里插入图片描述

探测散列表(probing hash table)

探测散列表(probing hash table)

在这里插入图片描述

分离链接散列算法的缺点是使用一些链表。由于给新单元分配地址需要时间(特别是在其他语言中),因此这就导致算法的速度有些减慢,同时算法实际上还要求对第二种数据结构的实现。另有一种不用链表解决冲突的方法是尝试另外一些单元,直到找出空的单元为止。更常见的是,单元h(x),h,(x),h2(x),…相继被试选,其中h:(x)=(hash(x)+f(i)mod
TableSize,且f(0)=0。函数f是冲突解决方法。因为所有的数据都要置入表内,所以这种解决方案所需要的表要比分离链接散列的表大。一般说来,对于不使用分离链接的散列表来说,其装填因子应该低于入=0.5。我们把这样的表叫作探测散列表(probing hash table)。现在我们就来考察三种通常的冲突解决方案。

在这里插入图片描述

第一个冲突在插人关键字49时产生;它被放入下一个空闲地址,即地址0,该地址是开放的。关键字58先与18冲突,再与89冲突,然后又和49冲突,试选三次之后才找到一个空单元。对69的冲突用类似的方法处理。只要表足够大,总能够找到一个自由单元,但是如此花费的时间是相当多的。更糟的是,即使表相对较空,这样占据的单元也会开始形成一些区块,其结果称为一次聚集(primary clustering),就是说,散列到区块中的任何关键字都需要多次试选单元才能够解决冲突,然后该关键字被添加到相应的区块中。

平方探针

平方探测是消除线性探测中一次聚集问题的冲突解决方法。平方探测就是冲突函数为二次的探测方法。流行的选择是f(i)=i**2。图5-13显示与前面线性探测例子相同的输人使用该冲突函数所得到的散列表。

在这里插入图片描述

当49与89冲突时,其下一个位置为下一个单元,该单元是空的,因此49就被放在那里。此后,58在位置8处产生冲突,其后相邻的单元经探测得知发生了另外的冲突。下一个探测的单元在距位置8为2=4远处,这个单元是个空单元。因此,关键字58就放在单元2处。对于关键字69,处理的过程也一样。

虽然平方探测排除了一次聚集,但是散列到同一位置上的那些元素将探测相同的备选单元。这叫作二次聚集(secondary clustering)。二次聚集是理论上的一个小缺憾。模拟结果指出,对每次查找,它一般要引起另外的少于一半的探测。下面的技术将会排除这个缺撼,不过这要付出计算一个附加的散列函数的代价。

双散列(double hashing)

在这里插入图片描述

我们将要考察的最后一个冲突解决方法是双散列(double hashing)。对于双散列,一种流行的选择是f(i)=i·hash2(x)。这个公式是说,我们将第二个散列函数应用到x并在距离hash2(x),2hash(x),…等处探测。hash2(x)选择得不好将会是灾难性的。例如,若把99插入到前面例子中的输入中去,则通常的选择hash2(x)=xmod9将不起作用。因此,函数一定不要算得0值。另外,保证所有的单元都能被探测到也是很重要的(但在下面的例子中这是不可能的,因为表的大小不是素数)。诸如hash2(x)=R-(x mod R)这样的函数将起到良好的作用,其中R为小于TableSize的素数。如果我们选择R=7,则图5-18显示插入与前面相同的一些关键字的结果。

第一个冲突发生在49被插入的时候。hash2(49)=7-0=7,故49被插入到位置6。hash2(58)7-2=5,于是58被插人到位置3。最后,69产生冲突,从而被插入到距离为hash2(69)=7-6=1远的地方。如果我们试图将60插入到位置0处,那么就会产生一个冲突。由于hash2(60)=74=3,因此我们尝试位置3、6、9,然后是2,直到找出一个空的单元。一般是有可能发现某个坏

在这里插入图片描述

Java标准库中的散列表

标准库包括Set和Map的散列表的实现,即HashSet类和HashMap类。HashSet中的项(或HashSet中的关键字)必须提供equals方法和hashCode方法,如较早我们在节5.3所描述的那样。HashSet和HashMap通常是用分离链接散列实现的。

如果这些表项是否可以依有序方式查看这一点并不重要,那么这些类可以使用。例如,在4.8节的单词变换例子中,存在三种映射:

1.其中关键字为单词长度(word length),而关键字的值是长为该单词长度的所有单词的集合。

2.关键字是一个代表(representative),而关键字的值是具有该代表的所有单词的集合。

3.关键字是一个单词(wod),而关键字的值是与该单词只有一个字母不同的所有单词的集合。

因为单词长度被处理的顺序并不重要,所以第1个映射可以是HashMap。而由于第2个映射建立以后甚至不需要代表,因此第2个映射也可以是HashMap。第3个映射还可以是HashMap,除非我们想要printHighChangeables依字母顺序列出单词的子集(这些单词可以被变换成许多其他单词)。

HashMap的性能常常优于TreeMap的性能,不过不按这两种方式编写代码很难有把握肯定。因此,在HashMap或TreeMap可以接受的情况下,更可取的方法是:使用接口类型Map进行变量的声明,然后,将TreeMap的实例变成HashMap的实例并进行计时测试。

在Java中,能够被合理地插人到一个HashSet中去或是所谓关键字被插入到HashMap中去的那些库类型已经被定义了equals和hashCode方法。

特别是String类中有一个hashCode方法。

在这里插入图片描述


总结

1.散列表,key,散列函数;
2.哈希冲突的解决;
3.string中的hashCode;

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

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

相关文章

SQLite数据库C_C++接口(保姆级API应用 1.4W字)(全网最详细介绍,学完必掌握)

目录 sqlite3的C/C API应用 前言 SQLite3库安装 API函数 打开、关闭、错误处理 打开 返回值 关闭 错误调试 实际应用 执行SQL(DDL、DML) API介绍 实际应用 回调函数查询 API介绍 实际应用 全缓冲查询 API介绍 实际应用 字节缓冲查询…

【Linux】【驱动】自动创建设备节点

【Linux】【驱动】自动创建设备节点 续驱动代码操作指令linux端从机端 续 这里展示了如何自动的方式去创建一个字符类的节点 下面就是需要调用到的程序 函数 void cdev_init(struct cdev *, const struct file_operations *);第一个参数 要初始化的 cdev 第二个参数 文件操作…

【微服务部署】01-Kubernetes部署流程

文章目录 部署1. Kubernetes是什么2. Kubernetes的优势3. 环境搭建4. 应用部署 部署 1. Kubernetes是什么 Kubernetes是一个用于自动部署、扩展和管理容器化应用程序的开源系统 2. Kubernetes的优势 自动化容器部署资源管理与容器调度服务注册发现与负载均衡内置配置与秘钥…

STM32 CUBEMX CAN通信数据发送失败原因分析

CAN通信是一种数据通信协议,用于在不同设备之间进行通信。它是一种高效的、实时的、可靠的、多主机的、串行通信系统,通常用于汽车电子、工业自动化等领域。CAN通信协议是由德国BOSCH公司于1986年引入,并在欧洲和日本广泛使用。CAN通信具有独…

uniapp热更新

首先热更新需要wgt包; 其次先了解这两个组件 下载的方法 安装的组件 场景: 当你项目的js文件或者页面文件或者静态图片文件css文件更新的时候可以走热更新; 而当你安装新的组件插件或者开启新的权限等功能的时候就无法通过热更新进行更新了…

自然语言处理(三):基于跳元模型的word2vec实现

跳元模型 回顾一下第一节讲过的跳元模型 跳元模型(Skip-gram Model)是一种用于学习词向量的模型,属于Word2Vec算法中的一种。它的目标是通过给定一个中心词语来预测其周围的上下文词语。 这节我们以跳元模型为例,讲解word2vec的…

C语言(第三十四天)

1. 二进制 其实我们经常能听到2进制、8进制、10进制、16进制这样的讲法,那是什么意思呢?其实2进制、8进制、10进制、16进制是数值的不同表示形式而已。 比如:数值15的各种进制的表示形式: 15的2进制:1111 15的8进制:1…

开源项目的文档:为什么它如此重要?

🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…

2023.8.28日论文阅读

文章目录 NestFuse: An Infrared and Visible Image Fusion Architecture based on Nest Connection and Spatial/Channel Attention Models(2020的论文)本文方法 LRRNet: A Novel Representation Learning Guided Fusion Network for Infrared and Visible Images本文方法学习…

JVM 内存大对象监控和优化实践

作者:vivo 互联网服务器团队 - Liu Zhen、Ye Wenhao 服务器内存问题是影响应用程序性能和稳定性的重要因素之一,需要及时排查和优化。本文介绍了某核心服务内存问题排查与解决过程。首先在JVM与大对象优化上进行了有效的实践,其次在故障转移与…

jmeter传参base64卡顿如何解决

部分接口需要传图片base64格式参数,但是输入转为base64格式的图片参数,jmeter直接卡死,甚至电脑也卡死,此时,只需要去掉文件头描述:data:image/jpeg;base64, 即可

Element Plus 日期选择器的使用和属性

element plus 日期选择器如果如果没有进行处理 他会返回原有的属性值data格式 如果想要获取选中的日期时间就需要通过以下的代码来实现选中的值 format"YYYY/MM/DD" value-format"YYYY-MM-DD" <el-date-pickerv-model"formInline.date" type&…

《Flink学习笔记》——第五章 DataStream API

一个Flink程序&#xff0c;其实就是对DataStream的各种转换&#xff0c;代码基本可以由以下几部分构成&#xff1a; 获取执行环境读取数据源定义对DataStream的转换操作输出触发程序执行 获取执行环境和触发程序执行都属于对执行环境的操作&#xff0c;那么其构成可以用下图表示…

AD画PCB时设置的中文丝印乱码

AD画PCB时设置的中文丝印乱码怎么解决&#xff1f; 画好PCB后通常会加一些没有电气属性的丝印或者板号&#xff0c;有时用英文有时用中文&#xff0c;通常用英文或者数字都能直接显示&#xff0c;但是用中文时显示的就是乱码&#xff1b;因为字符串放置好后默认的字体是“比划…

使用el-tag和el-select组件实现标签的增删

第一步 点击按钮&#xff0c;弹出博客所拥有的标签列表的气泡 效果图 第二步 选择标签列表中的标签进行添加 效果图 第三步 实现标签的移除 效果图 页面编写 <!-- 标签模块 start--><el-popover trigger"click" placement"top" :width&quo…

设计模式—策略模式

目录 一、定义 二、特点 三、优点 四、缺点 五、实例 六.涉及到的知识点 1、一个类里面有哪些东西&#xff1f; 2、类和实例 什么是类&#xff1f; 什么是实例&#xff1f; 什么是实例化&#xff1f; 3、字段和属性 什么是字段&#xff1f; 属性是什么&#xff1…

自实现getprocaddress(名称查找或者序号查找)

通过名称去找 // MyGETPRCOADDRESS.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 //#include <iostream> #include<Windows.h>/*WINBASEAPI //导出不需要使用&#xff0c;那么我们注释掉*/ FARPROC WINAPI MyGetProcAddress(_In_ HMO…

SSM学习内容总结(Spring+SpringMVC+MyBatis)

目录 1、什么是SSM2、学习内容汇总2.1、Spring2.2、SpringMVC2.3、MyBatis2.4、SSM整合 &#x1f343;作者介绍&#xff1a;准大三本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 &#x1f341;作者主页&#xff1a;逐梦苍穹 &#x1f440;近期目标…

SpringBoot初级开发--加入Log4j进行日志管理打印(6)

日志记录在整个java工程开发中占着很重要的比重&#xff0c;因为很多问题的排查需要通过日志分析才能确认。在SpringBoot中我用得最多的就是log4j这个日志框架。接下来我们具体配置log4j. log4j定义了8个级别的log&#xff08;除去OFF和ALL&#xff0c;可以说分为6个级别&#…

Jackpack - Hilt

一、概念 类中使用的某个对象不是在这个类中实例化的&#xff08;如Activity无法手动实例化使用&#xff09;&#xff0c;而是通过外部注入&#xff08;从外部传入对象后使用&#xff09;&#xff0c;这种实现方式就称为依赖注入 Dependency Injection&#xff08;简称DI&#…