【数据结构】 | java中 哈希表及其冲突解决

这篇具有很好参考价值的文章主要介绍了【数据结构】 | java中 哈希表及其冲突解决。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🎗️ 博客新人,希望大家一起加油进步
🎗️ 乾坤未定,你我皆黑马

1、哈希表概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(LogN), 搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:

  • 插入元素

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

  • 搜索元素

对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功
该方式即为哈希(散列)方法, 哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(HashTable)(或者称散列表)
例如:数据集合{1,7,6,4,5,9};
哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。
比如:
冲突及处理的概念数据结构,java 数据结构,数据结构,java,散列表,哈希表,哈希
引出冲突: 用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快, 问题:按照上述哈希方式,向集合中插入元素44,会出现什么问题?

2、冲突 - 概念

对于两个数据元素的关键字不同,经过一个哈希函数之后,两者的存储位置下标可能是相同的,即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。 把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”

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

3、冲突 - 避免 -哈希函数设计

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

哈希函数设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间

  • 哈希函数计算出来的地址能均匀分布在整个空间中

  • 哈希函数应该比较简单

常见哈希函数

1. 直接定制法–(常用)
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B ,优点:简单、均匀 缺点:需要事先知道关键字的分布情况,使用场景:适合查找比较小且连续的情况。
2. 除留余数法–(常用)
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。

还有其他不太常用的方法如:平方取中法,折叠法,随机数法,数学分析法

  • 注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

4、冲突 - 避免 -负载因子调节

散列表的载荷因子定义为: α =填入表中的元素个数 / 散列表的长度

α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,表明填入表中的元素越多,产生冲突的可能性就越大 ; 反之,α越小,标明填入表中的元素越少,产生冲突的可能性就越小。

实际上,散列表的平均查找长度是载荷因子α的函数,只是不同处理冲突的方法有不同的函数。
负载因子和冲突率的关系粗略演示:
冲突及处理的概念数据结构,java 数据结构,数据结构,java,散列表,哈希表,哈希
在java的HashMap类中,HashMap的底层就是哈希表,在jdk8中,默认的负载因子为0.75。

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

5、冲突 - 解决

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

5.1 闭散列

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。 那如何寻找下一个空位置呢?
1. 线性探测
比如上面的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,下标为4,因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。

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

  • 插入
    通过哈希函数获取待插入元素在哈希表中的位置如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。
    冲突及处理的概念数据结构,java 数据结构,数据结构,java,散列表,哈希表,哈希
  • 采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素, 若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标 记的伪删除法来删除一个元素。
    另外还有二次探测的方法用于查找下次的空位置,在此我们不做过多的介绍。
5.2 开散列

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
如图所示:
冲突及处理的概念数据结构,java 数据结构,数据结构,java,散列表,哈希表,哈希
从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

开散列,可以认为是把一个在大集合中的搜索问题转化为在小集合中做搜索了。

  • 冲突严重时的解决办法

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

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

6、哈希表的模拟实现

哈希表的底层其实是一个数组,为了有效解决哈希冲突的问题,使用开散列的方式进行冲突的解决,也就是使用链表数组来进行元素的储存。

然后就是主要实现哈希表的插入和查找操作。

哈希函数,由于我们模拟的是整数类型数据的哈希表,所以直接对key取数组长度的模即可。即:hash(key)=key%len。

设置的负载因子为0.75,

  • 插入操作

思路:

1. 通过哈希函数计算出所插入到数组的下标。
2. 判断下标所对应的链表中是否包含该数据,如果有,则进行更新value值(因为哈希表中不会放重复的元素),若没有,则采用尾插或者头插的方式进行插入数据,本文以头插的方式进行。
3. 期间还有进行判断是否扩容。

注意事项: 扩容之后是一个新的数组,把原来数组数据转移到新数组中去,此时由于数组长度的改变,由哈希函数新计算出的下标也会发生改变。

  • 代码实现:
//模拟实现一个哈希  : 数组加链表
public class HashBuck {
    static class Node {
        public int val;
        public int key;
        public Node next;

        public Node(int key, int val) {
            this.val = val;
            this.key = key;
        }
    }
    public Node[] array;  //数组中存放的都是Node类型的节点
    public int usedSize;  //记录存放数据的多少

    public static final double LOAD_FACTOR = 0.75;  //负载因子

    public HashBuck() {
        array = new Node[10];  //仅用于自己实现,源码不是如此
    }

    //模拟实现哈希表的放入数据 , 设置key对应的val
    public void put(int key,int val) {
        int index = key % array.length;  //相当于一个哈希函数
        Node node = new Node(key,val);
        //array[index] = node;
        Node cur = array[index];
        //判断放的位置是不是空的,若是空的,直接放,若不空,这个空格内加入链表结构
        if(array[index] == null) {
            array[index] = node;
            usedSize++;
        } else {
            while(cur != null) {
                if(cur.key == key) {   //这里说明原先保存的有这个key,更新它的次数即可
                                       //因为哈希表中不添加重复的值,只会更新它的val值
                    cur.val = val;
                    return;
                }
                cur = cur.next;
            }
            //走到这说明保存的没有这个key,需要进行插入,在此采用头插的方式
            cur = array[index];
            node.next = cur;
            cur = node;
            usedSize++;

            /**
             * 这里注意cur要再指回来给array[index],不然不会发生变化的,和下面 ============= 注释的原因是一个
             */
            array[index] = cur;
            /**
             *          注意扩容函数的位置放错了,应该放在 if的外面,每次添加完新的都要检查一下容量
             *   //判断是否需要扩容
             *             if(calculateLoadFactor() >= LOAD_FACTOR) {
             *                 //扩容
             *                 resize();
             *             }
             */

        }
        //判断是否需要扩容
        if(calculateLoadFactor() >= LOAD_FACTOR) {
            //扩容
            resize();
        }
    }

    private void resize() {
        Node[] arrayNew = new Node[2 * array.length];
        //再把原先的值放到新的数组里面
        for (int i = 0; i < array.length; i++) {
            //注意此时放的下标由于数组的扩容而会发生变化
            int index = i % arrayNew.length;
            /**
             *     ============================
             * 注意这种写法不对,
             *   for (int i = 0; i < array.length; i++) {
             *             //注意此时放的下标由于数组的扩容而会发生变化
             *             int index = i % arrayNew.length;
             *              arrayNew[index] = array[i];
             *          }
             * 因为下标由于数组的扩容而会发生变化,所以会有可能由后面的i计算出的新下标跟前面的重复,而导致覆盖问题发生错误
             * 正确的应该是找到节点继续一个一个地头插
             *arrayNew[index] = array[i];
              **/
            Node cur = array[i];
            //Node curNew = arrayNew[index];
            while(cur != null) {
                Node curNext = cur.next;
                //把cur的节点一个一个地以头插的方式插入到新节点中去
                cur.next = arrayNew[index];
                arrayNew[index] = cur;
                cur = curNext;
                //arrayNew[index] = curNew;
            }
            //arrayNew[index] = curNew;

            /**
             *    上面的代码也可以写成这种,这种格式更加直接,
             *    上面那种是赋值的,需要再给到原来的arrayNew[index] 节点处, 不然不会发生改变
             *          while(cur != null) {
             *                 Node curNext = cur.next;
             *                 //把cur的节点一个一个地以头插的方式插入到新节点中去
             *                 cur.next = arrayNew[index];
             *                 arrayNew[index] = cur;
             *                 cur = curNext;
             *          }
             */
        }
        array = arrayNew;
    }

    //计算负载因子
    private double calculateLoadFactor() {
        return usedSize*1.0 / array.length;
    }
}
  • 查找操作

思路:

1. 通过哈希函数计算出数组的下标,遍历数组找到该下标的链表
2. 遍历链表,判断链表中是否包含该元素,若包含返回其对应的value值

  • 代码实现
 public int get(int key) {
	//  for (int i = 0; i < array.length; i++) {
	//   }
        int index = key % array.length;
        Node cur = array[index];
        while(cur != null) {
            if(cur.key == key) {
                return cur.val;
            } else {   //这里其实用不到else,因为这两个不可能同时出现,因为上面有return,走上一步就不可能走下面了,直接返回了
                cur = cur.next;
            }
        }
        return -1;
    }

7、哈希表和 java 类集的关系

1. HashMap 和 HashSet 即 java 中利用哈希表实现的 Map 和 Set
2. java 中使用的是哈希桶方式解决冲突的
3. java 会在冲突链表长度大于一定阈值后,将链表转变为搜索树(红黑树)
4. java 中计算哈希值实际上是调用的类的 hashCode 方法,进行 key 的相等性比较是调用 key 的 equals 方法。所以如果要用自定义类作为 HashMap 的 key 或者 HashSet 的值,必须覆写 hashCode 和 equals
方法,而且要做到 equals 相等的对象,hashCode 一定是一致的。

🎗️🎗️🎗️ 好啦,到这里我们的 哈希表及其冲突解决的分享就结束了,如果感觉做的还不错的可以点个赞,关注一下,你的支持就是我继续下去的动力,蟹蟹大家了,我们下期再见,拜拜~ ☆*: .。. o(≧▽≦)o .。.:*☆文章来源地址https://www.toymoban.com/news/detail-805346.html

到了这里,关于【数据结构】 | java中 哈希表及其冲突解决的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • Java实现数据结构哈希表

    Java实现数据结构哈希表

    概述 给美分数据分配一个编号,放入表格(数组) 建立编号与表格索引的关系,将来就可以通过编号快速查找数据 理想情况编号当唯一,数组能容纳所有数据 现实是不能说为了容纳所有数据造一个超大数组,编号也可能重复 解决 有限长度的数组,以[拉链]方式存储数据 允许编号适当

    2024年02月21日
    浏览(8)
  • 【数据结构】用Java实现哈希表

    【数据结构】用Java实现哈希表

    目录 1 概念 2 冲突-概念 3 冲突-避免 4 冲突-避免-哈希函数设计 (1)直接定制法--(常用) (2)除留余数法--(常用) (3)平方取中法--(了解) (4)折叠法--(了解) (5)随机数法--(了解) (6)数学分析法--(了解) 5 冲突-避免-负载因子调节(重点掌握) 6 冲突-解决 (1)冲突-解决

    2023年04月11日
    浏览(14)
  • 【Java数据结构】顺序表、队列、栈、链表、哈希表

    【Java数据结构】顺序表、队列、栈、链表、哈希表

    是一个有类型参数(type parameter)的范型表(generic class) 能够自动调整容量,并且不需要写额外的代码 存放数据使用数组但是可以编写一些额外的操作来强化为线性表,底层依然采用顺序存储实现的线性表,称为顺序表 创建 常见操作 一旦确认数组列表大小恒定,不再发生

    2024年02月02日
    浏览(11)
  • java数据结构(哈希表—HashMap)含LeetCode例题讲解

    java数据结构(哈希表—HashMap)含LeetCode例题讲解

      目录 1、HashMap的基本方法 1.1、基础方法(增删改查) 1.2、其他方法  2、HashMap的相关例题 2.1、题目介绍 2.2、解题 2.2.1、解题思路 2.2.2、解题图解 2.3、解题代码 HashMap 是一个散列表,它存储的内容是键值(key-value)映射。 HashMap 的 key 与 value 类型可以相同也可以不同,根据定

    2024年02月05日
    浏览(11)
  • 【Java程序员面试专栏 数据结构】四 高频面试算法题:哈希表

    【Java程序员面试专栏 数据结构】四 高频面试算法题:哈希表

    一轮的算法训练完成后,对相关的题目有了一个初步理解了,接下来进行专题训练,以下这些题目就是汇总的高频题目,一个O(1)查找的利器哈希表,所以放到一篇Blog中集中练习 题目 解题思路 时间 空间 两数之和 辅助哈希 使用map存储出现过的值,key为值大小,v

    2024年02月22日
    浏览(11)
  • 【JavaSE专栏55】Java集合类HashTable解析,基于哈希表实现的唯一性键值对存储数据结构

    【JavaSE专栏55】Java集合类HashTable解析,基于哈希表实现的唯一性键值对存储数据结构

    作者主页 :Designer 小郑 作者简介 :3年JAVA全栈开发经验,专注JAVA技术、系统定制、远程指导,致力于企业数字化转型,CSDN学院、蓝桥云课认证讲师。 主打方向 :Vue、SpringBoot、微信小程序 本文讲解了 Java 中集合类 HashTable 的语法、使用说明和应用场景,并给出了样例代码。

    2024年02月15日
    浏览(14)
  • 数据结构-哈希-哈希表实现

    数据结构-哈希-哈希表实现

    🚀理想的搜索方法:不经过任何的比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过某种函数使元素的存储位置与其关键码之间能够建立起一一映射的关系,那么在查找的时候就能通过此函数快速的找到该元素。 🚀向该结构中插入元素:根据该元素的

    2024年02月10日
    浏览(12)
  • 【数据结构】哈希底层结构

    【数据结构】哈希底层结构

    目录 一、哈希概念 二、哈希实现 1、闭散列 1.1、线性探测 1.2、二次探测 2、开散列 2.1、开散列的概念 2.2、开散列的结构 2.3、开散列的查找 2.4、开散列的插入 2.5、开散列的删除 3、性能分析  顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查

    2024年02月06日
    浏览(9)
  • 【数据结构】哈希表与哈希桶

    【数据结构】哈希表与哈希桶

    👀 樊梓慕: 个人主页  🎥 个人专栏: 《C语言》 《数据结构》 《蓝桥杯试题》 《LeetCode刷题笔记》 《实训项目》 《C++》 《Linux》 《算法》 🌝 每一个不曾起舞的日子,都是对生命的辜负 目录 前言 1.概念 2.哈希冲突 3.解决哈希冲突 3.1闭散列 3.2开散列(哈希桶) 4.模拟实

    2024年03月21日
    浏览(14)
  • 「数据结构」哈希表2:实现哈希表

    🎇 个人主页 :Ice_Sugar_7 🎇 所属专栏 :Java数据结构 🎇 欢迎点赞收藏加关注哦! 在讲插入之前需要先了解扩容,因为 插入后载荷因子如果超过阈值,那我们就要扩容,即扩容是插入操作的一部分 扩容后,原先哈希表中的元素的哈希地址会改变。之前会发生哈希冲突的元素

    2024年02月21日
    浏览(13)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包