解决哈希冲突——闭散列和开散列(数据结构)

本文详细介绍了哈希表中冲突的概念,以及如何通过哈希函数设计和闭散列、开散列方法来避免和解决冲突。重点讨论了线性探测、二次探测两种闭散列策略的优缺点,并提供了开散列(链地址法)的模拟实现,包括插入、删除、查找操作。此外,还探讨了哈希表的负载因子及其对冲突率的影响,以及何时进行扩容以保持高效性。


一、冲突——概念

不同关键字通过相同的哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或者哈希碰撞。
首先,我们要明确一点,由于我们哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致一个问题,冲突的发生是必然的,我们能做的是尽量降低冲突率。

二、冲突——避免——哈希函数设计

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

1.哈希函数设计原则

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

2.常见哈希函数

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

2.除留余数法
设置列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key)=key%p(p<=m),将关键码转换成哈希地址。
还有平方取中法、折叠法、随机数法和数学分析法等几种不常用的方法,这里不再作过多阐述。
注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突。

二、冲突——解决——闭散列

1.概念

当发生哈希冲突时,从发生哈希冲突的位置开始按照某种方式找“下一个”空位置。
1.通过哈希函数计算哈希地址
2.插入元素----注意:可能会产生哈希冲突
在这里插入图片描述

找“下一个”空位置的方式:

1.线性探测

从发生哈希冲突的位置开始,逐个挨着依次往后查找,如果走到空间末尾再从头开始。
那么我们这里要插入36,求出哈希地址为6,但此位置已经被占据,我们就挨着往后进行查找,走到8号位置发现是空的,插入36即可。
那么这里有一个问题:我们怎么知道哪个位置有元素,哪个位置没有元素呢?
所以这里我们要给哈希表格中的每个空间进行状态标记:
有元素:EXIST 无元素:EMPTY
在这里插入图片描述
查找要插入的位置:
在这里插入图片描述

但是当删除表格内元素的时候,就会出现一些意外的情况:
当我们要删除6时,先要计算6的哈希地址,算出来为6号位置,直接将该位置状态修改成EM,但是如果把该位置修改了,就会造成我们后边的36找不到,因为我们要删除36求其哈希地址为6,但是6号位置已经为空了,就不会再往后找了,这样就会导致后期有些元素找不到。
所以在删除元素时,不能直接将其状态改为EM,得需要再给一个状态DELETE,表明该位置上的元素被删除了,所以把6删完,将其状态改为DELETE,说明该位置之前有元素,只是后来被删除了而已,我们就可以往后查找,就可以找到36。

但是我们会发现,上面写的程序最终会进入死循环,其实前面这种事有很大问题的,当表格快要存满时,哈希表的效率就会降低,发生冲突的概率也会升高
比如下图:当我们要在此基础上插入44时,我们需要从四号位置一直走,知道走到3号位置,效率很低了
在这里插入图片描述
因此哈希表格当中是不会将元素存满的,那么存多少个才算合适呢?
存的少了空间利用率不高,存的多了冲突的概率就大。

负载因子调节:
散列表中负载因子的定义为:α=填入表中的元素个数 / 散列表的长度
负载因子越小:存的元素越少,发生冲突的概率越低

负载因子的冲突率的关系粗略演示:
在这里插入图片描述
负载因子应严格限制在0.7~0.8以下,在线性探测中,我们一般情况下规定负载因子为0.75。
已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中数组的大小。

优点:
处理哈希冲突的方式简单—逐个挨着依次往后找

缺陷:
容易产生数据的堆积—一旦发生冲突,数据就容易连成一片。
在这里插入图片描述

解决方式: 二次探测

2.二次探测

线性探测的缺陷就是产生冲突的数据堆积在一块,这与其找下一个空位置有关,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为:Hi=(H0+i2)%m,或者:Hi=(H0-i2)%m。 其中:i=1,2,3…,H0是通过散列函数Hash(x)计算出来的哈希地址,m是表的大小。
如果index越界,接着取模运算

优点:
解决了线性探测数据堆积的问题。

缺陷:
当表格中的元素不断增多,二次探测可能要找的次数更多
在这里插入图片描述
所以二次探测的负载因子可能会被放得很低,比如α=0.5。

三、冲突——解决——开散列

1.概念

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

2.开散列的模拟实现

1.创造一个不带头的单链表:
    public static class ListNode{
   
   
        private int key;
        private int value;
        ListNode next;

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

2.创造一个数组用来存放链表的头结点并定义初始容量

    ListNode[] table;
    int size;

    public HashBucket(int initCapacity){
   
   
        initCapacity=initCapacity<=0?16:initCapacity;
        table=new ListNode[initCapacity];
    }

    private int
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dhdhdhdhg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值