哈希表【数据结构】

news2024/11/15 21:44:57

文章目录

    • 哈希表
      • 概念
        • 插入元素
        • 搜索元素
      • 结构
      • 冲突
        • 概念
      • 冲突-避免
        • 哈希函数设计
          • 常见哈希函数
        • 调节负载因子
          • 负载因子定义
          • 负载因子和冲突率的关系
      • 冲突解决
        • 冲突-解决-闭散列
          • 线性探测
            • 过程
            • 缺点
          • 二次探测
            • 概念
            • 缺点
        • 冲突-解决-开散列/哈希桶
          • 概念
          • 结构
          • 代码实现哈希桶
          • hashcode 和 equals
      • 问题
      • 面试题:HashMap和concurrentHashMap的区别

哈希表

概念

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

插入元素

根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放

搜索元素

对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

以上方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(HashTable)(或者称散列表)

结构

在这里插入图片描述

冲突

概念

哈希冲突:两个不同关键字key通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突哈希碰撞

冲突-避免

哈希函数设计

常见哈希函数
  1. 直接定制法(常用)
    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
    优点:简单、均匀 缺点:需要事先知道关键字的分布情况
    使用场景:适合查找比较小且连续的情况

调节负载因子

负载因子定义

负载因子和冲突率的关系

在这里插入图片描述
由图不难发现,只有降低负载因子,才能降低冲突率。那么想要降低冲突率,只能增加散列表长度。

冲突解决

冲突-解决-闭散列

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个”空位置中去。

线性探测

从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

过程
  1. 通过哈希函数获取待插入元素在哈希表中的位置
  2. 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。

如下,44与4发生哈希冲突,则向后找到哈希表中下一个空位置为下标8处。
在这里插入图片描述

缺点

通过以上概念及过程可知,线性探测法存在以下不足:

  1. 不好删除
    采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若**直接删除元素会影响其他元素的搜索**。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。
  2. 冲突元素分布紧密。由于每次冲突发生都是向后 找空位置(遇到后就存进去),此举会使得 尽量冲突的元素都放在了一起
二次探测
概念

为了避免 线性探测冲突数据堆积的问题,二次探测提出找下一个空位置的方法

在这里插入图片描述
其中:H0 是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小。

  • 表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。
  • 在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。
缺点

空间利用率低

冲突-解决-开散列/哈希桶

数组+链表+红黑树

概念

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

结构

在这里插入图片描述

代码实现哈希桶
public class HashBuck {
    static class Node {
        public int key;
        public int val;
        public Node next;

        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
    public Node[] array;

    public int usedSize;//存放的数据的个数

    public static final double DEFAULT_LOAD_FACTOR = 0.75;//默认的负载因子

    public HashBuck () {
        array  = new Node[8];
    }

    /**
     *
     * @param key
     * @param val
     * @return 代表你插入的元素的val
     */
    public boolean put(int key,int val) {

        Node node = new Node(key,val);
        //1、位置
        int index = key % array.length;

        //2、遍历这个下标的链表
        Node cur = array[index];//就是一个链表的头节点
        while (cur != null) {
            if(cur.key == key) {
                cur.val = val;//更新val值
                return false;
            }
            cur = cur.next;
        }
        //3、遍历完成了当前下标的链表,开始进行插入
        node.next = array[index];
        array[index] = node;
        this.usedSize++;
        //4、存放元素之后,判断当前哈希桶当中的负载因子 是否超过了默认的负载因子
        if(loadFactor() >= DEFAULT_LOAD_FACTOR) {
            //5、扩容
            resize();
        }
        return true;
    }

    /**
     * 扩容的同时 要进行重新哈希
     */
    private void resize() {
        //1、重新申请2倍的数组
        Node[] tmp = new Node[array.length*2];
        //2、遍历原来的数组,把每个下标的链表的节点,都重新进行哈希
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            while (cur != null) {
                int newIndex = cur.key % tmp.length;//新的数组的下标
                Node curNext = cur.next;//先要记录下来下一个
                cur.next = tmp[newIndex];
                tmp[newIndex] = cur;
                cur = curNext;//这里注意
            }
        }
        array = tmp;
    }

    public int get(int key) {
        //1、确定位置
        int index = key % array.length;
        Node cur = array[index];
        while (cur != null) {
            if(cur.key == key) {
                return cur.val;
            }
            cur = cur.next;
        }
        return -1;
    }

    /**
     * 计算当前哈希桶当中的负载因子
     * @return
     */
    private double loadFactor() {
        return this.usedSize*1.0 / this.array.length;
    }

}
public class TestDemo {
    public static void main2(String[] args) {
        HashBuck hashBuck = new HashBuck();
        hashBuck.put(1,1);
        hashBuck.put(2,2);
        hashBuck.put(3,3);
        hashBuck.put(6,6);
        hashBuck.put(14,14);// 14
        hashBuck.put(24,44);// 8
        System.out.println("hello");
    }
}

注意

哈希表扩容需注意:需要进行重新哈希!!!

如果哈希桶中存放引用类型

重写hashCode()和equals()方法

class Person {
    public String id;

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

    @Override
    public String toString() {
        return "Person{" +
                "id='" + id + '\'' +
                '}';
    }

    /**
     * 在下标底下 找哪个节点的key和我是一样的
     * @param o
     * @return
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(id, person.id);
    }

    /**
     * 找节点的小标的
     * @return
     */
    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}
public class TestDemo {


    public static void main(String[] args) {
        HashBuck2<Person,String> map = new HashBuck2<>();
        Person person1 = new Person("123456");
        Person person2 = new Person("123456");
        map.put(person1,"xyy");

        System.out.println(map.get(person2));//xyy
    }

    public static void main3(String[] args) {
        HashMap<Person,String> map = new HashMap<>();
        Person person1 = new Person("123456");
        Person person2 = new Person("123456");

        System.out.println(person1.hashCode());//找位置
        System.out.println(person2.hashCode());

        map.put(person1,"xyy");

        System.out.println(map.get(person2));
    }
}

HashMap 第一次put元素时,分配内存。

hashcode 和 equals

1、hashcode一样equals一定一样吗?不一定
2、equals一样hashcode一定一样吗?一定

HashCode简介 ——参考HashCode()和equals()的区别

hashCode()方法的作用是获取哈希码,返回的是一个int整数

Object类中的hashCode()方法定义如下

public native int hashCode();

哈希码的作用是确定对象在哈希表的索引下标。比如HashSet和HashMap就是使用了hashCode方法确定索引下标。如果两个对象返回的hashCode相同,就被称为“哈希冲突”。

equals简介

equals()方法的作用很简单,就是判断两个对象是否相等,equals()方法是定义在Object类中,而所有的类的父类都是Object,所以如果不重写equals方法则会调用Object类的equals方法。

Object类中的equals()方法定义如下

public boolean equals(Object obj) {    
	return (this == obj);
}

在equals()方法中的==,那么在Java中有什么含义呢,

我们都知道在Java中分为基本数据类型和引用数据类型。那么==在这两个类型中作用是不一样的。

基本数据类型:比较的是 两边值是否相等 -》==
引用数据类型: 比较的是 两边内存地址是否相等-》equals()

问题

1.如果new HashMap(19),bucket数组多大? 32,数组长度一定要是接近2的n次幂
2.HashMap什么时候开辟bucket数组占用内存? 默认的构造方法,不带参数的第一次put的时候,大小为16
3.hashMap何时扩容? 超过了负载因子
4.当两个对象的hashcode相同会发生什么? 碰撞(哈希冲突)
5.如果两个键的hashcode相同,你如何获取值对象? 遍历与hashCode值相等时相连的链表,直到相等(通过equals()判断)或者null
6.你了解重新调整HashMap大小存在什么问题吗? 必须重新哈希

面试题:HashMap和concurrentHashMap的区别

看到其他博主一篇较好的总结:HashMap和concurrentHashMap的区别
之后自己进一步学习后,会将此部分整理更新。

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

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

相关文章

Metabase学习教程:视图-2

线型图指南 当我们谈论线型图时&#xff0c;我们谈论的多数是&#xff1a;时间序列、趋势线、警报等等。 线型图对于绘制在序列中捕获的数据非常有用&#xff0c;无论该序列是时间的流逝&#xff0c;还是流程或流中的步骤。这些图表通常用于绘制时间序列&#xff08;也称为运…

语义分割实战:基于tensorflow搭建DeeplabV3实现语义分割任务

任务描述: 语义分割是一种典型的计算机视觉问题,其是将一些图像作为输入并将它们转换为具有突出显示的感兴趣区域的掩模,即图像中的每个像素根据其所属的感兴趣对象被分配类别。如下图中左图所示,其语义是人骑自行车,语义分割的结果如右图所示,粉红色代表人,绿色代表自行…

ssm基于安卓android的失物招领APP-计算机毕业设计

技术介绍 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 安卓框架&#x…

Qt MainWindow窗口部件简介

Qt MainWindow窗口部件简介 1、菜单栏 特性如下&#xff1a; 有且仅有一个**位置&#xff1a;**顶部 // 创建菜单 最多只能有一个 QMenuBar * bar menuBar(); // 将菜单栏放入到窗口处 setMenuBar(bar);// 创建顶部菜单 QMenu * fileMenu bar->addMenu("文件&quo…

用DIV+CSS技术制作一个简单的网页 我的家乡主题

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

redis三(3-2)

传统缓存的问题 传统的缓存策略一般是请求到达Tomcat后&#xff0c;先查询Redis&#xff0c;如果未命中则查询数据库&#xff0c;存在下面的问题&#xff1a; 请求要经过Tomcat处理&#xff0c;Tomcat的性能成为整个系统的瓶颈Redis缓存失效时&#xff0c;会对数据库产生冲击…

【Java八股文总结】之读写分离分库分表

文章目录读写分离&分库分表一、读写分离1、何为读写分离2、读写分离会带来什么问题&#xff1f;如何解决&#xff1f;3、如何实现读写分离&#xff1f;4、主从复制原理二、分库分表1、为什么要进行分库分表&#xff1f;2、何为分库&#xff1f;3、何为分表&#xff1f;★4、…

Github+Markdown(1)

报错配置 如果报错如下&#xff1a;Failed to connect to github.com port 443: Timed out 解决方案&#xff1a; 在C:\Users\m00585487\.gitconfig文件中&#xff0c;添加如下内容 [http "https://github.com"] proxy http://m00585487:J!f42022proxyhk.huawei…

基础SSM框架搭建

SSM框架一、注入依赖二、配置web.xml三、springmvc-common.xml配置四、mybatis-config.xml配置五、log4j.properties日志文件配置六、jdbc.properties连接信息七、applicationContext.xml配置八、UserDao.xml案例九、UserService接口十、UserServiceImpl实现十一、MyConverter十…

【表白程序】盛开的玫瑰代码

我挥舞着键盘和本子&#xff0c;发誓要把世界写个明明白白。 今天带来的是盛开的玫瑰&#xff0c;希望大家喜欢&#xff01; 简介 HTML5 SVG线条玫瑰花动画特效是一款基于svg绘制卡通玫瑰花动画&#xff0c;先用线条勾画出花的现状&#xff0c;在生成颜色过程特效。 利用所学…

如何提取图片中文字?安利这几个图片转文字提取的方法

在我们工作学习中&#xff0c;有没有遇到过需要将图片中的文字信息给记录下来的情况&#xff0c;一般这种时候你是怎么做的呢&#xff1f;是根据图片手动输入吗&#xff1f;如果是在文字少量的情况下&#xff0c;可以这样操作&#xff0c;可是如果文字较多的话&#xff0c;手动…

iOS 16.1新功能尝鲜:如何在iPhone上启用实时活动?

近日&#xff0c;苹果发布了iOS 16.1正式版&#xff0c;在本次更新中&#xff0c;苹果推出了全新“实时活动”功能&#xff0c;用户能在iPhone锁定屏幕上查看到更多信息&#xff0c;如果是iPhone 14 Pro机型&#xff0c;实时活动信息还将在灵动岛同步显示。 那么&#xff0c;i…

用HTML+CSS+JS做一个漂亮简单的游戏网页——全屏游戏美术大赛作品(4个滚动页面)

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

[论文阅读] Adversarial Learning for Semi-Supervised Semantic Segmentation

[论文地址] [代码] [BMVC 18] Abstract 我们提出了一种使用对抗性网络进行半监督性语义分割的方法。虽然大多数现有的判别器都是在图像层面上对输入图像进行真假分类的训练&#xff0c;但我们以完全卷积的方式设计了一个判别器&#xff0c;以区分预测的概率图和考虑到空间分辨…

盘点一下分布式模式下的服务治理和监控优化方案

什么是服务治理&#xff1f; 相信每一个软件公司&#xff08;企业&#xff09;都希望可以确保开发及项目运行流程可以顺利&#xff0c;但是如果要完美完结那么需要其中会有很多的因素存在。包括&#xff0c;最佳实践、架构原则、服务治理以及其他决定性的因素。而其中服务治理…

网络入侵检测 Network Intrusion Detection System (NIDS)

网络入侵检测 Network Intrusion Detection System--NIDS网络入侵检测 Network Intrusion Detection System (NIDS)1.学习内容2.数据集说明3.NIDS组件4.基于SDN的网络入侵检测5.实验步骤下载数据集下载代码配置环境结构目录运行程序训练结果6.总结参考论文数据集申明&#xff1…

JVM垃圾回收——CMS垃圾收集器

目录 一、什么是CMS垃圾收集器 二、CMS垃圾收集的过程 三、CMS收集器的不足 四、CMS收集器的参数配置 一、什么是CMS垃圾收集器 虽然HotSpot虚拟机已经在jdk14中移除了CMS垃圾收集的参数&#xff0c;但是考虑到还有很多开发是基于jdk8开发的&#xff0c;所以还是有必要了解…

leetcode17. 电话号码的字母组合

文章目录题目思考代码和注释总结题目 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 来源&#xff1a;力扣&#xff08;…

【计算机毕业设计】5.网上书店系统maven源码

一、系统截图&#xff08;需要演示视频可以私聊&#xff09; 摘要 随着Internet的发展&#xff0c;人们的日常生活已经离不开网络。未来人们的生活与工作将变得越来越数字化、网络化和电子化。网上销售&#xff0c;它将是直接市场营销的最新形式。本论文是以构建网上书店系统为…

善网ESG周报(第一期)

ESG报告&#xff1a; 诺基亚最新ESG报告已出炉 报告显示&#xff0c;诺基亚的ESG战略着重于环境、工业数字化、安全和隐私、缩小数字鸿沟、以及企业责任。 Lazada 发布首份ESG报告&#xff1a;为东南亚六国创造约110万经济机会 报告提出&#xff0c;Lazada在区域赋能方面、…