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

news2024/11/18 8:48:52

文本目录:

❄️一、哈希表:

   ☑ 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.coloradmin.cn/o/2177942.html

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

相关文章

太绝了死磕这本大模型神书!

今天给大家推荐一本大模型神书&#xff0c;就是这本&#xff1a;《大语言模型&#xff1a;基础与前沿》 书籍介绍&#xff1a; 本书深入阐述了大语言模型的基本概念和算法、研究前沿以及应用&#xff0c;涵盖大语言模型的广泛主题&#xff0c;从基础到前沿&#xff0c;从方法…

Colorful/七彩虹将星X15 AT 23 12代 4060显卡 Win11原厂OEM系统 带COLORFUL一键还原

安装完毕自带原厂驱动和预装软件以及一键恢复功能&#xff0c;自动重建COLORFUL RECOVERY功能&#xff0c;恢复到新机开箱状态。 【格式】&#xff1a;iso 【系统类型】&#xff1a;Windows11 原厂系统下载网址&#xff1a;http://www.bioxt.cn 注意&#xff1a;安装系统会…

优秀的拆分C++

题目&#xff1a; 样例解释&#xff1a; 样例1解释 6422221 是一个优秀的拆分。注意&#xff0c;6222 不是一个优秀的拆分&#xff0c;因为拆分成的 3 个数不满足每个数互不相同。 思路&#xff1a; 题目大致意思是说给定一个正整数NN&#xff0c;让你用二进制表示&#xff08;…

netty编程之实现websocket客户端并发送二进制消息

写在前面 源码。 本文看下netty如何实现websocket客户端并发送二进制消息。 ws的server端参考这篇文章。 1&#xff1a;正文 抽象类AbstractWebsocketClient定义了发送二进制数据的方法&#xff1a; public abstract class AbstractWebsocketClient implements Closeable {…

Vue $router.push打开新窗口

Vue $router.push打开新窗口 最近有粉丝小伙伴问我&#xff1a;$router.push方法用于在当前窗口中跳转路由&#xff0c;但有时候我们需要在新的窗口或标签页中打开一个路由改怎么实现呢&#xff1f; 那么这里就介绍下实现逻辑和代码案例&#xff01; 文章目录 Vue $router.pus…

机器人领域超重量奖项TRO傅京孙最佳论文奖汇总【下】

承接上篇&#xff1a; 机器人领域超重量奖项TRO傅京孙最佳论文奖汇总【上】https://blog.csdn.net/techflowai/article/details/142068484 2015年Best Paper 论文标题&#xff1a;ORB-SLAM: A Versatile and Accurate Monocular SLAM System 作者&#xff1a;Ral Mur-Artal,…

go dlv idea 远程调试-入门级

一&#xff0c;准备工作 linux 安装dlv git clone https://github.com/go-delve/delve.git $GOPATH/src/github.com/go-delve/delve cd $GOPATH/src/github.com/go-delve/delve make installecho export PATH$PATH:$GOPATH/bin >> ~/.bashrc## 测试是否安装成功 dlv ve…

【OS】计算机系统概述|操作系统基本概念|并发|并行|虚拟异步

✨ Blog’s 主页: 白乐天_ξ( ✿&#xff1e;◡❛) &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山冈&#xff01; &#x1f525; 所属专栏&#xff1a;C深入学习笔记 &#x1f4ab; 欢迎来到我的学习笔记&#xff01; 前言 一、操作系统的概念 操作系统…

DVWA | File Inclusion(文件包含)渗透测试

概念&#xff1a; 漏洞产生原因&#xff1a; 主要是由于开发人员没有对用户输入的文件路径进行严格的过滤和验证。例如&#xff0c;如果一个 Web 应用程序接受用户输入的文件路径&#xff0c;然后使用这个路径进行文件包含&#xff0c;而没有对用户输入进行任何检查&#xff0c…

【SOP】Windows下安装Neo4j流程

Neo4j简介 Neo4j 是一个基于图形结构的 NoSQL 数据库&#xff0c;专门用于存储和管理图数据。与传统的关系型数据库不同&#xff0c;Neo4j 使用 图&#xff08;graph&#xff09;的形式来表示数据&#xff0c;其中数据点&#xff08;称为 节点&#xff09;通过 边&#xff08;…

CHI trans--Home节点发起的操作

总目录&#xff1a; CHI协议简读汇总-CSDN博客https://blog.csdn.net/zhangshangjie1/article/details/131877216 Home节点能够发起的操作&#xff0c;包含如下几类&#xff1a; Home to Subordinate Read transactionsHome to Subordinate Write transactionsHome to Subor…

uniapp学习(001 介绍-安装等)

零基础入门uniapp Vue3组合式API版本到咸虾米壁纸项目实战&#xff0c;开发打包微信小程序、抖音小程序、H5、安卓APP客户端等 总时长 23:40:00 共116P 此文章包含第1p-第p4的内容 文章目录 创建项目目录结构效果下载微信小程序开发者工具在hbuilderX里配置开发者工具找到安全…

D22【python接口自动化学习】-python基础之判断与循环

day22 if语句的定义 学习日期&#xff1a;20240928 学习目标&#xff1a;内置数据类型--32 if语句&#xff1a;数据流程出现分支时&#xff0c;怎样编写程序&#xff1f; 学习笔记&#xff1a; 条件判断的用途 需求分析 真假值用布尔型表达 # if语句基本写法 if True:print…

技术速递|Python in Visual Studio Code 2024年9月发布

排版&#xff1a;Alan Wang 我们很高兴地宣布将于 2024 年 9 月发布适用于 Visual Studio Code 的 Python 和 Jupyter 扩展&#xff01; 此版本包括以下公告&#xff1a; Django 单元测试支持使用 Pylance 从 inlay 提示转到定义 如果您有兴趣&#xff0c;可以在我们的 Pyth…

cloud-(Nacos)--注册中心原理-服务注册-服务发现

并且通过(RestTemplate)Http请求实现了跨微服务的远程调用。不过这种手动发送Http请求的方式存在一些问题 在大型微服务项目中,服务提供者的数量会非常多,为了管理这些服务就引入了注册中心的概念。注册中心、服务提供者、服务消费者三者间关系如下: 流程如下: 服务启动…

【YOLOv8改进[SPPF]】使用SPPFCSPC替换SPPF模块 + 含全部代码和详细修改方式

本文将进行在YOLOv8中使用SPPFCSPC魔改v8,文中含全部代码、详细修改方式。助您轻松理解改进的方法。 改进前和改进后的参数对比如下: 目录 一 SPPFCSPC 二 使用SPPFCSPC魔改v8 1 整体修改 ① 添加SPPCSPC.py文件 ② 修改ultralytics/nn/tasks.py文件 2 配置文件

VGG16网络介绍及代码撰写详解(总结1)

可以从本人以前的文章中可以看出作者以前从事的是嵌入式控制方面相关的工作&#xff0c;是一个机器视觉小白&#xff0c;之所以开始入门机器视觉的学习只要是一个idea&#xff0c;想把机器视觉与控制相融合未来做一点小东西。废话不多说开始正题。 摘要&#xff1a;本文是介绍V…

超便携专业AI大师本带来生产力跃升,联想ThinkPad P1 AI 2024 AI元启版上市

随着AI技术在各行业的广泛应用&#xff0c;其实际效用愈加突出。无论是4K视频生成、建筑设计&#xff0c;还是仿真实验等专业领域&#xff0c;AI技术的支持使得过去需要数小时完成的任务如今分钟级即可完成。AI能够生成与人类创作者风格相似的内容&#xff0c;极大地提高了内容…

【Linux】基于驱动框架的程序编写测试

【Linux】基于驱动框架的程序编写测试 字符设备驱动工作原理☆ 驱动程序开发驱动程序开发步骤驱动代码框架驱动框架设计流程 编译与测试编译测试 参考博文&#xff1a; 【Linux】基于框架编写驱动代码、驱动代码编译和测试 Linux驱动&#xff08;驱动程序开发、驱动框架代码编…

智能摄像头DIY教程

你要去度假&#xff0c;想看看家里的情况吗&#xff1f;你想了解人工智能和计算机视觉吗&#xff1f;你有 Raspberry Pi、网络摄像头和一些空闲时间吗&#xff1f;那么这个项目就是为你准备的&#xff01; 在本文中&#xff0c;我们将介绍如何使用 Raspberry Pi 在 Python 中创…