Java-数据结构-Map和Set-(二)-哈希表 |ू・ω・` )

文本目录:

❄️一、哈希表:

   ☑ 1、概念:

       ☑ 2、冲突-概念:

       ☑ 3、冲突-避免:

         ☞ 1)、避免冲突-哈希函数的设计:

          ☞ 2)、避免冲突-负载因子调节(重点):

        ☑ 4、冲突-解决:

            ➷ 1)、解决冲突-闭散列:

             ➷ 2)、解决冲突-开散列 / 哈希桶(重点):

         ☑ 5、哈希表的实现:

         ▶ 1)、put(int key,int value)方法:

          ▶ 2)、getVal(int key)方法:

         ☑ 6、性能分析:

        ☑ 7、和Java类集的关系:

❄️总结:


❄️一、哈希表:

   ☑ 1、概念:

      顺序结构以及平衡树中,元素存储码与其存储位置之间没有对应的关系,因此在我们查找一个元素的时候呢,必需要根据关键码的多次比较。

      顺序查找的时间复杂度为O(N),在平衡树中查找的话就是树的高度为O(logN)。搜索效率取决于搜索过程中元素的比较次数。

       我们理想的搜索方法是:可以不经过任何的比较,一次直接从表中搜索到我们要查找的元素。如果构造一种数据结构,通过某种函数使元素的存储位置与它关键码之间能够建立一一映射关系,那么在查找中可以通过该函数快速查找到该函数。

实现:当向该结构中:

1、插入元素的时候:

         可以根据待插入的关键码,以此函数计算出该元素的存储位置并按照此位置进行存放。

2、查找元素的时候:

         对元素的关键码进行同样的函数计算,把得到的函数值。

     该方法呢就是 哈希(散列)方法,哈希方法的使用的转换函数称为 哈希(散列)函数,其构造出来的结构称为 哈希(散列)表。

我们来举一个例子来看看: 数据为{1,7,6,4,9,5};

      哈希函数设置为:hash = key % capacity,其中capacity 是总空间的大小。

    使用这个方法呢,进行搜索的时候呢,不需要进行多次关键字的比较,这直接可以找到,所以搜索效率比较高。 但是还是有些问题的,我们如果插入 11 的话呢,11 % 10 = 1,但是 1 下标的位置呢,已经有元素了,这样呢就会产生所谓的 —— 冲突,那么我们要如何解决并且降低这种冲突呢?我们往后来看:


       ☑ 2、冲突-概念:

       对于两个数据元素的关键字有 ki 和 kj (i != j),有 ki != kj,但是呢 hash(ki) == hash(kj),就是相当于 1 和 11 但是呢 hash(1) == hash(11),在 capacity 为10 的情况下。

       就是不同的 关键字 通过 哈希函数 计算相同的 哈希地址,该种现象称为 哈希冲突 或者 哈希碰撞。

       我们呢把不同的关键码而具有相同的 哈希地址 的数据元素称之为 “同义词”


       ☑ 3、冲突-避免:

      我们呢要知道一个点,由于我们的哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致我们的 冲突是必然要发生的,但是我们能做的就是尽量 降低冲突率。


         ☞ 1)、避免冲突-哈希函数的设计:

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

设计规则:

 1、哈希函数的定义域必须包括存储的关键字码,而如果哈希表有 m 个地址,其值域必须在0-m-1之间。

2、哈希函数设计出来的地址能够均匀的分布在整个空间中。

3、哈希函数比较简单。

常见的哈希函数:

     1、直接定制法(常用):

     取关键字的某个线性函数为散列地址:Hash(Key) = A * Key + B,优点:简单、均匀。

缺点:需要实现知道关键字的分布情况。使用场景:适合查找比较小并且连续的情况。


     2、除留余数法(常用): 

     设散列表中允许的地址数为 m,取一个不大于 m,但接近或者等于 m 的质数 p 作为除数。

按照 哈希函数:Hash(Key) = key % p(p <= m),将关键码转换成 哈希地址。


      3、平方取中法(了解):

     这个用于 不知道关键字的分布,而位数又不是很大的情况下。

比如:存放1234这个关键字,其平方为 1522756 ,抽取中间的三位数 227 作为哈希地址。


      4、折叠法(了解):

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

      折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况


      5、随机取数法(了解):

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


      6、数学分析法(了解):

     数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀的情况。


我们要注意:我们 哈希函数 设计的越好,其产生的 哈希冲突呢就越小,但是呢还是无法解决冲突


          ☞ 2)、避免冲突-负载因子调节(重点):

     负载因子就是: 填入表中的元素个数 / 散列表的长度 = 负载因子

     我们的 负载因子 和 填入表中的元素个数 是成正比的,所以当我们的 负载因子越大,可以填入的元素个数就越多,冲突发生的概率就越小,所以我们可以改变 负载因子来调节冲突。

     所以我们就可以 提高散列表的长度 来使 复杂因子 降低,就可以使填入的元素个数增加了。

我们的 负载因子 要控制在 0.7~0.8 之间。

我们了解了如何才能尽量避免冲突,接下来我们来看看如何才能解决冲突。


        ☑ 4、冲突-解决:

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


            ➷ 1)、解决冲突-闭散列:

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

那么我们如何才能找到这个下一个位置呢? 对于这个我们呢有两种方法可以找到:

1、线性探测:

    就比如我们的上面的那个例子,如果我们想要插入 11 的话呢,就是根据 哈希函数 计算出我们的 哈希地址 之后呢我们查看 这个地址是否有元素,如果有就放到其下一个位置,就可以了。

我们来看看这个例子: 

缺点呢就是:这个方法呢会使冲突的元素集中到了一起。

我们的目的是把其芬分布的均匀一点,这个就有很大的缺陷。


2、二次探测:

       我们的二次探测的方法呢,有一个公式可以找到要插入的位置:Hi = (H0 + i^2) % m 或者是 Hi = (H0 - i^2) % m ,其中呢 i = 1,2,3,4,5......... ,H0 是根据 哈希函数 计算出的 key 的哈希地址,m 呢是我们表的长度

       我们一上面的例子为例,来看看如何实现的:

      但是呢对于 闭散列 呢有一个很大的缺陷,就是 空间的利用率 很低 ,这就是一个缺陷了,所以呢我们就有了两一种方法了——开散列/哈希桶。


             ➷ 2)、解决冲突-开散列 / 哈希桶(重点):

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

 其实就是 数组+链表的一个组合方式。我们把每一个数据设为一个节点,放到我们的地址后面:

开散列 可以看成是把每一个大集合中的搜索问题转化为 小集合中的搜索问题。 


         ☑ 5、哈希表的实现:

     我们的 HashSet 的底层呢是 HashMap ,和我们上次介绍的 TreeSet 和 TreeMap 是差不多的,所以呢,这里我们自实现的 哈希表 呢就是 key-value 的,并且我们的 哈希表 是一个节点数组,所以我们来看看 哈希表的 的节点的代码,并且还要有我们的一个 负载因子

public class HashBuck {//节点static class Node {public int key;public int value;public Node next;public Node(int key,int value) {this.key = key;this.value = value;}}//哈希桶是一个节点数组,所以我们使用节点来创建一个数组public Node[] array = new Node[10];//有效的数据长度public int usedSize;//负载因子public static final double DEFAULT_LOAD_FACTOR = 0.75f;
}

         ▶ 1)、put(int key,int value)方法:

1、先使用 哈希函数 计算出 key 的 哈希地址

2、我们检查一下我们的这个 哈希地址 下的数组中时候有 key 这个值。如果有就要 更新 value。

3、如果没有和 key 相同的值,我们使用 头插法 进行把 key 值插入进去。(jdk1.8是尾插法)

4、usedSize++

5、我们的usedSize++,之后呢要检查我们的 负载因子 是否比我们的定义的大,如果大,我们就需要扩容。

 对于这里的扩容方法呢,不是简单的直接把原数组扩大 2 倍就可以的,如果这样写就是不对的。

扩容的注意:

        注意:就比如上面的例子,我们的扩大 2 倍之后呢,我们的长度是不是变成了 20,而我们的上面的 11 这个数据是不是就不是放到 1 这个地址的位置了,应该放到 11 这个位置了,所以我们的扩容方法呢,要遍历一遍我们的所有数据,对其进行重新计算 哈希地址 来放入我们的数据

       1、我们要把 cur.next 的位置记录下来,因为当我们把 11 放到 新的位置之后呢,我们的cur这个的 next 节点就找不到了,所以我们需要记录下来。

来看看这个扩容的代码:

private void resize() {Node[] newarray = new Node[2 * array.length];for (int i = 0; i < array.length; i++) {Node cur = array[i];while (cur != null) {int newindex = cur.key % newarray.length;Node curN = cur.next;cur.next = newarray[newindex];newarray[newindex] = cur;cur = curN;}}array = newarray;}

这样之后呢,我们来看看对于 put 这个方法是如何编写的:

public void put(int key,int value) {//计算地址int index = key % array.length;//检查是否出现相同的 key 值Node cur = array[index];while (cur != null) {if (cur.key == key) {cur.value = value;return;}cur = cur.next;}//如果没有key值的话,使用头插法把新的节点插入到 哈希表中Node newNode = new Node(key,value);newNode.next = array[index];array[index] = newNode;//插入之后有效长度增加usedSize++;//判断负载因子if (loadFactor() >= DEFAULT_LOAD_FACTOR) {//扩容resize();}}//计算负载因子private double loadFactor() {return usedSize * 1.0 / array.length;}

          ▶ 2)、getVal(int key)方法:

     对于这个呢就很简单了,我们同样先计算出我们的 哈希地址 之后呢,根据这个地址再来寻找我们传入的 key 所对应的 value 值。

      我们呢来直接看代码如何编写的:

public int getVal(int key) {int index = key % array.length;Node cur = array[index];while(cur != null) {if (cur.key == key) {return cur.value;}cur = cur.next;}return -1;}

到这里我们的 哈希表 就结束了,对于 哈希表 就只实现这两个方法就可以了。


         ☑ 6、性能分析:

     我们呢一般把每个桶中的链表的长度是一个常数,所以呢,通常我们的 哈希表的 插入/删除/查时间复杂度为 O(1)。  


        ☑ 7、和Java类集的关系:

1. HashMap 和 HashSet 即 java 中利用哈希表实现的 Map 和 Set
2. java 中使用的是哈希桶方式解决冲突的
3. java 会在冲突链表长度大于一定阈值后,将链表转变为搜索树(红黑树)

   这个阈值是:数组长度大于 64 && 链表的长度超过了 8 
4. java 中计算哈希值实际上是调用的类的 hashCode 方法,进行 key 的相等性比较是调用 key 的 equals 方法。所以如果要用自定义类作为 HashMap 的 key 或者 HashSet 的值,必须覆写 hashCode 和 equals 方法,而且要做到 equals 相等的对象,hashCode 一定是一致的。


❄️总结:

    OK,我们这次对于 哈希表 的介绍和简单的实现原理呢到这里也就结束了,我们接下来呢,来解决几道关于 哈希表 相关的题吧!!!让我们尽情期待吧~拜拜~~~

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

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

相关文章

那年我双手插兜,使用IPv6+DDNS动态域名解析访问NAS

估计有很多科技宅和我一样&#xff0c;会买一个NAS存储或者自己折腾刷一下黑群晖玩玩&#xff0c;由于运营商不给分配固定的公网IP&#xff0c;就导致我在外出的时候无法访问家里的NAS&#xff0c;于是远程访问常常受到IP地址频繁变动的困扰。为了解决这一问题&#xff0c;结合…

element 输入框文字+对应签进行长度 和 的判断

输入文字长度 指定标签的长度 &#xff08;判断长度并提示&#xff09; <div style"position: relative;" classchangyongyu><el-input type"textarea" :autosize"{ minRows: 8, maxRows: 8 }" style"margin-bottom:10px;"…

【React】useEffect

1. 基本介绍 概念 语法 副作用函数依赖项数组&#xff08;空数组时&#xff0c;只会在组件渲染完毕后执行一次副作用函数&#xff09; 2. 使用 import { useEffect, useState } from reactfunction App() {const [value, setValue] useState(0)useEffect(() > {console…

如何使用ssm实现小区物业管理系统

TOC ssm733小区物业管理系统jsp 第一章 绪论 1.1 研究背景 在现在社会&#xff0c;对于信息处理方面&#xff0c;是有很高的要求的&#xff0c;因为信息的产生是无时无刻的&#xff0c;并且信息产生的数量是呈几何形式的增加&#xff0c;而增加的信息如何存储以及短时间分析…

基于微信小程序的美食外卖管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

SPI驱动学习七(SPI_Slave_Mode驱动程序框架)

目录 一、SPI_Slave_Mode驱动程序框架1. Master和Slave模式差别1.1 主设备 (Master)1.2 从设备 (Slave)1.3 示例 2. SPI传输概述2.1 数据组织方式2.2 SPI控制器数据结构 3. SPI Slave Mode数据传输过程4. 如何编写程序4.1 设备树4.2 内核相关4.3 简单的示例代码4.3.1 master和s…

Anaconda虚拟环境默认路径在C盘怎么更改

笔者已经新建好了虚拟环境并且安装了对应库&#xff0c;输入conda env list查询发现虚拟环境竟然安装到了C盘(&#xff61;•́︿•̀&#xff61;)&#xff0c;为避免下一次创建虚拟环境出错&#xff0c;笔者现在修改默认路径置D盘&#xff08;软件安装盘&#xff09; 参考两…

【Oauth2整合gateway网关实现微服务单点登录】

文章目录 一.什么是单点登录&#xff1f;二.Oauth2整合网关实现微服务单点登录三.时序图四.代码实现思路1.基于OAuth2独立一个认证中心服务出来2.网关微服务3产品微服务4.订单微服务5.开始测试单点登录 一.什么是单点登录&#xff1f; 单点登录&#xff08;Single Sign On&…

【YOLO目标检测车牌数据集】共10000张、已标注txt格式、有训练好的yolov5的模型

目录 说明图片示例 说明 数据集格式&#xff1a;YOLO格式 图片数量&#xff1a;10000&#xff08;2000张绿牌、8000张蓝牌&#xff09; 标注数量(txt文件个数)&#xff1a;10000 标注类别数&#xff1a;1 标注类别名称&#xff1a;licence 数据集下载&#xff1a;车牌数据…

小程序-生命周期与WXS脚本

生命周期 什么是生命周期 生命周期&#xff08;Life Cycle&#xff09;是指一个对象从创建 -> 运行 -> 销毁的整个阶段&#xff0c;强调的是一个时间段。 我们可以把每个小程序运行的过程&#xff0c;也概括为生命周期&#xff1a; 小程序的启动&#xff0c;表示生命…

ant design vue中带勾选表格报Tree missing follow keys: ‘undefined‘解决方法

1、这里一定要给columns和data-source设置key即可。 <div><a-table:row-selection"rowSelection":dataSource"tableList":columns"columns":scroll"{ x: 100% }":pagination"false":loading"loading"&g…

C++ -- 异常

C中的异常是用于处理程序执行过程中出现的错误情况。通过异常处理&#xff0c;程序可以在遇到错误时优雅地处理这些问题&#xff0c;而不是直接崩溃。 C语言处理错误的方式 C语言传统的处理错误的方式主要有两种&#xff1a; 终止程序&#xff1a;使用如assert这样的宏来检查…

隐藏SpringBoot自动生成的文件

第一种方法——删除 第二种方法——Settings——Editor——fail types

idea 创建多模块项目

一、新建项目&#xff0c;创建父工程 新建项目&#xff0c;选择 spring initializr 填写相关信息后提交 删除不相关的目录&#xff0c;如下 修改打包方式为 pom&#xff0c;在 pom.xml 文件中新增一行&#xff0c;如下 二、创建子模块 新增子模块 三、修改 pom 文件 修…

怎样用python+sqlalchemy获得mssql视图对应物理表关系(二)

话不多说 目标:为了实现低代码数据视图对接,有必要得到视图所对应物理表及字段名称,字段类型等 1)约束:视图中用到的物理表不能起别名,所以修改上一篇中存储过程建立语句 USE [agui_conn] GO /****** Object: StoredProcedure [dbo].[sp_GetOrdersByTimestamp] Script D…

04 面部表情识别:Pytorch实现表情识别-表情数据集训练代码

总目录&#xff1a;人脸检测与表情分类 https://blog.csdn.net/whiffeyf/category_12793480.html 目录 0 相关资料1 面部表情识数据集2 模型下载3 训练 0 相关资料 面部表情识别2&#xff1a;Pytorch实现表情识别(含表情识别数据集和训练代码)&#xff1a;https://blog.csdn.n…

vscode配置Eslint后保存出现大量波浪线

解决问题&#xff1a;配置代码格式化 快捷键打开设置&#xff1a;ctrlshiftP 输入&#xff1a; format code 选择&#xff1a;

UE5 项目缓存文件删除、版本控制说明(工程目录结构)

文章目录 前言一、项目文件示例二、缓存文件删除、版本控制说明前言 我们在拷贝项目或者使用 Git 进行版本控制,如果不对文件选择性的控制,大量缓存文件会导致传输速度变慢;或者我们的项目报错了,想要删除缓存文件又不知如何下手,哪些是可删除的,哪些又是不可删除的,本…

SLA(立体光固化成型技术)

01 SLA 3D打印技术简介 SLA工艺简介 SLA是"Stereo lithography Appearance"的缩写&#xff0c;即立体光固化成型法。用特定波长与强度的激光聚焦到光固化材料表面&#xff0c;使之由点到线&#xff0c;由线到面顺序凝固&#xff0c;完成一个层面的绘图作业&#x…

OpenCV图像文件读写(4)解码图像数据函数imdecode()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 从内存缓冲区读取图像。 imdecode 函数从指定的内存缓冲区读取图像。如果缓冲区太短或包含无效数据&#xff0c;函数将返回一个空矩阵 (Mat::dat…