【Java】哈希表

news2024/9/23 22:51:23

文章目录

  • 一、概念
  • 二、哈希冲突
    • 2.1概念
    • 2.2设计合理的哈希函数-避免冲突
    • 2.3调节负载因子-避免冲突
    • 2.4闭散列-冲突解决(了解)
    • 2.5开散列/哈希桶-冲突解决(重点掌握)
  • 三、代码实现
    • 3.1成员变量及方法的设定
    • 3.2插入
    • 3.3重新哈希
    • 3.4 获取到value的值


一、概念

不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函
数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素

当向该结构中:
插入元素
根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
搜索元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功
该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(HashTable)(或者称散列表)

举例:数组元素{1,7,6,4,5,9}
在这里插入图片描述
用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快

二、哈希冲突

2.1概念

当我们插入11的时候,会发现通过哈希函数计算出的要插入的位置等于1,而此时该位置已经有元素存在,即:不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

2.2设计合理的哈希函数-避免冲突

首先,我们需要明确一点,由于我们哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致一个问题,冲突的发生是必然的,但我们能做的应该是尽量的降低冲突率
引起哈希冲突的一个原因可能是:哈希函数设计不够合理
哈希函数设计原则:
哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
哈希函数计算出来的地址能均匀分布在整个空间中
哈希函数应该比较简单
常用的哈希函数
1.直接定制法
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况 使用场景:适合查找比较小且连续的情况
2.除留余数法
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址
以上两种是比较常用的方法,还有其他方法感兴趣的可以自行查阅相关资料
注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

2.3调节负载因子-避免冲突

负载因子 α = 哈希表中元素个数 / 哈希表的长度
哈希表的长度没有扩容是定长的,即 α 与 元素的个数是成正比的,当 α 越大,即代码哈希表中的元素个数越多,元素越多,发生哈希冲突的概率就增加了,因此 α 越小,哈希冲突的概率也就越小。所以我们应该严格控制负载因子的大小,在 Java 中,限制了负载因子最大为 0.75,当超过了这个值,就要进行扩容哈希表,重新哈希(重新将各个元素放在新的位置上)

当冲突率达到一个无法忍受的程度时,我们需要通过降低负载因子来变相的降低冲突率
已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小

2.4闭散列-冲突解决(了解)

解决哈希冲突两种常见的方法是:闭散列和开散列
这里我们先讲闭散列
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。 那么如何寻找下一个空位置呢?
1.线性探测
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止
插入
通过哈希函数获取待插入元素在哈希表中的位置
如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到
下一个空位置,插入新元素
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他
元素的搜索。比如删除元素1,如果直接删除掉,11查找起来可能会受影响。因此线性探测采用标
记的伪删除法来删除一个元素
2.二次探测
线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为: Hi= ( H0+i^2 )% m, 或者:Hi= (H0 - i ^2)% m。其中:i = 1,2,3…, H0是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小
当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不
会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容
所有:闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷

2.5开散列/哈希桶-冲突解决(重点掌握)

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中,简单来说就是数组+链表的结构
如图所示:
在这里插入图片描述
从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。
开散列,可以认为是把一个在大集合中的搜索问题转化为在小集合中做搜索了

三、代码实现

3.1成员变量及方法的设定

public class HashBuck2 <K,V>{
    static class Node<K,V> {
        public K key;
        public V val;
        public Node<K,V> next;

        public Node(K key, V val) {
            this.key = key;
            this.val = val;
        }
    }
    public Node<K,V>[] array = (Node<K,V>[])new Node[10];
    //当前存储的元素个数
    public int usedSize;
    //负载因子
    public static final double  LOAD_FACTOR = 0.75;
    private double doLoadFactor() {
        return usedSize*1.0 / array.length;
    }

这里我们采用数组来存储我们的数据,而每个数组的元素是 Node这样的节点,节点中包含 next 引用,用来存放下一个节点,从而实现数组中每个元素可以是一个链表的结构
如图所示:
在这里插入图片描述

3.2插入

这里的插入有两种情况
1.通过 hash 值,得到哈希表的位置上不存在元素,也就是 hash 位置为 null 的情况下
直接在当前位置new一个节点进行插入
2.通过 hash 值,得到哈希表的位置上已经存在元素了,也就是 hash 位置 不为 null 的情况下
遍历链表如果没有与要插入元素相同,就直接采用头插或者尾插
如果遍历链表发现有与要插入元素相同,直接修改该元素所对应的value值就可以了

代码如下:

    public void push(K key,V val) {
        Node<K,V> node = new Node<K,V>(key, val);
        //找到位置
        int hash = key.hashCode();
        int index = hash % array.length;

        //遍历数组
        Node<K,V> cur = array[index];
        while (cur != null) {
            if(cur.key.equals(key)) {
                //对val进行更新
                cur.val = val;
                return;
            }
            cur = cur.next;
        }
        //头插法
        node.next = array[index];
        array[index] = node;
        usedSize++;
        if( doLoadFactor() >= 0.75) {
            //重新哈希
            reSize();
        }
    }

以上采用的是头插法,这里每插入一个元素都要判断是否超出了我们设定的负载因子,如果超出了就要重新调整哈希表的长度

3.3重新哈希

哈希表的长度发生改变,表中元素key所对应的hash值也会发生改变,所以扩容之后,原来表中所有元素的位置都要通过新的 hash 值放入到新的位置上,再把新的数组拷贝回原来的数组

代码如下:

    private void reSize() {
        Node[] newArray = new Node[array.length*2];
        //处理重新哈希
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            while (cur != null) {
                int index = cur.key.hashCode() % newArray.length;
                //记录下来 之前的cur.next
                Node curNext = cur.next;
                //进行头插法,插入到新数组
                cur.next = newArray[index];
                newArray[index] = cur;
                cur = curNext;
            }
        }
        //把数据给到原数组 array
        array = newArray;
    }

3.4 获取到value的值

通过key获取到index的位置,这个位置可能没有元素,可能是一条链表,但链表中也可能不存在key,也可能存在 key,如果 index 位置没有元素,或者遍历 index 位置都没找到 key,那么就返回 null,找到了即返回 key 对应的 value 值即可

代码如下:

    public V get(K key) {
        //找到数组中index的位置
        int hash = key.hashCode();
        int index = hash % array.length;
        //遍历数组中的每个链表
        Node<K,V> cur = array[index];
        while (cur != null) {
            if(cur.key.equals(key)) {
                return  cur.val;
            }
            cur = cur.next;
        }
        return null;
    }

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

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

相关文章

面试题-Elasticsearch集群架构和调优手段(超全面)

对于Elasticsearch&#xff08;ES&#xff09;&#xff0c;我了解并有经验。在我之前的公司&#xff0c;我们有一个相对大型的ES集群&#xff0c;以下是该集群的架构和一些调优手段的概述&#xff1a; 1. 集群架构 集群规模&#xff1a;我们的ES集群由15个节点组成&#xff0c…

ICC2:postmask ECO参考脚本

更多学习内容请关注「拾陆楼」知识星球 拾陆楼知识星球入口 eco_netlist -by_verilog_file eco.v -write_changes eco.tcl set_app_options -name design.eco_freeze_silocon_mode -value true source eco tcl set_app_options -name design.eco_freeze_silocon_mode -valu…

C++ 3.25作业

1、定义自己的命名空间&#xff0c;其中有string类型的变量&#xff0c;再定义两个函数&#xff0c;一个函数完成字符串的输入&#xff0c;一个函数完成求字符串长度&#xff0c;再定义一个全局函数完成对该字符串的反转 #include <iostream>using namespace std;namesp…

vite+vue3动态模块化导入并使用pinia

一、安装引入pinia 1.安装 pnpm install pinia # 或者使用 yarn yarn add pinia # 或者使用 npm npm install pinia 2.在main.js里引入 import { createApp } from vue import App from ./App.vue import { createPinia } from pinia createApp(App).use(createPinia()).mo…

【linux网络(一)】初识网络, 理解四层网络模型

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux网络 1. 前言2. 初识网络…

Python 全栈体系【四阶】(十八)

第五章 深度学习 一、基本理论 4. 神经网络的改进 4.1 神经网络的局限 全连接神经网络的局限&#xff08;一&#xff09; 未考虑数据的“形状”&#xff0c;会破坏数据空间结构。例如&#xff0c;输入数据是图像时&#xff0c;图像通常是高长通道方向上的 3 维形状。但是&a…

JavaParser 手动安装和配置

目录 前言 一、安装 Maven 工具 1.1 Maven 软件的下载 1.2 Maven 软件的安装 1.3 Maven 环境变量配置 1.4 通过命令检查 Maven 版本 二、配置 Maven 仓库 2.1 修改仓库目录 2.2 添加国内镜像 三、从 Github 下载 JavaParser 3.1 下载并解压 JavaParser 3.2 从路径打…

C#学习笔记2:设置启动项目、数组/二维数组

今日继续我的C#学习之路 设置启动项目&#xff1a; Visual Studio 无法直接启动带有“类库输出类型“的项目。若要调试此项目&#xff0c;请在此解决方案中添加一个引用库项目的可执行项目。将这个可执行项目设置为启动项目问题解决 调试学习时的一个小插曲&#xff0c;有时会…

yolov8直接调用zed相机实现三维测距(python)

yolov8直接调用zed相机实现三维测距&#xff08;python&#xff09; 1. 相关配置2. 相关代码3. 实验结果 相关链接 此项目直接调用zed相机实现三维测距&#xff0c;无需标定&#xff0c;相关内容如下&#xff1a; 1.yolov5直接调用zed相机实现三维测距&#xff08;python&#…

深入解析MySQL的四种打开方式

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

利用云手机技术,开拓海外社交市场

近年来&#xff0c;随着科技的不断进步&#xff0c;云手机技术逐渐在海外社交营销领域崭露头角。其灵活性、成本效益和全球性特征使其成为海外社交营销的利器。那么&#xff0c;究竟云手机在海外社交营销中扮演了怎样的角色呢&#xff1f; 首先&#xff0c;云手机技术能够消除地…

Linux:进程概念认识

进程 基本概念 课本概念&#xff1a;程序的一个执行实例&#xff0c;正在执行的程序等 内核观点&#xff1a;担当分配系统资源&#xff08; CPU 时间&#xff0c;内存&#xff09;的实体。 描述进程 -PCB 进程信息被放在一个叫做进程控制块的数据结构中&#xff0c;可以理解为…

【Canvas与艺术】淡蓝辉光汽车速度仪表盘

【关键点】 内圈处渐变色的采用。 【效果图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>淡蓝辉光汽车速度仪表盘</t…

【学习】Web安全测试需要考虑哪些情形

一、数据加密 某些数据需要进行信息加密和过滤后才能在客户端和服务器之间进行传输&#xff0c;包括用户登录密码、信用卡信息等。例如&#xff0c;在登录某银行网站时&#xff0c;该网站必须支持SSL协议&#xff0c;通过浏览器访问该网站时&#xff0c;地址栏的http变成https…

【智能算法】飞蛾扑火算法(MFO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2015年&#xff0c;Mirjalili等人受到飞蛾受到火焰吸引行为启发&#xff0c;提出了飞蛾算法(Moth-Flame Optimization&#xff0c;MFO)。 2.算法原理 2.1算法思想 MFO基于自然界中飞蛾寻找光源的…

【MySQL数据库】数据类型和简单的增删改查

目录 数据库 MySQL的常用数据类型 1.数值类型&#xff1a; 2.字符串类型 3.日期类型 MySQL简单的增删改查 1.插入数据&#xff1a; 2.查询数据&#xff1a; 3.修改语句&#xff1a; 4.删除语句&#xff1a; 数据库 平时我们使用的操作系统都把数据存储在文件中&#…

2015年认证杯SPSSPRO杯数学建模A题(第一阶段)绳结全过程文档及程序

2015年认证杯SPSSPRO杯数学建模 A题 绳结 原题再现&#xff1a; 给绳索打结是人们在日常生活中常用的技能。对登山、航海、垂钓、野外生存等专门用途&#xff0c;结绳更是必不可少的技能之一。针对不同用途&#xff0c;有多种绳结的编制方法。最简单的绳结&#xff0c;有时称…

python爬虫基础-----运算符(第三天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

ubuntu下安装minconda

1.搜索清华源 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 2.搜索conda 3.选一个合适自己的下载到本地 4.将下载的文件传入到ubuntu中 bash Miniconda3-py311_23.11.0-1-Linux-x86_64.sh 安装 5.source ~/.bashrc 激活即可&#xff08;必要步骤&#xff09;

了解一波经典的 I/O 模型

最近读了波网络 I/O 相关的文章&#xff0c;做下总结、摘录。&#xff08;未完&#xff09; 经典 I/O 模型 {% checkbox red checked, 阻塞式 I/O&#xff08;blocking I/O&#xff09; %}{% checkbox red checked, 非阻塞式 I/O&#xff08;non-blocking I/O&#xff09; %}…