模拟实现哈希表 - HashMap(Java版本)

目录

1. 概念

2. 冲突-概念

3. 冲突-避免

4. 冲突-避免-哈希函数设计

5. 冲突-避免-负载因子调节 ⭐⭐⭐⭐⭐

6. 冲突-解决

6.1 冲突-解决-闭散列

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

7. 冲突严重时的解决办法

8. 模拟实现


1. 概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(logN),搜索的效率取决于搜索过程中元素的比较次数

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

当向该结构中:

  • 插入元素:根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。
  • 搜索元素:对位元素的关键码进行同样的计算,把求得的函数值当做元素的存储置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。

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

例如:数据集合{1,7,6,4,5,9};

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

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快

问题:按照上述哈希方式,向集合中插入元素44,会出现什么问题? 

2. 冲突-概念

对于两个数据元素的关键字 和 (i != j),有 != ,但有:Hash( ) == Hash( ),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”

3. 冲突-避免

首先,我们需要明确一点,由于我们哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致一个问题,冲突的发生是必然的,但我们能做的应该是尽量的降低冲突率。

4. 冲突-避免-哈希函数设计

引起哈希冲突的一个原因可能是:哈希函数设计不够合理。

哈希函数设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间。
  • 哈希函数计算出来的地址能均匀分布在整个空间中。
  • 哈希函数应该比较简单。

常见哈希函数

(1)直接定制法--(常用)

取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况。

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

面试题:字符串的第一个唯一字符

(2)除留余数法--(常用)

设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。 

(3)其他了解即可的设计哈希函数的方法:平方取中法、平方取中法、随机数法、数学分析法。

5. 冲突-避免-负载因子调节 ⭐⭐⭐⭐⭐

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

问:所以当冲突率达到一个无法忍受的程度时,我们需要通过降低负载因子来变相的降低冲突率,那么如何实现呢?

答:已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小。

6. 冲突-解决

解决哈希冲突两种常见的方法是:闭散列和开散列

6.1 冲突-解决-闭散列

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

(1)线性探测

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

插入:

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

  • 采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。 

(2)二次探测

线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为: = ( +i )% m, 或者:= ( - i)% m。其中:i = 1,2,3…, 是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小。

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

因此:闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。

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

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

开散列的方法也是我们等下模仿实现哈希表,解决冲突的方法。

从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。
开散列,可以认为是把一个在大集合中的搜索问题转化为在小集合中做搜索了 。

7. 冲突严重时的解决办法

刚才我们提到了,哈希桶其实可以看作将大集合的搜索问题转化为小集合的搜索问题了,那如果冲突严重,就意味着小集合的搜索性能其实也时不佳的,这个时候我们就可以将这个所谓的小集合搜索问题继续进行转化,例如:

  • 每个桶的背后是另一个哈希表
  • 每个桶的背后是一棵搜索树

8. 模拟实现

  • 第一步:构建里面的键值对关系的结点
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;
        }
    }
}
  • 第二步:确认负载因子(0.75)、默认的数组长度、当前加入的键值对有多少个
    //默认创建一个数组,用来存储节点
    public Node[] array = new Node[10];
    //记录当前有多少个元素的变量 useSize
    public int useSize;
    //负载因子
    public static final double LOAD_FACTOR = 0.75;
  • 第三步:模拟实现put方法,以及服务于put方法的其他方法
    /*
    存入一个键值对,解决冲突使用开散列或者说哈希桶
     */
    public void push(int key, int val) {
        Node node = new Node(key, val);
        //1.找到位置(在数组中的下标)
        int index = key % array.length;
        //2.遍历数组,判断是不是有相同key值的结点已经在桶里了
        Node cur = array[index];
        while (cur != null) {
            if (cur.key == key) {
                //如果有key值相同的结点,把它的val值更新一下,return
                cur.val = val;
                return;
            }
            cur = cur.next;
        }
        //3.如果没有相同的结点,则把结点的存入哈希桶中,使用头插法
        node.next = array[index];
        array[index] = node;
        useSize++;
        //4.判断是否超出负载因子的限制,如果超过,需要重写哈希
        if (doLoadFactor() > 0.75) {
            //重写哈希
            reSize();
        }
    }

    //重写哈希
    private void reSize() {
        Node[] newArray = new Node[array.length * 2];
        //处理重写哈希
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            while(cur != null){
                //记录在新的哈希表的位置
                int index = cur.key % newArray.length;
                //记录下来之前的cur.next
                Node curNext = cur.next;
                //进行头插法,插入到新数组中
                cur.next = newArray[index];
                newArray[index] = cur;
                cur = curNext;
            }
        }
        array = newArray;
    }

    //计算负载因子
    private double doLoadFactor() {
        return useSize * 1.0 / array.length;
    }
  • 第四步:模拟实现get方法
    public int get(int key){
        //1.找到位置
        int index = key % array.length;
        //2.遍历数组
        Node cur = array[index];
        while(cur != null){
            if(cur.key == key){
                return cur.val;
            }
            cur = cur.next;
        }
        return -1;
    }

开始容量只有7,接下来就要扩容了,再加入一个元素,8 / 10 > 0.75了,就需要扩容了,我们来看看会发生什么!

此时数组就会发生增容到原来的两倍,然后14加入进来,根据哈希函数被安置下标14的位置。

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

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

相关文章

蓝桥杯---牌型种数

小明被劫持到X赌城&#xff0c;被迫与其他3人玩牌。一副扑克牌(去掉大小王牌,共52张)&#xff0c;均匀发给4个人&#xff0c;每个人13张。这时&#xff0c;小明脑子里突然冒出一个问题&#xff1a;如果不考虑花色&#xff0c;只考虑点数&#xff0c;也不考虑自己得到的牌的先后…

HTM标签 - 2

HTM标签 超链接标签 超链接标签&#xff1a;<a> 文本或图片 </a> 用法1&#xff1a;在页面中使用超链接标签跳转到另一个页面 属性描述href页面跳转的地址&#xff0c;相对地址或绝对地址&#xff1b;###&#xff1a;空连接&#xff1b;#&#xff1a;跳转到当前…

vue3+threejs+koa可视化项目——实现登录注册(第三步)

文章目录 ⭐前言&#x1f496;往期node系列文章&#x1f496;threejs系列相关文章&#x1f496;vue3threejs系列 ⭐koa后端登录注册逻辑&#xff08;jwt&#xff09;&#x1f496; koa登录注册 ⭐vue3前端登录注册权限控制&#x1f496; 登录页面&#x1f496; 注册页面 ⭐总结…

AcWing.883.高斯消元解线性方程组

输入一个包含 n 个方程 n 个未知数的线性方程组。 方程组中的系数为实数。 求解这个方程组。 下图为一个包含 m 个方程 n 个未知数的线性方程组示例&#xff1a; 输入格式 第一行包含整数 n n n。 接下来 n n n 行&#xff0c;每行包含 n 1 n1 n1 个实数&#xff0c;表…

01背包问题 动态规划

01背包问题 动态规划 01背包问题 动态规划写了点代码 C#实现程序运行结果代码和程序已经上传 01背包问题 动态规划 很有意思的问题。 写了点代码 C#实现 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Ta…

二进制漏洞挖掘之ret2text栈溢出

栈溢出产生的主要原因是对一些边界未进行严格检查&#xff0c;攻击者可以通过覆盖函数的返回地址执行任意代码。栈溢出漏洞主要的利用方式是ROP&#xff08;Return Oriented Programming&#xff0c;返回导向编程&#xff09;&#xff0c;通过覆盖返回地址&#xff0c;使程序跳…

【01】Linux 基本操作指令

带⭐的为重要指令 &#x1f308; 01、ls 展示当前目录下所有文件&#x1f308; 02、pwd 显示用户当前所在路径&#x1f308; 03、cd 进入指定目录&#x1f308; 04、touch 新建文件&#x1f308; 05、tree 以树形结构展示所有文件⭐ 06、mkdir 新建目录⭐ 07、rmdir 删除目录⭐…

C++进阶(八)红黑树

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、红黑树的概念二、红黑树的性质三、红黑树结构四、红黑树的插入操作1、情况一2、情况二3、…

vue3开发,axios发送请求是携带params参数的避坑

vue3开发,axios发送请求是携带params参数的避坑&#xff01;今天一直报错&#xff0c;点击新增购物车&#xff0c;报错&#xff0c; 【Uncaught (in promise) TypeError: target must be an object】。查询了网上的资料说的都不对。都没有解决。最终还是被我整明白了。 网上网…

力扣之2621.睡眠函数

/*** param {number} millis* return {Promise}*/ async function sleep(millis) {return new Promise(resolve > setTimeout(resolve, millis)); }/** * let t Date.now()* sleep(100).then(() > console.log(Date.now() - t)) // 100*/ 这样的异步休眠功能在实际应用…

页面通过Vue进行整体页面不同语言切换 i18n库

目录 引入 如何做到 下载i18n库 构建整体翻译文件结构 语言包文件 i18n配置文件 把i18n挂载到vue实例上 添加按钮点击事件切换语言 引入 我们现在有这样一个要求,我们想要对我们开发的网页进行国际化操作,也就是我们不仅要有中文,还要有英文等。用户可以随时进行不同语言…

DB之家:数据库开发工程师的衣柜(云原生时代数据库性能优化点子集合)

基础数据结构 布隆过滤器&#xff1a; modular bloom filter 减少布隆过滤器所需要的内存。参考文献&#xff1a;Mun, J. H., Zhu, Z., Raman, A., & Athanassoulis, M. (n.d.). LSM-Trees Under (Memory) Pressure. 基础算法 字符串压缩 FSST算法 利用向量化计算加…

多线程代码案例之单例模式

作者简介&#xff1a; zoro-1&#xff0c;目前大二&#xff0c;正在学习Java&#xff0c;数据结构&#xff0c;javaee等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; 多线程代码案例之单例模式 单例…

【Node-RED】node-red-contrib-opcua-server模块使用(3)

【Node-RED】node-red-contrib-opcua-server模块使用&#xff08;3&#xff09; 前言node-red-contrib-iiot-opcuanode-red-contrib-lativnode-red-contrib-nupmes 前言 在前面博文【Node-RED】node-red-contrib-opcua-server模块使用&#xff08;1&#xff09;我们有提及过&a…

ICMPv6报文解析及NAT处理

ICMPv6报文概述 参考RFC4443和RFC2460 ICMPv6报文是IPv6在internal control management protocol&#xff08;ICMP&#xff09;的基础之上做了一些改动&#xff0c;得到了ICMPv6协议&#xff0c;IPv6的next_header为58。 Message general format 每条ICMPv6消息之前都有一个…

lombok导致的IndexOutOfBoundsException

一、问题描述 ERROR 25152 --- [1.190-81-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.mybatis.spring.MyBatisSyste…

MacBook安装虚拟机VMware Fusion

MacBook安装虚拟机VMware Fusion 官方下载地址: https://customerconnect.vmware.com/cn/downloads/info/slug/desktop_end_user_computing/vmware_fusion/11_0 介绍 之前的版本都要收费,现在出了对个人免费的版本, 棋哥给的破解版的版本是8,升级系统后用不了了. 官方去下载…

thinkadmin操作栏审核通过(操作确认),审核驳回(录入信息)

录入信息页面 {extend name="../../admin/view/main"}{block name=content} <style>textarea {font-size: 16px;padding: 10px;border: 1px solid #ccc;

EtherNET主站转Profinet网关实现EtherNET协议和Profinet协议相互转换

北京兴达易控EtherNET主站转Profinet网关是一种能将EtherNET协议和Profinet协议相互转换的设备&#xff0c;将网络通信技术与工业自动化技术完美结合。它不仅简化了通信的复杂度&#xff0c;而且提高了系统的可靠性和稳定性。 作为一个EtherNET主站转Profinet网关&#xff0c;它…

Python实现avif图片转jpg格式并识别图片中的文字

文章目录 一、图片识别文字1、导包2、代码实现3、运行效果 二、avif格式图片转jpg格式1、导包2、代码实现3、运行效果4、注意事项 三、Python实现avif图片转jpg格式并识别文字全部代码 在做数据分析的时候有些数据是从图片上去获取的&#xff0c;这就需要去识别图片上的文字。P…
最新文章