散列表也称作为哈希表,它是一个动态表,然而它支持字典的操作,因为它的存储一个值,需要相应的键。在查找一个值时,也需要一个键。最基本的操作就是:insert, search以及delete。散列表作为一个动态表,它的性能表现的异常好。甚至在合理的假设条件下,它的操作只需要
O(1)
的代价。
本章主要内容:
1. 直接地址法(Direct-address tables)
当我们的关键字全域
U
比较小时,直接寻址就显得十分有效,直接上图:
上图展示了,全域的大小就是表的大小,同时关键字只映射一个值。这个明显的一个缺点时,当我们的全域变得很大时,它就很会浪费空间。
2. 哈希函数(Hash function)
哈希函数的主要作用就是将我们的关键字映射成一个在哈希表中的位置。最基本的方法有除留余法,乘法散列,以及全域散列。在实际过程中我们用的更是复杂的哈希函数(如RS,JS),在这里我们只介绍全域散列。
全域散列
因为在实际当中,可能一个散列函数,会把许多关键子散列成同一个值,而实际当中最有效的方法就是,我们在一组不错的散列列函数,选择
3. 碰撞冲突
碰撞冲突,主要是因为我们在散列的时候可能散列到同一个值,这样就会形成冲突。在这里我们主要有种方法解决冲突。链接法,再哈希以及开放定址法。开放地址法还有线性探查,二次探查以及双重散列。
链接法
直接上图更容易清楚:
从途中我们可以看出,当我们有散列值冲突的时候,我们就需要用一个双向链表将冲突的具有相同的散列值得关键字链在一起。这样就能够很好的解决冲突。
我们来分析其一般的性能:假设表长为
m
, 共有
个人觉得其有一个不好的点:就是对于频繁删除和添加的操作则,要不断频繁的创造节点和删除节点,可能会消耗大量的时间,不过它的实现过程十分简单而且解决冲突的效果也是十分不错。
再哈希
当我们关键字在
hash
函数
h1
映射的时候发生了碰撞,然后选择另一个
hash
函数
h2,h3,...hn
直至没有冲突。这样做的缺点很明显,
1
是需要大量的
开放定址法(open addressing)
线性探测(linear probing)
假设表长为
m
, 则散列函数如下:
给定一个关键字 k , 先用
二次探测(quadratic probing)
直接看散列函数:
和线性探测不同的时探测函数用的是二次函数。
双重散列(double hashing)
直接看散列函数:
3 种开放定址法对比
通过图我们可以很容易看出他们之间的区别
2)二次探测
3)双重散列
从上面我们可以看出,双重散列更接近均匀散列。线性探测,可能导致查找的平均时间变长,而二次探测为了能够充分利用散列表,则
c1,c2,m
要受到限制。
均匀散列分析
定理3.1 [1]
给定一个装载因子 (loadfactor) α=n/m<1 的开放寻址散列表,则对于一次不成功的探查次数至多为 1/(1−α)
定理3.2 [2]
对于一个装载因子为
α<1
的开放寻址散列表,一次从成功查找中的探查期望数至多为
证明略,当 nm=12 时,探查期望小于 1.387 , 而当 nm=0.9 时, 探查期望小于2.559. [3]
4. 源码
本人在clion IDE中实现的 双重散列 , 下载请转此下载:http://download.csdn.net/detail/yzf0011/9772449
5. 感谢
本文是基于《算法导论》写的,最主要的是有本人大量的心得体会,感谢《算法导论》的那些作者Thomas H.Cormen、Charles E.Leiserson等 人。如果有错误的请留言,不甚感激。谢谢。
6. 参考
[1]《算法导论》Thomas H.Cormen、Charles E.Leiserson等 第三版第11章 “散列表” p155
[2]《算法导论》Thomas H.Cormen、Charles E.Leiserson等 第三版第11章 “散列表” p155
[3]《算法导论》Thomas H.Cormen、Charles E.Leiserson等 第三版第11章 “散列表” p155