全面介绍散列表和冲突消除问题

散列表的概念

散列表又被称作哈希表,是一种可以在O(1)时间内实现增删改查功能的强力技术,当然,为了实现这种目标,散列是无法被排序的。我们先通过一个简单的例子来理解一下这几个概念:读书的时候我们每个人都有长长的一串学号,假设学号的格式是专业码+入学年份+编号(比如123 2021 003),我们要储存同学的信息到系统中,而且要一秒输入学号查找到人,这里我们就可以用散列表了!因为只是储存一个专业的同一级的每个人,我们只需要以最后三位作为下标然后将每个人的信息存到下标对应的数组空间中就行了。其中,散列函数(Hash function)就是截取最后三位的操作。如果这时,我们要添加别的年级或别的专业,再用截取最后三位的操作就行不通了,因为不同的学号会对应到同一个空间(比如123 2021 003和124 2020 003)。不同的输入对应到同一个输出,这就叫做冲突(collision)
在这里插入图片描述

散列函数

相信上面的例子已经讲明白了散列函数(Hash function)的含义,散列函数没啥特别的,可以把它当作 f ( x ) f(x) f(x)来理解,这个 f ( x ) f(x) f(x)要满足这个条件:输入的 x x x如果不同,输出的 y y y必须不同且 y ∈ N y\in N yN(不同的x对应一个y会有冲突,下标没有负数)。同时,一个好的散列函数要尽可能将 y y y均匀分配到数组的存储空间中,不要浪费空间。尽管散列函数理解起来很容易,但是找到一个足够好的散列函数是很困难的事情。如果没记错的话,计算机导论学过,连SHA这种算法都无法避免不出现不同 x x x对应相同 y y y的情况。这样的话就引出了下一个主题:如何消除冲突呢?

如何消除冲突?

书上说分离链接法和开放地址法是最简单的,所以我就来讲讲自己是怎么理解它们的。刚刚提到,目前几乎没有散列函数可以彻底解决冲突的问题,所以消除冲突就是将两个不同输入对应的相同输出区别开来保存,这也就引出了这两种方法。这里有个概念,散列表的装填因子(load factor) λ \lambda λ为散列表中元素个数与散列表大小的比值。

分离链接法(Separate chaining)

分离链接法就是将每一个数组的空间变为存储一个结点,如果有两个不同的输入对应同一个输出,就将后来的那个输入储存到当前结点的链接的下一个结点,在查找的时候就只需要查找这个链表中有没有对应的值即可。
另外,分离链接法是一个数组与链表结合运用的很好例子。这边运用到的就是之前讲过的带有哑结点(表头)的链表了。新节点将直接通过哑结点(表头)被插入表中,所以插入的时间复杂度是O(1),而且还有一个好处是后新插入的结点一般最有可能被先访问。 但是当查找的时候就需要遍历某个链表了。如果我们的散列函数将n个元素比较均匀的分布到有m个空间的数组中,那么很容易看出查找的时间复杂度就是O(n/m)了。当分离链接法的装填因子 λ ≈ 1 \lambda \approx 1 λ1说明分布较为均匀(这里不考虑比如有m个空间的数组而n个元素只塞在一个一个空间形成链表的情况,因为这种情况说明我们的散列函数很糟糕,要调整的是散列函数)。
在这里插入图片描述

开放地址法

分离链接法的小问题就在于它需要多分配一个空间保存指针,如果元素数量足够大,那么这样的做法肯定会影响速度,开放地址法可以就不需要额外分配空间保存指针。
开放地址法会通过持续筛选新的散列表中的空间直到有地方安排这个元素。假设 h i ( X ) = ( H a s h ( X ) + F ( i ) )    m o d    T a b l e S i z e h_i(X) = (Hash(X)+F(i))\; mod \; TableSize hi(X)=(Hash(X)+F(i))modTableSize一定能找到一个空间, F ( i ) F(i) F(i)是对第i个单元的探测,其中 F ( 0 ) = 0 F(0)=0 F(0)=0,因为第0个单元的插入是肯定会成功的,表里没有别的元素嘛。

线性探测法

线性探测法中 F ( i ) = i F(i)=i F(i)=i,也就是该方法会逐一探测每一个单元,有位置就放进去,没有位置就继续探测。通过画图发现,线性探测会一个一个格子储存数据,那么当数据越来越多,空位越来越少,线性探测解决冲突的时间就会越来越长。
在这里插入图片描述

平方探测法

平方探测法中 F ( i ) = i 2 F(i)=i^2 F(i)=i2,这个方法和线性探测挺类似的。

双散列

双散列中 F ( i ) = i ⋅ h a s h 2 ( X ) F(i)=i\cdot hash_2(X) F(i)=ihash2(X),也就是一个散列函数处理完后,如果没位置,就再丢一个散列函数里去找到那个位置探测。

小结

其实平方探测法和双散列都是将F(i)替换为不同的函数来处理冲突。所以我这边就没过多写了。

装太满怎么办?

装太满了就用再散列的方式扩容两倍,再将原本的元素重新用散列函数计算,然后将这些元素储存进新的散列表中。书上的例子是将13、15、24、6和23用线性探测法插入散列表,散列函数是 H a s h ( X ) = X    m o d    T a b l e S i z e Hash(X)=X\; mod \; TableSize Hash(X)=XmodTableSize,然后当 λ \lambda λ达到某个阈值的时候就扩容,换了新的 T a b l e S i z e TableSize TableSize自然要重新计算咯(书上的图)
在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值