模拟解决哈希表冲突

目录

解决哈希表冲突原理:

模拟解决哈希表冲突代码:

负载因子:

动态扩容:

总结:

HashMap和HashSet的总结:


解决哈希表冲突原理:

        黑色代表一个数组,当 出现哈希冲突时(相同数组下标),该位置下标会变成一个链表(上图红色区域)。这样就能存储了。叫做开散列 / 哈希桶。链表长度超过阈值(默认8)时转为红黑树,优化冲突性能。

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

模拟解决哈希表冲突代码:

private static class Node {
        private int key;
        private int value;
        Node next;


        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }


    private Node[]  array;
    private int size;   // 当前的数据个数
    private static final double LOAD_FACTOR = 0.75;  //默认最大负载因子
    private static final int DEFAULT_SIZE = 8; //默认桶的大小

    //初始化哈希桶
    public HashBucket() {
        array = new Node[DEFAULT_SIZE];
    }


    //放元素
    public int put(int key, int value) {

        //1.遍历index数组下的链表,如果有相同的key则更新val
        int index = key % array.length;
        Node cur = array[index];
        while(cur != null) {
            if(cur.key == key) {
                cur.value = value;
                return 1;
            }
            cur = cur.next;
        }

        //2.头插法擦插入节点
        Node node = new Node(key,value);
        node.next = array[index];
        array[index] = node;

        //3.重新计算当前的负载因子 是不是超过了 我们规定的负载因子
        if(loadFactor() >= LOAD_FACTOR) {
            //扩容
            resize();
        }
        return -1;
    }


    //扩容
    private void resize() {
        // write code here
        Node[] newArray = new Node[array.length * 2];

        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            while(cur != null) {
                int newindex = cur.key % newArray.length;

                //把当前节点 放到新的数组的 newindex 位置  头插法
                Node curN = cur.next;
                cur.next = newArray[newindex];
                newArray[newindex] = cur;
                cur = curN;

            }
        }
        array = newArray;

    }


    //计算负载因子
    private double loadFactor() {
        return size * 1.0 / array.length;
    }
    
    //取对应key的value值
    public int get(int key) {
        // write code here
        int index = key % array.length;
        Node cur = array[index];

        while (cur != null) {
            if(cur.key == key) {
                return cur.value;
            }
            cur = cur.next;
        }
        return -1;
    }

负载因子:

哈希表中已存储的元素数量(n)与哈希表的容量(m)的比值,通常用公式表示为:
负载因子 = 已存储元素数量哈希表容量 / 哈希表容量。
例如,一个哈希表的容量是 100,当前已经存储了 50 个元素,那么此时该哈希表的负载因子就是 50 / 100 = 0.5

        负载因子主要用于衡量哈希表的空间使用程度和性能,它直接影响哈希表的插入、查找和删除操作的效率:

空间利用率:负载因子越大,说明哈希表中存储的元素越满,空间利用率越高;反之,负载因子越小,空间利用率越低,意味着有较多的空闲存储桶。

冲突概率:冲突是指不同的键通过哈希函数计算得到了相同的存储桶索引。负载因子越大,发生冲突的概率就越高。因为随着元素数量的增加,有限的存储桶被占用的比例也在增大,新元素更容易被映射到已经有元素的桶中。

动态扩容:

为了保证哈希表在不同的元素数量下都能保持较好的性能,当负载因子超过某个阈值(通常是默认的负载因子)时,哈希表会进行动态扩容操作。具体步骤如下:

  1. 创建更大的哈希表:通常新的哈希表容量是原容量的 2 倍。
  2. 重新哈希:将原哈希表中的所有元素重新计算哈希值,并插入到新的哈希表中。
  3. 更新引用:将原哈希表的引用指向新的哈希表。

总结:

1.java 中使用的是哈希桶方式解决冲突的。

2.java 会在冲突链表长度大于一定阈值后,将链表转变为搜索树(红黑树)。

3. java 中计算哈希值(相当于计算出的数据应该在数组放的下标位置)实际上是调用的类的 hashCode 方法,进行 key 的相等性比较是调用 key 的  equals 方法

        所以如果要用自定义类作为 HashMap 的 key 或者 HashSet 的值,必须重写 hashCode 和 equals 方法,而且要做到 equals 相等的对象,hashCode ⼀定是⼀致的:

比如:

import java.util.HashMap;
import java.util.Map;

class Person {
    private int id;

    public Person(int id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id;
    }

    @Override
    public int hashCode() {
        return id;
    }
}

public class HashMapDuplicateKeyExample {
    public static void main(String[] args) {
        Map<Person, String> map = new HashMap<>();
        Person p1 = new Person(1);
        Person p2 = new Person(1);
        map.put(p1, "Alice");
        map.put(p2, "Bob");
        System.out.println(map.size()); // 输出: 1
    }
}

代码过程:

HashMap 先计算 p1 的哈希码,因为 p1 的 id 是 1,所以哈希码就是 1

根据这个哈希码找到对应的哈希桶,把键值对 (p1, "Alice") 存进去。

HashMap 计算 p2 的哈希码,由于 p2 的 id 也是 1,所以哈希码同样是 1,和 p1 的哈希码一样,会对应到同一个哈希桶。

接着,HashMap 会用 equals() 方法比较 p1 和 p2,因为 p1 和 p2 的 id 相等,equals() 方法返回 true,这表明 p1 和 p2 是相同的键。

此时,HashMap 不会再新增一个键值对,而是用新的值 "Bob" 覆盖掉原来键 p1(和 p2 视为相同键)对应的值 "Alice"

由于 p1 和 p2 被 HashMap 判定为相同的键,所以 HashMap 里实际上只有一个键值对,调用 map.size() 就会输出 1

此段代码我们知道:

hashCode 方法用于确定键对象在哈希表中的存储位置

如果两个键的 hashCode 相同,并且 equals 方法返回 true,则认为这两个键是相同的,后插入的键值对会覆盖之前的。

        

HashMap和HashSet的总结:

特性HashMapHashSet
存储结构键值对(Key-Value)单一对象(Key)
重复元素键唯一,值(value)可重复元素唯一
null支持1个null键,多个null值1个null元素
底层原理哈希表(数组+链表 / 红黑树)HashMap
核心方法put(), get(), remove()add(), remove(), contains()
应用场景键值映射、缓存
去重、集合操作

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FPGA简介|结构、组成和应用

Field Programmable Gate Arrays&#xff08;FPGA&#xff0c;现场可编程逻辑门阵列&#xff09;&#xff0c;是在PAL、GAL、CPLD等可编程器件的基础上进一步发展的产物&#xff0c; 是作为专用集成电路&#xff08;ASIC&#xff09;领域中的一种半定制电路而出现的&#xff0c…

【机器学习】超参数调优指南:交叉验证,网格搜索,混淆矩阵——基于鸢尾花与数字识别案例的深度解析

一、前言&#xff1a;为何要学交叉验证与网格搜索&#xff1f; 大家好&#xff01;在机器学习的道路上&#xff0c;我们经常面临一个难题&#xff1a;模型调参。比如在 KNN 算法中&#xff0c;选择多少个邻居&#xff08;n_neighbors&#xff09;直接影响预测效果。 • 蛮力猜…

UGUI RectTransform的SizeDelta属性

根据已知内容&#xff0c;SizeDelta offsetMax - offsetMin 1.锚点聚拢情况下 输出 那么此时SizeDelta就是UI元素的长宽大小 2. 锚点分散时 引用自此篇文章中的描述 揭秘&#xff01;anchoredPosition的几何意义&#xff01; SizeDelta offsetMax - offsetMin (rectMax…

51单片机入门_10_数码管动态显示(数字的使用;简单动态显示;指定值的数码管动态显示)

接上篇的数码管静态显示&#xff0c;以下是接上篇介绍到的动态显示的原理。 动态显示的特点是将所有位数码管的段选线并联在一起&#xff0c;由位选线控制是哪一位数码管有效。选亮数码管采用动态扫描显示。所谓动态扫描显示即轮流向各位数码管送出字形码和相应的位选&#xff…

mybatis使用typeHandler实现类型转换

使用mybatis作为操作数据库的orm框架&#xff0c;操作基本数据类型时可以通过内置的类型处理器完成java数据类型和数据库类型的转换&#xff0c;但是对于扩展的数据类型要实现与数据库类型的转换就需要自定义类型转换器完成&#xff0c;比如某个实体类型存储到数据库&#xff0…

瑞萨RA-T系列芯片ADCGPT功能模块的配合使用

在马达或电源工程中&#xff0c;往往需要采集多路AD信号&#xff0c;且这些信号的优先级和采样时机不相同。本篇介绍在使用RA-T系列芯片建立马达或电源工程时&#xff0c;如何根据需求来设置主要功能模块ADC&GPT&#xff0c;包括采样通道打包和分组&#xff0c;GPT触发启动…

最新智能优化算法:牛优化( Ox Optimizer,OX)算法求解经典23个函数测试集,MATLAB代码

一、牛优化算法 牛优化&#xff08; OX Optimizer&#xff0c;OX&#xff09;算法由 AhmadK.AlHwaitat 与 andHussamN.Fakhouri于2024年提出&#xff0c;该算法的设计灵感来源于公牛的行为特性。公牛以其巨大的力量而闻名&#xff0c;能够承载沉重的负担并进行远距离运输。这种…

【linux】在 Linux 服务器上部署 DeepSeek-r1:70b 并通过 Windows 远程可视化使用

【linux】在 Linux 服务器上部署 DeepSeek-r1:70b 并通过 Windows 远程可视化使用 文章目录 【linux】在 Linux 服务器上部署 DeepSeek-r1:70b 并通过 Windows 远程可视化使用个人配置详情一、安装ollama二、下载deepseek版本模型三、在 Linux 服务器上配置 Ollama 以允许远程访…

【Linux网络编程】应用层协议HTTP(请求方法,状态码,重定向,cookie,session)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux网络编程 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 ​ Linux网络编程笔记&#xff1a; https://blog.cs…

Chrome多开终极形态解锁!「窗口管理工具+IP隔离插件

Web3项目多开&#xff0c;继ads指纹浏览器钱包被盗后&#xff0c;更多人采用原生chrome浏览器&#xff0c;当然对于新手&#xff0c;指纹浏览器每月成本也是一笔不小开支&#xff0c;今天逛Github发现了这样一个解决方案&#xff0c;作者开发了窗口管理工具IP隔离插件&#xff…

Canal同步MySQL增量数据

引言 在现在的系统开发中&#xff0c;为了提高查询效率 , 以及搜索的精准度, 会大量的使用 redis 、memcache 等 nosql 系统的数据库 , 以及 solr 、 elasticsearch 类似的全文检索服务。 那么这个时候, 就又有一个问题需要我们来考虑, 就是数据同步的问题, 如何将实时变化的…

MacOS 15.3 卸载系统内置软件

1、关闭系统完整性&#xff08;SIP&#xff09; 进入恢复模式(recovery) 如果您使用的是黑苹果或者白苹果&#xff0c;可以选择 重启按住CommandR 进入&#xff0c;如果是M系列芯片&#xff0c;长按开机键&#xff0c;进入硬盘选择界面进入。 我是MacMini M4芯片&#xff0c;关…

【核心算法篇七】《DeepSeek异常检测:孤立森林与AutoEncoder对比》

大家好,今天我们来深入探讨一下《DeepSeek异常检测:孤立森林与AutoEncoder对比》这篇技术博客。我们将从核心内容、原理、应用场景等多个方面进行详细解析,力求让大家对这两种异常检测方法有一个全面而深入的理解。 一、引言 在数据科学和机器学习领域,异常检测(Anomaly…

Ubuntu24.04无脑安装docker(含图例)

centos系统请看这篇 Linux安装Docker教程&#xff08;详解&#xff09; 一. ubuntu更换软件源 请看这篇&#xff1a;Ubuntu24.04更新国内源 二. docker安装 卸载老版docker(可忽略) sudo apt-get remove docker docker-engine docker.io containerd runc更新软件库 sudo a…

thingboard告警信息格式美化

原始报警json内容&#xff1a; { "severity": "CRITICAL","acknowledged": false,"cleared": false,"assigneeId": null,"startTs": 1739801102349,"endTs": 1739801102349,"ackTs": 0,&quo…

✨2.快速了解HTML5的标签类型

✨✨HTML5 的标签类型丰富多样&#xff0c;每种类型都有其独特的功能和用途&#xff0c;以下是一些常见的 HTML5 标签类型介绍&#xff1a; &#x1f98b;结构标签 &#x1faad;<html>&#xff1a;它是 HTML 文档的根标签&#xff0c;所有其他标签都包含在这个标签内&am…

day12_调度和可视化

文章目录 day12_调度和可视化一、任务调度1、开启进程2、登入UI界面3、配置租户4、创建项目5、创建工作流5.1 HiveSQL部署&#xff08;掌握&#xff09;5.2 SparkDSL部署&#xff08;掌握&#xff09;5.3 SparkSQL部署&#xff08;熟悉&#xff09;5.4 SeaTunnel部署&#xff0…

使用nvm管理node.js版本,方便vue2,vue3开发

在Vue项目开发过程中&#xff0c;我们常常会遇到同时维护Vue2和Vue3项目的情况。由于不同版本的Vue对Node.js 版本的要求有所差异&#xff0c;这就使得Node.js 版本管理成为了一个关键问题。NVM&#xff08;Node Version Manager&#xff09;作为一款强大的Node.js 版本管理工具…

Java8适配的markdown转换html工具(FlexMark)

坐标地址&#xff1a; <dependency><groupId>com.vladsch.flexmark</groupId><artifactId>flexmark-all</artifactId><version>0.60.0</version> </dependency> 工具类代码&#xff1a; import com.vladsch.flexmark.ext.tab…

Qt开发①Qt的概念+发展+优点+应用+使用

目录 1. Qt的概念和发展 1.1 Qt的概念 1.2 Qt 的发展史&#xff1a; 1.3 Qt 的版本 2. Qt 的优点和应用 2.1 Qt 的优点&#xff1a; 2.2 Qt 的应用场景 2.3 Qt 的应用案例 3. 搭建 Qt 开发环境 3.1 Qt 的开发工具 3.2 Qt SDK 的下载和安装 3.3 Qt 环境变量配置和使…