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.xdnf.cn/news/1550472.html

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

相关文章

宠物空气净化器该怎么选?希喂、美的、有哈这三款有推荐的吗?

终于要到国庆了&#xff0c;这可是打工人除春节外最长的假期&#xff01;在外上班后&#xff0c;回家的次数越来越少了&#xff0c;这次国庆肯定要回去陪陪父母。这票是真难买啊&#xff0c;候补了我一个多星期才买到。本来以为最困难的问题已经解决了&#xff0c;又想到我家猫…

有通话质量更好的蓝牙耳机推荐吗?高品质的平价开放式耳机推荐

个人认为开放式耳机在通话方面还是表现不错的&#xff0c;主要有以下几个原因&#xff1a; 首先&#xff0c;在麦克风设计与配置方面&#xff1a; 拥有高品质麦克风硬件。优质的开放式耳机往往会配备高性能的麦克风&#xff0c;这些麦克风灵敏度较高&#xff0c;能够精准地捕捉…

情感短视频素材下载推荐

在制作热门的情感短视频时&#xff0c;优质的素材是不可或缺的。作为一名资深视频剪辑师&#xff0c;今天我将为你推荐几个可以下载高清无水印情感视频素材的网站&#xff0c;助你轻松找到创作灵感。 蛙学网 蛙学网是国内领先的视频素材平台&#xff0c;专注于情感和治愈类视频…

TypeScript是基于LLM上层研发的受益者

TypeScript优在哪里 TypeScript是一种由微软开发的开源编程语言&#xff0c;它是JavaScript的一个超集&#xff0c;添加了类型系统和一些其他特性。TypeScript的优势在于&#xff1a; 静态类型检查&#xff1a;TypeScript的最大卖点是它的静态类型系统。这允许开发者在编写代码…

11.C++程序中的常用函数

我们将程序中反复执行的代码封装到一个代码块中&#xff0c;这个代码块就被称为函数&#xff0c;它类似于数学中的函数&#xff0c;在C程序中&#xff0c;有许多由编译器定义好的函数&#xff0c;供大家使用。下面就简单说一下&#xff0c;C中常用的函数。 1.sizeof sizeof函…

AI预测福彩3D采取888=3策略+和值012路或胆码测试9月28日新模型预测第101弹

经过100多期的测试&#xff0c;当然有很多彩友也一直在观察我每天发的预测结果&#xff0c;得到了一个非常有价值的信息&#xff0c;那就是9码定位的命中率非常高&#xff0c;100多期一共只错了12次&#xff0c;这给喜欢打私房菜的朋友提供了极高价值的预测结果~当然了&#xf…

Learn OpenGL In Qt之炫酷进度条

竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生~ 公众号&#xff1a; C学习与探索 | 个人主页&#xff1a; rainInSunny | 个人专栏&#xff1a; Learn OpenGL In Qt 文章目录 设计实现目录结构需要哪些类接口设计关键函数 实现效果Shader解析GLSL基本函数clampsmoothstep 实现分析效…

2.点位管理——帝可得后台管理系统

目录 前言点位管理菜单模块1.需求说明2.库表设计3.生成基础代码0 .使用若依代码生成器最终目标1.创建点位管理2.添加数据字典3.配置代码生成信息4.下载代码并导入项目 4.优化菜单——点位管理1.优化区域管理2.增加点位数3. 合作商 前言 提示&#xff1a;本篇介绍点位管理模块&…

[论文精读]AI-Guardian: Defeating Adversarial Attacks using Backdoors

会议名称&#xff1a;2023 IEEE Symposium on Security and Privacy (SP) 发布链接&#xff1a;AI-Guardian: Defeating Adversarial Attacks using Backdoors | IEEE Conference Publication | IEEE Xplore 中文译名&#xff1a;AI-Guardian:利用后门防御对抗攻击 阅读原因…

记一次教学版内网渗透流程

信息收集 如果觉得文章写的不错可以共同交流 http://aertyxqdp1.target.yijinglab.com/dirsearch dirsearch -u "http://aertyxqdp1.target.yijinglab.com/"发现 http://aertyxqdp1.target.yijinglab.com/joomla/http://aertyxqdp1.target.yijinglab.com/phpMyA…

IT基础监控范围和对象

监控易作为一款由美信时代独立自主研发的分布式一体化集中监控平台&#xff0c;其监控范围极为广泛&#xff0c;几乎涵盖了所有主流的IT基础设施以及相关的设备和系统。以下是对监控易监控范围的详细介绍&#xff1a; 一、IT基础资源监控 服务器硬件监控&#xff1a;监控易支…

fmql之Linux阻塞和非阻塞IO

继续学习正点原子吧。 概念简介 什么是阻塞、非阻塞IO 阻塞/非阻塞访问的代码示例 等待队列&#xff08;阻塞访问使用&#xff09; 轮询&#xff08;非阻塞访问使用&#xff09; poll操作函数的使用&#xff08;轮询的一种&#xff09; 阻塞IO的实验 blockio.c blockioAPP.c 运…

大模型能力扩展之——提示词(Prompt),知识库,思维链(CoT)和Agent(代理)

前言 “大模型的推理能力配合外部工具才能真正发挥大模型的作用” 在学习和使用大模型的过程中&#xff0c;我们会发现大模型只能用来进行一下简单的问答&#xff1b;一旦涉及到复杂的问题&#xff0c;大模型就无能为力了。 其原因就在于我们并不会使用大模型&#xff0c;或…

【韩顺平Java笔记】第3章:变量

只记录我觉得重点的&#xff0c;自用&#xff0c;如果有漏的请自己看视频 文章目录 33. 内容梳理34. 变量原理34.1 为什么需要变量35. 变量概念35.1 概念35.2 变量使用的基本步骤36. 变量入门36.1 变量使用入门案例 37. 变量细节37.1 变量使用注意事项 38. 加号使用38.1 程序中…

Node.js安装Express,Node.js支持Typescript以及Express支持Typescript的步骤

1. Node.js 安装Express 运行如下命令&#xff1a; $ mkdir express-demo $ cd express-demo$ npm install express $ npm install body-parser //(可选)中间件&#xff0c;用于处理 JSON, Raw, Text 和 URL 编码的数据 $ npm install cookie-parser //(可选)通过req.cookies…

如何使用ssm实现大学生校园招聘网的设计与实现

TOC ssm738大学生校园招聘网的设计与实现jsp 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不可跨域…

网页WebRTC电话和软电话哪个好用?

关于WebRTC电话与软件电话哪个更好用&#xff0c;这实际上取决于多个因素&#xff0c;并没有一个绝对的答案。不过&#xff0c;我可以根据WebRTC技术的一些特点&#xff0c;以及与传统软件电话相比的优劣势&#xff0c;为你提供一个清晰的对比。 首先&#xff0c;让我们了解一下…

单细胞miloR分析(基于 KNN 图的细胞差异丰度分析方法)

通常情况下&#xff0c;对两组或多组样本进行了不同处理/干预之后&#xff0c;研究者首先会进行同种细胞亚群处理前后的细胞数量的比较&#xff0c;但在单细胞分辨率时代之后&#xff0c;即使是同一个亚群中的不同细胞也应当看成不同的样本。 那么问题就来了&#xff0c;既然应…

算法:按既定顺序创建目标数组

力扣1389 提示&#xff1a; 1 < nums.length, index.length < 100nums.length index.length0 < nums[i] < 1000 < index[i] < i 题解&#xff1a; class Solution {public int[] createTargetArray(int[] nums, int[] index) {int[] target new int[num…

SD2.0 Specification之CRC(Cyclic Redundancy Code)

文章目录 本文章主要讲解关于SD2.0中的CRC应用&#xff0c;其它基础概念和其它内容请参考以下文章。 SD2.0 Specification简述 CRC全称为Cyclic Redundancy Code&#xff0c;中文名称是循环冗余校验&#xff0c;该方法通过附加冗余数据来保证数据的完整性&#xff0c;即用于检…