散列函数
冲突解决办法
直接寻址技术,顾名思义,为每一个关键字分配一个槽,适用于全域数量较少,并且对内存并不要求的情况下,这种方法可以保证了其每一个操作的时间复杂度都是O(1)。
但是很明显的是,当全域特别大的时候,这意味着我们要创建一个内容特别大的数组,如果关键字较少则会造成空间的浪费,于是我们想出了使用散列表,控制数组的长度,通过使用散列函数(除法散列法、乘法散列法、全域散列法等
),找出关键字对应到各自相应的槽,也就是说,如果我们能够平均的分配关键字到相应的槽,避免冲突,则可以实现直接寻址技术一样的效果,同时还大大的减少了内存。
理想情况下,冲突可以完全避免,但是现实中,我们可以通过散列函数尽量的避免冲突,尽可能的使其随机化,同时还是会不可避免的发生冲突,此时我们还可以通过拉链法或者开放寻址法来解决冲突。
通过取 k 除以 m 的余数,将关键字映射到m个槽中。
乘法包括两个步骤,先是将关键字 k 乘以一个常数A(0 < A < 1),然后取其小数部分,乘以m后向下取整。
表示随机的选择一种散列函数,将关键字映射到散列函数,然后通过计算返回槽的位置。
链表法则是通过在每个槽的后面加上链表,如果发生了冲突,则将其依次加入的链表中(可能存在一个排序的问题),链表可以是单链表也可以是双链表,不过使用双链表更容易删除一些。
缺点:需要额外的指针空间。
除了链表法,还有另外一种方式更加的灵活,它用大小为M的数组去保存N个键值对(M>N),所有的元素都会保存在散列表中,我们需要依靠数组中的空位来解决碰撞冲突,比如说当发生冲突的时候,我们直接检查在散列表中冲突位置的下一个位置,如果发现仍然会造成冲突,则继续往下面找(可以循环,表尾可到表头),直到找到一个空白元素。这种方式的好处在于,不用存储指针从而节省空间,使得可以用同样的空间来提供更多的槽,潜在的减少冲突,提高检索效率。不过需要注意的是,它的装载因子(哈希表保存的元素数量和哈希表容量的比)不应该设置的太大,否则很容易发生哈希冲突,最好设置其不要超过0.5(即元素数量要小于哈希表的容量的一半)。
开放寻址法包括几种方法:线性探查,二次探查,双重探查;
线性探查
顾名思义,其是一个线性递增的过程,如果当前发生冲突,则会递增的找下一个,如果最后一个仍然找不到,那么则又会从头开始查找,以此类推。但是这会存在一个问题,叫做一次群集,随着连续被占用的槽增加,那么它查找的时间也会不断的增加。
二次群集
和之前不同的是,在开始的槽的位置上发生冲突以后,其不再是线性的递增,而是通过一个偏移量(1,2,3...的二次方地址处),这在一定程度下缓解了基本群集的问题,但是依旧存在问题,比如在具有相同偏移量的位置发生冲突,那么就和之前的一样,槽会发生连续被占用的情况,甚至是该相对偏移量下的所有槽都被占用情况,此时再使用偏移量则会找不到空白的槽。
key1:hash(key)+0
key2:hash(key)+1^2
key3:hash(key)+2^2
双重探测
这可能是最好的方法之一,因为它不再像是线性和二次固定的增加偏移,而是会变化的去寻找下一个位置。即下一个探测的位置以关键字的另一个散列函数值作为增量。其公式为:
h(k, i) = (h1(k) + ih2(k)) mod m