数据结构与算法基础(五)- 散列表

179 阅读2分钟

散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。 散列表用的就是数组支持按照下标随机访问的时候,时间复杂度是O(1)的特性。我们通过散列函数把元素的键值映射为下标,然后把数据存储在数组中对应下标的位置。当我们按照键值查询元素时,我们用同样的散列函数,将键值转化数组下标,从对应的数组下标的位置取数据。

散列函数

散列函数我们可以把它定义成hash(key),其中key表示元素的键值,hash(key)的值表示经过散列函数计算得到的散列值。 散列函数三点基本要求:

  • 散列函数计算得到的散列值是一个非负整数;
  • 如果key1 = key2,那么hash(key1)=hash(key2);
  • 如果key1 != key2,那么hash(key1)!=hash(key2);

散列冲突

解决散列冲突的方法有两种:开放寻址法,链表法。

开发寻址法

开放寻址法的核心思想是,如果出现了散列冲突,我们就重新探测一个空闲位置,将其插入。

线性探测:当我们往散列表中插入数据时,如果某个数据经过散列函数之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。但是线性探测存在很大问题。当散列表中插入的数据越来越多时,散列冲突发生的可能性就会越来截止大,空闲位置就会越来越少,线性探测的时间就会越来越久。极端情况下,可能需要探测整个散列表,在删除和查找同理。

对于开放寻址冲突解决方法,除了线性探测方法之外,还有两个:二次探测|双重散列。

二次探测:二次探测的步长就变成了原来的“二次方”;

双重散列:就是使用多个散列函数;

装载因子表示空位的多少。

计算公式:装载因子=填入表中的元素个数/散列表的长度

链表法

在散列表中,每个“桶”或者“槽”会对应一条链表,所有的散列值相同的元素我们都放到相同槽位对应的链表中。