回顾:通过上一节课的学习,我们不能笼统的说hash表的查找的时间复杂度为O(1 ),hash表的时间复杂度跟hash函数,装载因子,hash冲突等有关,如果hash函数设计的不好,或者装载因子过高,活导致hash表查询的时间复杂度增大,在极端的情况下,当hash函数设计不好,所有数据经过hash都存在同一个佣中,hash表查找会变成单链表的查找.
1>今天我们主要学习下该如何设计一个工程的hash表,应该注意哪些要点,
a>Hash函数的设计,一个好的hash函数应该满足这样的条件,hash计算不能太过复杂,太过复杂容易导致计算性能下降,且通过hash函数的值应该随机分布,这样才可以避免hash冲突,即使存在hash冲突,也会使得每个佣中的数据个数大致相同,不会使得查询变成极端的成功.
2>装载因子的值都低是多少合适呢?
公式:装载因子= 佣用的元素个数/佣的个数
如果涉及的装载因子过大,说明佣中的存储空间已经很少了,hash冲突的概率增大,在插入的过程中需要多次寻址,且需要很长的链表.查找也需要遍历较长的链表结构,
对于没有频繁的插入操作的情况下,我们很容易设计出完美的Hash表,毕竟数据是已知的,
对于频繁的进行插入删除操作,因为数据量我们无法估计,我们可以使用动态扩容机制,重新申请一个更大的散列表,重新hash,将数据加载到新的Hash表中,
解说:在图中21本身存储在佣中的0下标,经过动态扩容机制之后,佣数量变为原来的2倍,经过重新hash,数据21存储在下标为7的位置,
复杂度分析:在最好的情况下,插入一个数据不需要动态扩容,数据放在链表就可,时间复杂度为O(1),当插入一个数据需要进行动态扩容,重新hash,插入的时间复杂度是O(n);
3>如何高效的解决hash冲突
上节课我们讲了2种方法,开放寻址法,链表法,java中的hashmap就是佣的链表法 ,localhashmap中使用的是开放寻址法莱解决hash冲突.
当数据量比较小的情况下 ,且装载因子比较小,适合采用开放寻址法,
当数据量比较大的时候,比起开放寻址法,他更起到优化作用,比如用红黑树代替链表结构.
4>hashmap的实列
a:hashmap在初始创建,容量大小是16,这个值是可以设置的,可以设置的更大,来减少扩容的次数
b:装载因子:hashmap的装载隐因子是0.75,当hashmap中的元素数据个数超过0.75*16=12,就会进行扩容,每次扩容都会变为原来的2倍.
c:hashmap底层采用的是拉链法莱解决hash冲突,但是在一些极端的情况下,也避免不了链表过长,查找性能下降,在jdk1.8中,当链表长度超过8时,链表将用红黑树来代替,
5>解答开篇,工厂hash表的设计
1>支持快速的插入删除操作
2>内存占用合理,不鞥浪费过多的空间内存
3>性能稳定,在极端的情况下,散列表退化成链表可在接受的情况下
如何设计一个散列表
a>设计一个合适的hash函数
b>定义装载因子的阈值,支持动态扩容机制
c>选择合适的hash冲突解决方法(开放寻址法,链表法)