哈希桶的基本操作 || HashMap和HashSet介绍(手撕源码)

news2024/11/20 10:20:56

目录

        前言:

        哈希表介绍

        哈希冲突

        闭散列

        开散列(哈希桶)

        模拟实现哈希桶中插入和获取方法

        代码实现

         HashMap介绍

        手撕源码

        HashSet介绍

        手撕源码

         小结:


前言:

🎈一棵搜索树的最快查找数据的时间复杂度是O(logN),虽然这个速度已经是相当快了。但是又提出了,查找数据时间复杂度为O(1)的数据结构。意味着我们要找哪个数据,直接就知道这个数据在哪一块,它就是哈希表。

哈希表介绍

🪖哈希表的存储方式类似于我们之前学过的计数排序。每个数据会有自己相对应的哈希值,根据这个哈希值通过哈希函数计算其下标,存储到数组相应的位置。那么我们要找数据,只需通过哈希函数计算其下标就可在O(1)的时间复杂度内找到。

注意:如果不同的数据通过哈希函数计算到了同一个下标怎么办?

哈希冲突

🤞不同的数据通过哈希函数计算到了同一个下标,这样的情况被称为哈希冲突。解决哈希冲突常见的是用闭散列和开散列的方法。

🤞首先介绍下,负载调节因子\alpha\alpha = 填入数据个数 / 数组长度。\alpha越大冲突率就会越大,一般控制在0.7 - 0.8之间,在Java中规定不能超过0.75。如果在插入数据时\alpha超过了限定值,我们就会去减小它。由于填入数据个数是不变的,所以会考虑增加数组的长度。那么这个情况下相同的值就会被哈希到不同的位置,所以就需要将原有的数据重新哈希。只能降低冲突率不能完全避免它。

闭散列

🎈闭散列有两种方法,线性探测和二次探测。线性探测就是如果出现的哈希冲突,会在这个位置往下找到空位置,去存放这个元素。那么在删除的时候需要标记,不然会找不到其他冲突的数据。并且它会把冲突的数据集中在一块。

🎈二次探测是考虑线性探测会将冲突数据集中在一块。采用二次函数的方式找下一个空位置,Hi = (H0 + i^2)% m 或者 Hi = (H0 - i^2)% m。H0就是通过哈希函数计算的下标,i就是1,2,3,m是数组长度。这样会有效的避免冲突数据集中在一块,但同时也造成了空间浪费。

开散列(哈希桶)

😄开散列也叫做链地址法,用一个数组存储链表的头节点,每一个链表就是一个桶。如果一个数据通过哈希函数计算其下标产生了冲突,Java8之前是采用头插法插入到相应的链表中,Java8之后采用的是尾插法。如果链表长度超过8且数组长度大于64,会将其变为一颗搜索树。

模拟实现哈希桶中插入和获取方法

😣哈希桶中不能有重复的数据,如果插入了重复数据会更新其原来的val值。负载调节因子\alpha不能超过0.75,因此需在插入时检查\alpha。如果超过则需增加数组长度,但是相同的数据就会被哈希到不同的位置,因此扩容后需将原来的数据全部哈希到这个新的空间上来,然后再交给原来的指针去维护。

😣查找其中的key,只需通过哈希函数计算其下标,遍历其后面的链表或者树。

代码实现

public class MyHashList {
    static class Node {
        public int key;
        public int val;
        public Node next;
        public Node(int key, int val) {
            this.val = val;
            this.key = key;
        }
    }
    private static final int DEFAULTSIZE = 10;
    private Node[] arr;
    private int size;
    public MyHashList() {
        arr = new Node[DEFAULTSIZE];
        this.size = 0;
    }
    //扩容后,数组长度会改变,原来的值需重新Hash
    private void addCapicity() {
        Node[] tmp = new Node[arr.length * 2];
        for(int i = 0; i < this.arr.length; i++) {
            Node cur = arr[i];
            //直接将cur拿起,插入到新位置
            while(cur != null) {
                Node curNext = cur.next;
                int index = cur.key % tmp.length;
                //头插法
                cur.next = tmp[index];
                tmp[index] = cur;
                cur = curNext;
            }
        }
        this.arr = tmp;

    }
    public int put(int key, int val) {
        //遍历哈希表如果有相同key则替换其val
        for(int i = 0; i < this.arr.length; i++) {
            Node cur = arr[i];
            while(cur != null) {
                if(cur.key == key) {
                    cur.val = val;
                    return val;
                }
                cur = cur.next;
            }
        }
        //采用头插法
        //哈希值通过哈希函数计算其下表
        int index = key % arr.length;
        Node newNode = new Node(key, val);
        newNode.next = this.arr[index];
        this.arr[index] = newNode;
        this.size++;

        //负载因子大于0.75,冲突率会大,需扩容
        float ret = size * 1.0f / arr.length;
        if(ret >= 0.75) {
            addCapicity();
        }
        return val;
    }
    public int get(int key) {
        //对象,会有自己对应的哈希码
        //根据哈希码可计算出对应的哈希值
        int index = key % arr.length;
        Node cur = arr[index];
        while(cur != null) {
            if(cur.key == key) {
                return cur.val;
            }
            cur = cur.next;
        }
        return -1;
    }
    public int size() {
        return this.size;
    }
}

 HashMap介绍

HashMap底层就是哈希桶,它查找数据的时间复杂度为O(1)。数据存储是按照(key - val)键值对的方式去存储。HashMap是实现Map接口的,相对于Iterable是独立的。遍历其中的元素,需调用entrySet方法,将其(key - val)转换成Entry类型存储到set集合中,利用foreach遍历。也可以单独获取其中的key和val。

通过HashMap对象调用keySet或者values方法,将key或者val分别利用Set集合或者Collection集合组织起来。如果在其中插入相同的数据会更新其原来的val值,那么相应的key值是不可以修改的。

由于底层是哈希桶的结构,它其中的对象可以不具有比较性,因此可以入null。但是自定义类型需重写Objiect中equals和hashCode方法。

手撕源码

依次是:哈希表的默认容量,最大容量,负载调节因子,链表长度(链表树化条件),解树化条件,数组大小(链表树化条件)。

注意:树化条件是链表长度大于8且数组长度大于64。

  

注意:可以看见当调用无参构造方法时,并没有为数组开辟空间,而是在第一次put时为数组开辟默认的容量16。

 

注意:可以看见当我们指定哈希表容量后,它会计算调整比指定容量大,且最接近的二的指数。 

 

注意:当实例化对象不具有比较性的时候,会根据其哈希值进行比较来建树。

 

注意:我们在传key的时候,需重写Object类下equals和hashCode方法,不然就会调用Object的equals和hashCode方法。

HashSet介绍

🎉HashSet底层是用HashMap实现的,那么相应的底层也是哈希桶。它只存储key,其中的val都是Object对象。

🎉HashSet是实现set接口的,在Iterable下。因此可以直接利用foreach遍历。其数据不能重复,key值不可以修改,自定义类型需重写equals和hashCode方法,具有去重功能。

手撕源码

 注意:HashSet的三个构造方法都是实例化HashMap对象的。

 

注意:它的val值都是Object对象。 

 小结:

🐵我们在理解原理的同时,去查看源码,会增加我们对其的理解性。

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

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

相关文章

Java------String类和正则表达式

String在设计时被定义成了final类&#xff08;最终类&#xff09;&#xff0c;所以不能定义String类的子类&#xff0c;String用来表示字符串常量&#xff0c;是引用数据类型&#xff0c;一旦String类对象的字符序列被定义&#xff0c;那么这个字符序列的值是不能再变化的。 创…

今年十八,喜欢SQL注入

#1024程序员节&#xff5c;用代码&#xff0c;改变世界# 前言 &#x1f340;作者简介&#xff1a;被吉师散养、喜欢前端、学过后端、练过CTF、玩过DOS、不喜欢java的不知名学生。 &#x1f341;个人主页&#xff1a;被吉师散养的职业混子 &#x1fad2;每日emo&#xff1a;怀揣…

牛客竞赛每日俩题 - Day5

DP贪心 年终奖_牛客题霸_牛客网 思路&#xff1a; 定义f(i,j)表示从左上角走到坐标(i&#xff0c;j)处能获得的最大奖励。 搜索所有从左上角走到右下角的路径&#xff0c;找到最优路径。 f(i,j)分三种情况&#xff1a; 第一列&#xff1a;f(i, 0) f(i-1, 0) board(i, 0) 如果…

Java:企业应该用Java构建供应链软件吗?

很多公司使用Java编程语言&#xff0c;Java是最流行的编程语言之一&#xff0c;它帮助开发人员简化复杂企业解决方案的应用程序开发过程。 供应链是零售和制造业的重要组成部分&#xff0c;6%的零售业公司使用Java&#xff0c;另一方面&#xff0c;4%的制造公司使用编程语言&am…

基于莱维飞行扰动策略的麻雀搜索算法-附代码

基于莱维飞行扰动策略的麻雀搜索算法 文章目录基于莱维飞行扰动策略的麻雀搜索算法1.麻雀优化算法2. 改进麻雀算法2.1 莱维飞行搜索机制2.2 莱维飞行扰动策略2.3 初始化混沌搜索机制3.实验结果4.参考文献5.Matlab代码6.Python代码摘要&#xff1a;为了解决麻雀搜索算法存在迭代…

(02)Cartographer源码无死角解析-(04) 熟悉bag文件,配置.launch与.lua文件,初步调参

本人讲解关于slam一系列文章汇总链接:史上最全slam从零开始&#xff0c;针对于本栏目讲解(02)Cartographer源码无死角解析-接如下: (02)Cartographer源码无死角解析- (00)目录_最新无死角讲解&#xff1a;https://blog.csdn.net/weixin_43013761/article/details/127350885 文…

算法 | 详解斐波那契数列问题

14天阅读挑战赛 本篇是学习了《趣学算法&#xff08;第2版&#xff09;》 第一章之后总结的。 上一篇讲到了等比数列求和问题&#xff0c;求Sn122223...263&#xff1f;S_n 1 2 2^2 2^3 ... 2^{63} &#xff1f;Sn​122223...263&#xff1f;&#xff0c;该函数属于爆…

1024,我们节日

文章目录1024的前奏1024应该锻炼身体1024 &#xff1f;为何是1024其他的1024代码有Bug&#xff0c;世界却温暖特殊的一类人群&#xff08;一&#xff09;特殊的一类人群&#xff08;二&#xff09;写在最后1024的前奏 写博文的时间是23号&#xff0c;距离1024这个标志性的节日…

趣学算法14天阅读|Day2

14天阅读挑战赛 文章目录前言什么是算法&#xff1f;算法复杂度如何评定好算法案例案例一&#xff1a;棋盘的麦子案例二&#xff1a;兔子数列总结前言 &#x1f4eb; 作者简介&#xff1a;「六月暴雪飞梨花」&#xff0c;专注于研究Java&#xff0c;就职于科技型公司后端中级工…

@AutoConfigurationPackage注解详解

AutoConfigurationPackage是在springboot启动类注解SpringBootApplication下的EnableAutoConfiguration下。AutoConfigurationPackage作用是指定springboot扫描包&#xff0c;默认就是扫描启动类同包下的类。可以通过AutoConfigurationPackage来附加其他路径&#xff0c;然后sp…

asp.net园林绿化服务交易网站

本设计的目的是通过对园林绿化的初步了解&#xff0c;在巩固网站设计所学知识的基础上。运用于实践&#xff0c;在实践中强化理论知识&#xff0c;做到理论联系实践。在设计中学会搜集资料地方法&#xff0c;学会查阅设计规程。掌握设计步骤&#xff0c;同时培养创新能力&#…

【图像评价】基于matlab GUI图像评价系统【含Matlab源码 2185期】

一、图像质量评价 图像质量评价对图像处理算法的可行性分析以及某类算法达成效果的比较都起着非常重要的指导作用。随着图像处理技术的发展&#xff0c;对于图像质量评价的研究也日益深入&#xff0c;新的评价指标和评价算法不断涌现&#xff0c;共同推动了图像质量评价的进步…

【模型训练】YOLOv7旋翼无人机检测

YOLOv7旋翼无人机检测 1、YOLOv7算法旋翼无人机检测模型训练2、YOLOv7模型模型评估3、模型和数据集下载1、本项目采用YOLOv7算法实现对空中旋翼无人机的检测,在一万多张旋翼无人机检测数据集中训练得到,我们训练了YOLOv7模型,经评估我们得出了各个模型的评价指标; 2、目标类…

研发效能工程实践开篇

背景 随着技术的发展&#xff0c;大至一线互联网公司、小至几十人研发团队的公司都开始开展devops运动。大公司有能力自研&#xff0c;小公司则采用开源解决方案。如何实施devops业界已经有非常成熟的方案&#xff0c;所以本系列不再谈devops工具链以及如何实施。本系列想谈谈…

初识C++ - 类与对象(中篇·下半)

目录 赋值运算符重载 以下是一个日期类的运算符重载的案例(重点) 关于流插入与流提取的使用 方法一&#xff1a;定义与声明分离 方法二&#xff1a;使用内联函数 const成员 概念 关于上述日期类代码为什么需要在函数后面加入const 取地址及const取地址操作符重载 …

5G核心网技术基础自学系列 | 网络切片

书籍来源&#xff1a;《5G核心网 赋能数字化时代》 一边学习一边整理内容&#xff0c;并与大家分享&#xff0c;侵权即删&#xff0c;谢谢支持&#xff01; 附上汇总贴&#xff1a;5G核心网技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 支持网络切片是设计5G架构的主要驱…

【牛客刷题--SQL篇】必会的常用函数之条件函数SQL26计算25岁以上和以下的用户数量(多种写法)

&#x1f496;个人主页&#xff1a;与自己作战 &#x1f4af;作者简介&#xff1a;CSDN博客专家、CSDN大数据领域优质创作者、CSDN内容合伙人、阿里云专家博主 &#x1f49e;牛客刷题系列篇&#xff1a;【SQL篇】】【Python篇】【Java篇】 &#x1f4cc;推荐刷题网站注册地址&a…

基于WEB的二维码生成系统设计与实现(Asp.net)

目 录 1 引言 6 2 二维码的生成原理 6 2.1基础知识 6 2.1.1定位图案 7 2.1.2功能性数据 8 2.1.3数据码和纠错码 8 2.2数据编码 8 2.3结束符和补齐符 9 2.3.1补齐码&#xff08;Padding Bytes&#xff09; 9 2.3.2纠错码 10 2.4最终编码 11 2.4.1穿插放置 11 2.4.2Remainder Bi…

JWT基础概念

1. 介绍 JWT &#xff08;JSON Web Token&#xff09; 是目前最流行的跨域认证解决方案&#xff0c;是一种基于 Token 的认证授权机制。 从 JWT 的全称可以看出&#xff0c;JWT 本身也是 Token&#xff0c;一种规范化之后的 JSON 结构的 Token。 JWT 自身包含了身份验证所需要…

【正点原子I.MX6U-MINI应用篇】6、嵌入式Linux在LCD屏幕上显示字符

一、原始方式&#xff1a; 取模显示字符 LCD显示屏是由width * height个像素点构成的&#xff0c;显示字符&#xff0c;一个非常容易想到的方法便是对字符取模&#xff0c;然后在LCD屏上打点显示字符&#xff1b;如果大家以前学习过单片机&#xff0c;想必接触过一些显示屏&am…