数据结构之散列表
什么是散列表
散列表又叫哈希表,利用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展。
我们通过散列函数把元素的键值映射为下标,然后将数据存储在数组中对应下标的位置,当我们按照键值查询元素的时候,用同样的散列函数,将键值转换为数组下标,再从对应的数组下标的位置获取数据。
如何构造一个散列函数
那么,要如何构造一个散列函数呢?下面有三点构造散列函数的基本要求。
1、通过散列函数计算得到的散列值必须是一个非负整数。
2、如果key值相同,那么通过散列函数计算得到的值也一样。
3、如果key值不同,那么通过散列函数计算得到的值也不一样。
其实对于第三点的实现是非常困难的,在真实情况下几乎是不可能的,而且,因为数组的存储空间有限,也会加大这种可能性。两个key通过散列函数计算得到相同的值,这种叫散列冲突,又叫哈希冲突。
如何解决哈希冲突
开放寻址法
开放寻址法的核心在于,如果发生了散列冲突,那么我们就重新探寻一个空闲位置,当我们插入一个数据,如果其存储位置已经被占用,我们就从当前位置开始,依次往后查询,直到找到空闲位置。但是查询的话就有点问题了,我们通过散列函数计算key得到的值,获取下标为该值上的数据,如果是就找到了,但是不是的话继续往下找,直到找到一个空闲位置,那么就说明该数不存在。但是这种情况下,如果我们本来删了一个数据,那么这个位置就为空了,这就会导致我们的查询方法失效,本来存在的数据就会被认为不存在,我们可以把被删除的数据做一个标记,如果遇到了有标记的数据,就继续往下找。这个方法叫线性探测法,但是这个方法其实有很大的弊端,因为随着数据量的增多,导致散列冲突的可能性越来越大,空闲位置就越来越少,导致查询的时间越来越长。
这就引出了另外两种经典的开放寻址法,双重散列和二次探测,二次探测的步长是线性探测的二次方。
所谓双重散列,我们可以准备多个散列函数,如果某个冲突了就换下一个。
但是不论采用何种方法,最后冲突的概率就会越来越高。为了尽可能保证散列表的操作效率,我们尽量保证散列表中有一定的空位,我们用装载因子表示空位的多少,装载因子的计算方式是,表中的元素个数/散列表的长度。
链表法
链表法是常用的解决散列冲突的办法,插入的时候我们只需要计算出散列值插入到对应散列值存放的链表中,查找只需要查询到对应链表后,再遍历链表即可。