数据结构与算法系列15(中)--散列表(哈希表)

如何设计一个散列函数?

1.散列函数的设计不能太复杂,否则会消耗很多计算时间,也就影响了散列表的性能。
2.散列函数生成的值要尽可能的随机并且均匀分布,这样才能最小化散列冲突,即便发生冲突,散列到每个槽里的数据也会比较平均,不会出现某个槽里数据太多的情况。

装载因子的选择

上一节我们讲过,装载因子越大,说明散列表中的元素越多,空闲位置越小,散列冲突的概率就越大。那我们怎样解决这个问题呢?可以通过设置装载因子的阈值来控制是扩容还是缩容,设计一个支持动态扩容的散列表。这里的装载因子的选择要权衡各个方面的因素,如果选择的值过大,会导致冲突过多,如果选择的值太小,会导致内存的严重浪费。所以我们选择装载因子的伐值时一定要权衡时间、空间复杂度,如果内存空间不紧张,对执行效率要求很高,可以降低装载因子的伐值。相反,如果内存空间不紧张,对执行效率要求不高,可以增加装载因子的值。

如何避免低效的扩容

我们知道,当装载因子已经达到伐值,需要先进行动态扩容,搬移旧数据,再插入新数据。如果此时的数据量非常大,那插入数据的过程就会变得特别慢,甚至无法接受。这样一次性扩容的操作,确实耗时过多,那么我们有什么好的方法来解决这个问题吗?事实上我们可以将扩容的过程穿插在插入数据的过程中,分批来完成。
具体过程:
当我们要插入一个新的数据时,如果装载因子已经达到伐值,我们先申请一个新的扩容后的空间,注意此时我们不做旧数据的搬移,而是直接将新数据插入到新的散列表中,然后从老的散列表中取出一个数据放到新的散列表中。以后每插入一个新的数据,我们都重复上面的过程,经过多次的插入操作后,老的散列表的数据就一点一点地全都搬移到新的散列表中。这样我们就没有了集中一次性插入数据和搬移数据,整个插入过程就会变得非常快。通过这种方式,任何情况下,插入数据的时间复杂度都是O(1)。
有人可能会有疑问,那中间的查询操作怎么办呢?
对于查询操作,为了兼容旧的和新的散列表,我们可以先在新的散列表中查找,如果没有找到,就在旧的散列表中查找。

如何选择散列冲突的解决办法

1.开放寻址法:
当数据量较小,装载因子较小时,适合采用开发寻址法。
2.链表法:
大部分情况下,链表法用的比较普遍。比较适合储存大对象,大数据量的散列表,而且,比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表,来避免散列表时间复杂度退化成O(n),抵御散列碰撞攻击。

如何设计一个工业级的散列函数

先提一个问题,何为一个工业级的散列表?工业级的散列表应该具有哪些特性?
1.支持快速的查询、插入、删除操作;
2.内存占用合理,不能浪费过多的内存空间;
3…性能稳定,在极端情况下,散列表的性能也不会退化到无法接受的情况。
具体方案:(如何设计这样一个散列表呢)
1.设计一个合适的散列函数;
2.定义装载因子阈值,并且设计动态扩容策略;
3.选择合适的散列冲突解决方法。
关于散列函数、装载因子、动态扩容策略,还有散列冲突的解决办法,我们前面都讲过了,具体如何选择,还要结合具体的业务场景、具体的业务数据来具体分析。
4.对于动态散列表来说,不管我们如何设计散列函数,选择什么样的散列冲突解决方法。随着数据的不断增加,散列表总会出现装载因子过高的情况。这个时候,我们就需要启动动态扩容。

猜你喜欢

转载自blog.csdn.net/qq_34493908/article/details/83962595