哈希表及其与Java类集的关系

news2025/1/20 7:15:17

目录

1.哈希表的概念

2.哈希冲突

3.如何避免哈希冲突? 

3.1哈希函数设计

3.2 负载因子的调节

4.解决哈希冲突

4.1闭散列

4.1.1线性探测

4.1.2二次探测

4.2开散列(哈希桶)

5.HashMap

6.HashSet


1.哈希表的概念

假设有一组数据,要让你去搜索其中的一个关键码,这种场景是很常见的,不同的数据结构的搜索的效率是不同的.顺序结构及平衡树中,元素关键码与其存储的位置之间没有对应关系,因此在查找一个元素时,必须要经过关键码的多次比较.

顺序查找时间复杂度为O(N),平衡树查找时间复杂度是O(log2 N),搜索的效率取决于搜索过程中比较的次数! 

哈希表这个数据结构可以做到不经过任何比较,一次直接从表中得到要搜索的元素.

哈希表通过构造哈希函数使元素与元素存储位置之间能够建立一一映射的关系,那么查找的时间复杂度就是O(1).

向该结构插入元素时,根据待插入的元素的关键码,以哈希函数计算出元素存储的位置进行存放

搜索元素时同样方式,把求得的函数值作为元素的存储位置,在结构中按此位置取元素比较,相等则搜索成功

这种方式就是哈希(散列)方法,使用的函数称为哈希函数(散列函数),构造出来的结构称为哈希表结构(散列表)

2.哈希冲突

了解哈希冲突之前先看一个哈希表存储的例子 :
数据集合{1,5,9,8,6};

哈希函数:hash(key) = key % capacity;capacity为存储元素底层空间的总大小

 经过哈希函数的计算,1%10 = 1,5%10 = 5,9%10 = 9,8%10 = 8,6%10 = 6.

这样就将数据集合存进了哈希表,在搜索的时候不必进行多次比较,搜索效率大大提高

但是效率高的同时,也会出现其他问题,如果在上述哈希表中插入55,会出现什么问题?

我们发现哈希值是相同的,但是5下标已经存储了5,55没位置存了,即不同关键字通过相同的哈希函数计算出来相同的哈希地址,这种现象被称为哈希冲突.把具有相同哈希地址不同关键码的数据元素称为"同义词"

3.如何避免哈希冲突? 

 由于哈希表底层数组的容量往往下小于实际要存的关键字的数量,导致了哈希冲突的发生是必然的,我们能做的就是尽量降低哈希冲突率 


3.1哈希函数设计

引起哈希冲突可能是因为哈希函数设计不够合理

哈希函数设计的原则:

函数定义域必须包括需要存储的全部关键码,散列表允许有m个地址时,值域必须在0-m-1之间

哈希函数计算出来的哈希地址能均匀分布在整个空间中

哈希函数应该比较简单

常见的哈希函数:

1.直接定制法

取关键字的某个线性函数为哈希地址:hash(key) = A*key+B

优点:简单均匀

缺点:需要先知道关键字分布情况

使用场景:适合查找较小且连续的情况

2.除留余数法

设散列表中允许的地址为m,取一个质数p(p<=m),按照hash(key) = key%p,将关键码转换成哈希地址

哈希函数设计的越精妙,越能降低哈希冲突率,但不能避免哈希冲突

3.2 负载因子的调节

散列表负载因子定义: \alpha = 表中元素个数/散列表的长度

\alpha与表中元素个数成正比, \alpha越大,元素个数越多,反之, \alpha越小,表中元素个数越少,哈希冲突概率越低

Java的系统库限制了负载因子为0.75,超过这个值将resize散列表

负载因子和冲突率的关系演示

 因此当冲突率达到一定程度时,我们要通过降低负载因子来降低冲突率

哈希表的关键字个数是不变的,因此只能控制散列表的长度来降低冲突率

4.解决哈希冲突

在上述优化都不能有效降低哈希冲突率时,我们就要引入其他办法来解决哈希冲突

4.1闭散列

闭散列也叫开放定址法,当发生哈希冲突时,如果哈希表没有被装满,说明在哈希表中还有其他位置,那么可以将key放到冲突位置的"下一个"位置,寻找下一个位置有两种探测方法

4.1.1线性探测

在上面的场景中,要插入55元素,通过计算哈希地址为5,但是5位置已经被5填入了,即发生哈希冲突

线性探测法:从发生哈希冲突的位置一次向后寻找,找到一个空位置填入待插入元素

缺点

采用闭散列方法解决哈希冲突时要注意不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索,如果突然删除5,那么搜索55时,就找不到了,因此线性探测法要删除一个元素采用标记的伪删除法来删除一个元素

线性探测还会把冲突的元素都放在一起,如果要放入65,75,95,那么这些元素会被堆积在相邻有空的位置

4.1.2二次探测

为了解决线性探测将冲突数据堆积在一起的缺点,二次探测提供了寻找空位置的方法

或者

其中i = 1,2,3......i表示某个位置第几次重复哈希冲突

H0是通过散列函数Hash(x)对关键码key进行计算得到的位置

m表示表的大小 

如果放入55,65,75

 有效的解决了线性探测将冲突元素堆积的情况

当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能插入,而且任何一个位置都不会被探查两次.因此只要表中有一半的空位置,就不会存在表满的问题,在搜索时可以不考虑装满的情况,但在插入时必须确保表的负载因子<=0.5,如果超出,先扩容

删除时也是采用标记的方法删除

这也造成了闭散列的一个缺点:空间里利用率低

4.2开散列(哈希桶)

这是Java中的HashMap用到的解决哈希冲突的方式

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

 对于上述的插入55,65,75

 上图中开散列中每个桶放的都是发生哈希冲突的元素

开散列可以认为是把一个在大集合中的搜索问题,转化成在小集合中做搜索了

Java的HashMap中就是用这种数组加链表的方式解决哈希冲突,当数组长度超过64并且链表长度超过8的时候,就会变成红黑树,链表如果一直变长,那么搜索效率还是降低,链表的搜索效率是O(N),转化成红黑树存储,效率会提升!!

性能分析: 虽然哈希表一直在和冲突做斗争,但在实际使用过程中,我们认为哈希表的冲突率是不高的,冲突个数是可控的, 也就是每个桶中的链表的长度是一个常数,所以,通常意义下,我们认为哈希表的插入/删除/查找时间复杂度是 O(1) 

5.HashMap

我们使用HashMap实例化Map

public class Test {
    public static void main(String[] args) {
        HashMap<String,Integer> map = new HashMap<>();
        map.put("zhangsan",12);
        map.put("lisi",13);
        System.out.println(map);
    }
}

结果顺序不是我们put的顺序,是因为在put时,计算出的哈希值是不一样的,存放的位置就不同于put的顺序,原因不是比较造成的,是哈希出的地址不同

传入不可比较对象时,也可以添加

class Student{

}
public class Test {
    public static void main(String[] args) {
        HashMap<Student,Integer> map = new HashMap<>();
        map.put(new Student(),12);
        map.put(new Student(),13);
        System.out.println(map);
    }
}

添加元素时没有对象的比较,因此可以添加null值为key

public class Test {
    public static void main(String[] args) {
        HashMap<Student,Integer> map = new HashMap<>();
        map.put(null,null);
        System.out.println(map);
    }
}

 

HashMap中不会存在相同的key,key相同则更新value

public class Test {
    public static void main(String[] args) {
        HashMap<String,Integer> map = new HashMap<>();
        map.put("hello",12);
        map.put("hello",22);
        System.out.println(map);
    }
}

 HashMap的方法和TreeMap介绍的方法是相同的

使用HashMap统计一组数据中,每个数据出现的次数

public static void main(String[] args) {
        HashMap<Integer,Integer> map = new HashMap<>();
        int[] arr = {1,3,4,2,1,3,4};
        for (int i = 0; i < arr.length; i++) {
            int key = arr[i];
            if(map.get(key)==null){
                map.put(arr[i],1);
            }else{
                int val = map.get(key);//key出现的次数
                map.put(key,val+1);
            }
        }
        System.out.println("");
        for (Map.Entry<Integer,Integer> entry:
                map.entrySet()) {
            System.out.println("key: "+entry.getKey()+"出现次数: "+entry.getValue());
        }
    }

6.HashSet

使用HashSet实例化Set

public class Test {
    public static void main(String[] args) {
        HashSet<Integer> set = new HashSet<>();
        set.add(1);
        set.add(1);
        System.out.println("size: "+set.size());
    }
}

 HashSet也不能重复添加元素,是因为底层也是一个HashMap,来保证key是唯一的

value是一个默认值

HashSet有去重的功能,使用HashSet 统计一组数据中,第一个重复的数

    public static void main(String[] args) {
        HashSet<Integer> set = new HashSet<>();
        int[] arr = {5,3,4,2,1,3,4};
        for (int i = 0; i < arr.length; i++) {
            if(!set.contains(arr[i])){
                set.add(arr[i]);
            }
            else{
                System.out.println(arr[i]);
                break;
            }
        }
    }

在Map和Set一文中使用TreeSet对数据进行去重,使用HashSet也可以,并且效率更高

 

 

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

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

相关文章

JWT快速入门及所需依赖

目录 1.JWT 1.1什么是JWT 1.2JWT的构成 jwt的头部 payload signature 1.3JWT快速入门案例 2Jwt认证&#xff08;微服务&#xff09; 2.1微服务下统一权限认证 2.2应用认证 3.无状态的JWT令牌如何实现续签功能&#xff1f; 3.1不允许改变Token令牌实现续签 3.2允许改…

计算机毕业设计django基于python大学生多媒体学习系统

项目介绍 随着计算机多媒体技术的发展和网络的普及。采用当前流行的B/S模式以及3层架构的设计思想通过Python技术来开发此系统的目的是建立一个配合网络环境的大学生多媒体学习系统的平台,这样可以有效地解决数据学习系统混乱的局面。 本文首先介绍了大学生多媒体学习系统的发…

eslint Parsing error: The keyword ‘export‘ is reserved

报错 原因 ECMAScript modules(import/export) 是 es6 的语法。 根据 eslint 官方文档 Configure language options &#xff0c;eslint 默认使用 es5 语法&#xff1a; 解决 要让 eslint 知道我在使用 es6 的 modules 语法。有下面几种方法&#xff1a; 设置 env 为 es6&am…

喜讯 | 第三届国际科创节,企企通喜提两项大奖

近日&#xff0c;第三届国际科创节暨数服会STIF奖评选活动重磅揭晓&#xff0c;旨在向科技创新与数字化转型引领者致敬。企企通作为作为数字化采购平台领军者&#xff0c;凭借业内领先的技术实力与优秀的服务口碑&#xff0c;经过层层筛选和专业评审&#xff0c;企企通最终荣膺…

【LeetCode每日一题:1785. 构成特定和需要添加的最少元素~~~数组公式推导+防止整型溢出+向上取整+贪心】

题目描述 给你一个整数数组 nums &#xff0c;和两个整数 limit 与 goal 。数组 nums 有一条重要属性&#xff1a;abs(nums[i]) < limit 。 返回使数组元素总和等于 goal 所需要向数组中添加的 最少元素数量 &#xff0c;添加元素 不应改变 数组中 abs(nums[i]) < limi…

内存管理:虚拟地址空间和堆

准备用一个系列来总结一下内存管理涉及到的相关知识&#xff0c;范围从底层的数据结构和算法&#xff0c;到上层的API的使用&#xff0c;这里的内存管理&#xff0c;目前打算主要是侧重在堆的管理&#xff0c;本文作为一个引子&#xff0c;先粗略讲一下虚拟地址空间、堆管理、a…

​合并PDF文件什么方法很简单?看完你就明白了

想要将几个PDF文件合并到一起&#xff0c;什么方法使用起来是很简单的呢&#xff1f;PDF文件作为大家经常使用的文件之一&#xff0c;对它的编辑需求也很多&#xff0c;除了需要编辑文件的内容之外&#xff0c;还有需要将几个文件合并到一起使用的需求。那么我们如果遇到这种情…

traffic-forward

traffic-forward traffic-forward 是一款python开发的流量转发工具&#xff0c;可以使用python脚本行运行&#xff0c;也可以封装使用命令行&#xff0c;同样可以使用pyinstaller等工具进行封装成Macos&#xff0c;Linux, Windows 下的可执行文件运行&#xff0c;可用于本地流量…

简单理解HTML区块_HTML学习第七篇区块元素和内联元素

简单理解HTML区块_区块元素和内联元素HTML篇_第七章、区块一、区块元素和内联元素1.1块级元素1.2内联元素二、<div>元素三、<span>元素HTML篇_第七章、区块 一、区块元素和内联元素 HTML元素可以通过<div>和<span>元素组合起来&#xff0c;大多数 HT…

固定行数的纵向分栏

【问题】 what can ı configure the jasper report detail heapriider layout ? ı want to print datas side by side and every sides have 4 datas sub bottom 1 data1 5 data5 2 data2 6 data6 3 data3 4 data4 【回答】 整张报表纵向分栏可在 jasper 中设置分栏数&a…

性能高、上手快,实体类转换工具 MapStruct 到底有多强大

1.什么是MapStruct 1.1 JavaBean 的困扰 对于代码中 JavaBean之间的转换&#xff0c; 一直是困扰我很久的事情。在开发的时候我看到业务代码之间有很多的 JavaBean 之间的相互转化&#xff0c; 非常的影响观感&#xff0c;却又不得不存在。我后来想的一个办法就是通过反射&…

用Quasar开发Vue3+Electron跨平台应用的简单指南

1. 前言 Quasar是一个开源的vue.js基础框架&#xff0c;简单配置即可在其基础上进行SPA, SSR, PWA, 手机网站以及跨平台应用程序的开发&#xff0c;本文将简述如何基于Quasar Vue3 Vite Electron进行桌面应用开发。 2. 配置流程 2.1 框架构建 首先&#xff0c;在要存放代…

『NLP学习笔记』NER任务的CRF-layer的原理

NER任务的CRF-layer的原理 文章目录一. 预备工作二. BILSTM-CRF模型2.1. BiLSTM层输出2.2. 如果没有CRF层会怎么样2.3. CRF层可以从训练数据中学到约束三. CRF层3.1. 发射(Emission)分数3.2. 转移(Transition)分数3.3. CRF损失函数3.4. 实际路径得分3.5. 所有可能的路径的得分…

Ac-EEVVAC-pNA,389868-12-6

Ac-EEVVAC-pNA, chromogenic substrate for a continuous spectrophotometric assay of HCV NS3 protease. The sequence EEVVAC is derived from the 5A-5B cleavage junction of the HCV polyprotein. Ac-EEVVAC-pNA, HCV NS3蛋白酶连续分光光度法测定的显色底物。EEVVAC序列…

FPGA驱动24C04实现读写操作,提供工程源码和技术支持

目录1.24c04芯片手册解读2.纯verilog的i2c驱动3.24c04读写状态机设计4.上板调试验证5.福利&#xff1a;工程源码获取1.24c04芯片手册解读 24c04芯片手册很简单&#xff0c;原理图设计页很简单&#xff0c;这里只说代码设计需要注意的点&#xff1a; 1、写操作延时周期大于等于…

嵌入式软件工程师技能树——Linux应用编程+网络编程+驱动开发+操作系统+计算机网络

文章目录Linux驱动开发1、Linux内核组成2、用户空间与内核的通讯方式有哪些&#xff1f;3、系统调用read/write流程4、内核态用户态的区别5、bootloader内核 根文件的关系6、BootLoader的作用7、BootLoader两个启动阶段1、汇编实现&#xff0c;完成依赖于CPU体系架构的设置&…

推荐系统学习笔记--推荐系统简介

由来 互联网的出现和普及给用户带来了大量的信息&#xff0c;满足了用户在信息时代对信息的需求&#xff0c;但随着网络的迅速发展而带来的网上信息量的大幅增长&#xff0c;使得用户在面对大量信息时无法从中获得对自己真正有用的那部分信息&#xff0c;对信息的使用效率反而…

前三季度净亏损8.01亿元,亿咖通海外上市背后的「吉利阴影」

中国智能汽车产业链供应商正在通过SPAC方式在海外上市&#xff0c;或将成为新一轮资本浪潮的焦点。这些企业大多数已经具备一定规模&#xff0c;但仍处于亏损状态。 11月21日&#xff0c;亿咖通&#xff08;ECARX Holdings, Inc.&#xff09;宣布&#xff0c;之前与COVA Acqui…

Windows Terminal 快速配置 oh-my-posh

背景 想美化下windows terminal &#xff0c;选择了oh-my-posh。网上的文章有点多&#xff0c;加上官方的教程对初次使用着并不是太友好&#xff0c;所以自己快速摸索了。记录下过程。 步骤 1&#xff0c;安装oh-my-posh 打开以下链接&#xff0c;安装oh-my-posh Windows …

一个进程只能最多创建2000个线程吗?

我经常听到有人说&#xff0c;为什么不能创建一个包含2000个线程的进程。 原因不是Windows中固有的任何特定限制。相反&#xff0c;程序员没有考虑每个线程使用的地址空间量。 线程由内核模式下的一些内存&#xff08;内核堆栈和对象管理&#xff09;和用户模式下的一些内存&…