参考
算法导论
引文
散列表(hash table)是实现字典操作的一种有效的数据结构。尽管最坏的情况下,散列表中查找一个元素的时间与链表中查找的时间相同,达到了O(n)。然而实际应用中,散列的查找的性能是极好的。在一些合理的假设下,在散列表中查找一个元素的平均时间是O(1)。
冲突和解决
利用哈希函数h(k),可以把关键字映射到一个小的数组中,但是会发生冲突。
解决方法有链接法和开放寻址法。
链接法
以链表的方式把冲突的元素往链表中插。
链表太长了性能下降如何优化?可以用完全散列结婚数,使用二次散列表。对冲突的元素再进行一次散列。
比如说,第一次算出来有好几个key的h(key)都等于2,h(key)=2,那就把这些冲突的元素再做一次散列。如果冲突的元素有n个,要求二次散列表的大小位m = n * n。
注:参考算法导论的举的例子(图11-6),从直接寻址表中引出的链表,还应存放二级散列表的哈希函数的参数。
算法导论:利用精心选择的散列函数hj,可以确保在第二级上不出现冲突。
开放寻址法
所有的元素都存放在散列表里。也就是说,每个表项或包含动态集合的一个元素,或包含NIL。当查找某个元素时,要系统地检查所有的表项,直到找到所需的元素,或者最终查明该元素不在表中。不像链表法,这里既没有链表,也没有元素存放在散列表外。因此在开放寻址法中,散列表可能会被填满,以至于不能插入任何新的元素。
发生冲突时有三种探查方式:
线性探查、二次探查、双重散列。
线性探查就是一直找下一个可以放的槽
h(k, i) = (h’(k) + i) mod m
二次探查是利用二次函数来查找下一个可以放的槽
h(k, i) = (h’(k) + c1*i + c2*i*i) mod m
双重散列的效果最好。
h(k, i) = (h1(k) + i * h2(k)) mod m
举个例子。
假设,散列表的大小为13,h1(k) = k mod 13,h2(k) = 1+ (k mod 11)。
第1次计算:h1(k) = 14 mod 13 = 1, 如果发现槽1被占,就继续算下一个。
h2(k) = 1 + (14 mod 11) = 4。
第1次冲突,要尝试的下一个槽的计算:
h(k, 1) = 1 + 1 * 4 = 5。尝试槽5。
如果槽5也被占了呢?第2次冲突,要尝试的下一个槽的计算:
h(k, 2) = 1 + 2 * 4 = 9。尝试槽9。
以此类推。
python是怎么实现dict的?
参考
Python是使用开放寻址法中的二次探查来解决冲突的。然后如果使用的容量超过数组大小的2/3,就申请更大的容量。数组大小较小的时候resize为*4,较大的时候resize*2。实际上是用左移的形式。