Hash表(三)——Hash函数&装载因子&动态扩容

2019-08-27 09:55:32 浏览数 (1)

Hash函数的确定

通过前面学习到, Hash表的查询效率并不是 O(1),它与 Hash函数、散列冲突等因素有关。如果 Hash函数确定得不好,可能导致散列冲突概率升高,查询效率下降。那么,该如何设计 Hash函数呢?

首先, Hash函数一般设计得不要过于复杂,过于复杂的 Hash函数会导致计算时间过多,从而影响散列表的性能;

其次, Hash函数生成的值要尽可能随机并且均匀分布,这样才能避免或者最小化散列冲突,而且即使出现冲突,散列到每个槽中的数据也会比较平均,不会导致某些槽中的数据过多,而另一部分槽中的数据过少的情况。

传统的 Hash函数的设计方法有直接寻址法、平方取中法、折叠法、随机数法等,也可以根据实际情况自己设计 Hash函数。

装载因子的确定

为了定量的表示 Hash表中空位的多少,定义装载因子

代码语言:javascript复制
Hash表的装载因子 = 填入表中的元素个数 / Hash表的长度

由公式可知,装载因子越大,说明 Hash表中的元素越多,空闲位置越少,散列冲突的概率越大,散列表的性能就会下降。

对于没有频繁插入和删除的静态数据结合来说,可以根据数据的特点和分布情况设计出符合这些数据的 Hash函数,从而减少了散列冲突。

但是大部分情况下是动态数据,数据集合是频繁变动的,我们无法事先知道数据的个数,因此也无法事先申请一个足够大的 Hash表。随着数据加入,填入表中的元素个数增多,装载因子增大,当装载因子达到一定程度时,散列冲突便不可接受,因此我们无法根据数据的特征和分布情况设计出符合这些数据的 Hash函数,而是需要动态扩容,重新申请一个更大的 Hash表,将数据重新存储到新的 Hash表中。

如下图中的散列表,当装载因子达到0.8时进行扩容,装载因子变为0.4,原来的数据就会存储在新的 Hash表中。

当数据插入到 Hash表时,如果装载因子还未达到临界值,此时还不需要扩容,插入的数据非常快,但如果装载因子达到了临界值,这是就需要先进行扩容,然后再插入数据,这个时候就会变得很慢。

当数据需要从 Hash表中删除时,如果 Hash表已经经历过扩容,随着数据的删除,空闲空间会越来越多。当程序对内存空间非常敏感时,可以设置当装载因子小于某个临界值时,启动动态缩容,让内容空间得到充分利用;当程序对内存空间不太敏感时,就不需要进行动态缩容处理。

动态扩容策略

为了减少动态扩容耗时,我们可以将扩容的操作穿插在插入操作过程中。具体如下图所示:

这样每次插入时迁移一个数据,没有集中一次性迁移数据那样耗时,不会形成明显的阻塞。

由于迁移过程中,有新旧两个 Hash表,查找数据时,先在新的 Hash表中进行查找,如果没有,再去旧的 Hash表中进行查找。

0 人点赞