当前位置: 首页 > news >正文

java基础 之 Hash家族_哈希冲突

文章目录

  • 前言
  • 入门小场景
  • 哈希冲突
    • 定义
    • 出现冲突的原因
    • 解决哈希冲突的办法
      • 拉链法
      • 开放地址法
      • 再哈希法
    • 哈希冲突的实现代码
      • 拉链法
      • 开放地址法
        • 线性探测法
        • 二次探测法
        • 双重哈希法
      • 再哈希法

前言

前文主要介绍了HashCode、HashMap、HashTable、ConcurrentHashMap和HashSet这五大行者,这期就往细里扒拉一下~
戳这里 → java基础 之 Hash家族_五行者

入门小场景

提到哈希表,那肯定得说哈希函数;说到哈希函数,不可避免的就是哈希冲突,那么他们是什么呢?接下来我们借用图书馆来想象一下 ~

  • 哈希函数

    图书管理员希望快速找到每本书,于是他设计了一个规则哈希函数):根据书名的字母来决定把书放到哪个书架的哪个格子里。比如书名有5个字母的书就放在第五个书架的第五个格子,书名有6个字母的书就放在第六个书架的第六个格子(这里就是举例,拒绝较真哈)。这样管理员在找书时就很快了。

  • 哈希冲突

    但是问题来了。比如有两本书,一本叫《猫》,一本叫《狗》,书名都是3个字母。按照规则它们都会被放在第三个书架的第三个格子。但是每个格子只能放一本,放了一本后另一本怎么办?这就发生了冲突哈希冲突

  • 解决哈希冲突
    • 1、拉链法

      管理员发现冲突后,想了个办法:在每个书架格子里挂一个小挂钩,如果有多本书被分配到同一个格子,就把它们都挂在同一个挂钩上。比如《猫》和《狗》都被分配到第3个书架的第3个格子,管理员就把它们都挂在同一个挂钩上。这样,虽然它们共享一个格子,但通过挂钩可以区分它们。在哈希表中,这种方法叫“拉链法”,每个格子后面挂一个链表来存储冲突的元素。

    • 2、开放地址法

      管理员还有另一种办法:如果一个格子被占用了,就找下一个空的格子。比如《猫》放在了第3个书架的第3个格子,而《狗》也被分配到这里,管理员就看看第3个书架的第4个格子是不是空的,如果空就放进去。如果第4个格子也被占了,就继续往后找,直到找到一个空的格子。这种方法叫“开放定址法”,意思是“如果这个地方满了,就找下一个空的地方”。

    • 3、建立更大的书架(扩容)

      如果书架格子太少,冲突太多,管理员也可以选择增加书架的格子数量。比如原来只有10个书架,每个书架有10个格子,现在增加到20个书架,每个书架有20个格子。这样,冲突的概率就会降低,因为格子更多了。在哈希表中,这叫“扩容”,也就是增加哈希表的大小。

这么讲,是不是比较清晰呢?那我们言归正传,来吧

哈希冲突

定义

哈希冲突是指在哈希表中,两个或多个元素被映射到了同一个位置的情况。
映射到同一个位置的原因就是经过哈希函数的处理,得到了相同的哈希值。

出现冲突的原因

  • 哈希函数设计不合理

    如果哈希函数设计的不合理,那么生成的哈希值就会分布不均匀。例如取模运算,就会出现规律性的输入值就会有大概率映射到相近的哈希值

  • 哈希表容量不足

    当数据量远大于哈希表的容量时,冲突的概率会显著增加。例如,将10000个数据映射到长度为10的表中

  • 负载因子过高

    负载因子 = 已存储元素数 ➗ 哈希表长度,用来衡量哈希表的空间使用率

    当负载因子过高时,哈希表总的空槽位减少,扩容不及时会增加冲突概率。
    java中的hashMap默认表长为16,负载因子是0.75,也就是使用了75%容量时会触发扩容

解决哈希冲突的办法

拉链法

将冲突的元素存储在同一个哈希桶中。哈希桶通常使用链表、红黑树等数据结果

  • 操作过程
    • 查询

      计算哈希值并找到对应的哈希桶,然后遍历桶内元素。如果找到相等的key就返回值,否则返回null;

    • 插入

      计算哈希值并找到对应的哈希桶,如果桶内存在相同的key,就更新值;没有就将元素插入桶中

    • 删除

      计算哈希值并找到对应的哈希桶,如果桶内存在相同的key,就删除该节点,并调整链表或树结构

  • 优缺点
    • 优点

      1、可以使用多种数据结构来优化查询效率,例如jdk1.8之后,当hashMap在哈希桶链表长度超过8且哈希表长度超过64时,链表会转换为红黑树
      2、插入、删除操作简单,不需要移动其他元素

    • 缺点

      1、如果某个桶内链表过长,查询效率会降低
      2、每个节点需要额外存储指针,增加内存开销

开放地址法

将所有元素直接存储在哈希表的数组中。如果发生冲突,系统会根据探测策略查找下一个空槽位

  • 常见的探测策略
    • 线性探测

      每次冲突后按照固定步长(通常为1)向后探测;可能导致冲突元素集中在相邻槽位中,称为一次聚集

      index = (Hash(key)+i)%table_size,其中i为探测次数,i=0,1,2,3,4,5…

    • 平方探测

      每次冲突后按照平方递增的步长向后探测,减少一次聚集现象,但冲突元素会总会探测到相同槽位,称为二次聚集

      index = (Hash(key) + i²) % table_size,其中 i 为探测次数, i² = 0,1,4,9 …

    • 双重哈希

      使用两个不同的哈希函数处理冲突,第一个哈希函数确定初始槽位,第二个哈希函数确定步长,减少聚集现象,但对哈希函数要求较高

      index = (Hash1(key) + i * Hash2(key)) % table_size,其中 i 为探测次数

  • 如何判断哈希表探测完成?
    • 空槽位:探测到空槽位,说明该位置未被占用,可以停止探测。
    • 已删除槽位:探测到已删除槽位,说明该位置曾经被使用过,后续可能还存在冲突元素,应继续探测。
    • 回到起始槽位:表明所有位置已探测完毕。
  • 操作过程
    • 查询

      计算哈希值确定初始槽位,按探测策略依次检查槽位,若找到目标 key,则返回值;否则返回 null。

    • 插入

      空槽位,插入,停止探测;非空槽位,若 key 相等更新值,否则继续探测

    • 删除

      找到 key 相等的目标后,把槽位标记为已删除,并停止探测

  • 优缺点
    • 优点

      1、实现简单,不需要额外的数据结构,所有数据都存储在哈希表中。
      2、查询效率较高,直接访问数组。

    • 缺点

      1、冲突频繁时,探测效率会急剧下降。
      2、扩容成本高,需要重新分配和移动所有元素

再哈希法

使用多个独立的哈希函数处理冲突。冲突时,依次用不同的哈希函数来探测槽位

  • 操作过程
    • 查询

      计算哈希值 hash = H1(key)定位槽位,若冲突,则尝试 H2(key),H3(key)… 依次类推

    • 插入

      计算哈希值 hash = H1(key)定位槽位,若冲突,则尝试 H2(key),H3(key)… 依次类推

    • 删除

      计算哈希值 hash = H1(key)定位槽位,若槽位中的 key 匹配,则标记为已删除;否则尝试下一个哈希函数

  • 优缺点
    • 优点

      哈希值分布更均匀,冲突概率低

    • 缺点

      1、实现复杂,需要设计多个独立的哈希函数。
      2、性能依赖哈希函数的计算效率

哈希冲突的实现代码

拉链法

public class Chaining {// 链地址法的基本思想是,每个槽包含一个链表,当冲突发生时,新的键值对被插入到链表中。private int size;private LinkedList<Integer>[] table;public Chaining(int size) {this.size = size;table = new LinkedList[size];for (int i = 0; i < size; i++) {table[i] = new LinkedList<Integer>();}}// 插入public void insert(int key) {int index = key % size;if (!table[index].contains(key)) {table[index].add(key);}else {System.out.println("Key " + key + " already exists.");}}// 查找public boolean search(int key) {int index = key % size;return table[index].contains(key);}
}

开放地址法

线性探测法
public class Linear {private int[] table;private int size;public Linear(int size) {this.size = size;table = new int[size];Arrays.fill(table,-1);}/*** 插入 */public void insert(int key){int hash = key%size;while (table[hash]!=-1){// 线性探测,容易出现堆积效应hash = (hash+1)%size;}table[hash] = key;}/*** 搜索 */public int search(int key){int hash = key%size;while (table[hash]!=-1){if (table[hash]==key) return hash;hash = (hash+1)%size;}return -1;}
}
二次探测法
public class LinearSquare {private int[] table;private int size;public LinearSquare(int size) {this.size = size;table = new int[size];Arrays.fill(table,-1);}/*** 插入 */public void insert(int key){int hash = key%size;int num = 0;while (table[hash]!=-1){// 平方探测法:按照二次方级别递增探测位置,避免堆积效应hash = (hash+num*num)%size;}table[hash] = key;}/*** 搜索 */public int search(int key){int hash = key%size;int num = 0;while (table[hash]!=-1){if (table[hash]==key) return hash;hash = (hash+num*num)%size;}return -1;}
}
双重哈希法
public class DoubleHashing {private int[] hashTable;private int size;public DoubleHashing(int size){this.size = size;hashTable = new int[size];Arrays.fill(hashTable,-1);}// 第一个哈希函数private int hash1(int key){return key % size;}// 第二个哈希函数private int hash2(int key){//①对关键字取模7,确保结果再0~6之间;//②7-(key % 7)保证最终结果为1~7,避免步长为0// 选择质数7是为了提高探测序列的分布均匀性,减少聚集现象;一般这个质数的选择取决于哈希表的sizereturn 7 - (key % 7);}public void insert(int key){int hash = hash1(key);int step = hash2(key);int num = 0;while (hashTable[hash]!=-1){hash = (hash+num*step)%size;}hashTable[hash] = key;}public int search(int key){int hash = hash1(key);int step = hash2(key);int num = 0;while (hashTable[hash]!=-1){if (hashTable[hash]==key){return hash;}hash = (hash+num*step)%size;}return -1;}
}

再哈希法

public class ReHashing {// 再哈希法的基本思想是,当哈希表达到一定装填因子时,重新计算一个新的哈希函数,将所有数据重新分配到更大的哈希表中private int[] table;private int size;private int length;private final double loadFactor = 0.75;public ReHashing(int size) {this.size = size;table = new int[size];Arrays.fill(table, -1);}// 插入public void insert(int key) {if (length >= size * loadFactor) {// 扩容int newSize = size * 2;int[] newTable = new int[newSize];Arrays.fill(newTable, -1);for (int i = 0; i < size; i++) {if (table[i] != -1) {int index = table[i] % newSize;while (newTable[index] != -1) {index = (index + 1) % newSize;// 线性探测法}newTable[index] = table[i];}}table = newTable;size = newSize;}}// 查找public boolean  search(int key) {int index = key % size;while (table[index] != -1) {if (table[index] == key)  return true;index = (index + 1) % size;}return false;}
}
http://www.dtcms.com/a/490767.html

相关文章:

  • 算法--双指针二
  • RK3576开发板/核心板应用分享之开源鸿蒙
  • 公司网站页脚外包公司的业务员
  • [crackme]028-ArturDents-CrackMe#3
  • 黑盒测试与白盒测试
  • 为安防装上“智慧大脑”:解密视频融合平台EasyCVR的智能分析技术内核
  • 同一设备多账号登录,如何避免消息推送“串门”?
  • 【Linux】认识Framebuffer
  • 深圳做网站公司有哪些公司英文购物网站模板下载
  • 力扣热题100道之560和位K的子数组
  • Pixel-Perfect Depth with Semantics-Prompted Diffusion Transformers,nips 2025
  • 网站可以换主机吗做外贸网站 用国外空间 还是 国内空间 区别
  • **SLAM技术:探索现代定位与地图构建的新纪元**在现代科技领域,同步定位与地图构建(SLAM)技术已成为机器人导航和自动驾驶等领
  • 环保教育展厅建设方案-VR垃圾分类体验游戏-垃圾分类拍拍乐
  • 网站空间怎么更换莱芜在线论坛最新消息
  • 龙岩做网站哪家好如何绑定网站域名
  • [Linux系统编程——Lesson14.基础IO:系统文件IO]
  • golang的一些技巧
  • 高性能 Go 语言带 TTL 的内存缓存实现:精确过期、自动刷新、并发安全
  • ML.NET机器学习框架基本流程介绍
  • Day32_【 NLP _2.RNN及其变体 _(2) LSTM】
  • 重庆建站模板代理怎么做p2p网站
  • iis配置网站是什么网站建设方案书阿里云模板
  • 【计算机视觉】SAM 3 技术深潜:从“分割万物”到“理解概念”的范式转移
  • 「深度学习笔记3」概率论深度解析:从不确定性到人工智能的桥梁
  • 齐河专业企业网站建设做网站引流到天猫
  • 技术贴!【谷歌浏览器】实用工具推荐之谷歌浏览器(Google Chrome)离线纯净版完全安装指南:告别广告与捆绑骚扰
  • Centos7 自建Umami-开源免费的网站访问流量统计分析平台
  • 申威架构安装Java 11 RPM包教程:java-11.0.7-swjdk-11u-8.ky10.sw_64.rpm详细安装步骤
  • 【STM32项目开源】基于STM32的人体健康监测系统