首页 » 算法技术手册 » 算法技术手册全文在线阅读

《算法技术手册》驱动因素

关灯直达底部

选择散列函数是在实现基于散列的查找之前必须做的第一个决定。散列已经被研究多年,并且有大量的描述高效散列函数的论文,不过用在查找上使用这些函数简直是杀鸡用牛刀。例如,某些特定的散列函数对于加密非常重要。对于查找来说,一个散列函数必须要有一个好的分布,并且计算速度非常快。

存储器空间是设计基于散列查找时需要考虑的另外一个因素。主存储器A必须能够足够大,以存下所有的查找键值,并且要给冲突键值留下足够的空间。A的大小通常是一个素数。但是,如果我们不使用开放定址的话(见接下来的“变种”一节),我们能够使用任何数字作为A的大小。实践中一个好的选择是2k-1,即使这个值并不是素数。存储在散列表的元素对内存有直接的影响。考虑图5-3的是如何在链表中存储每个字符串的,所以存储在A中的元素看做链表元素。堆中包含着指向元素的指针。每一个链表都有存储的开销,包含着指向链表中的第一个和最后一个元素。如果你使用Java的LinkedList类,一些很重要的附加字段和类使得实现非常灵活。程序员可以写一个简单得多的链表类,只提供必需的功能,但是确实给基于散列查找算法带来了额外的开销。

如果你使用LinkedList类,假设指针是四个字节,那么A中的每一个非空元素都需要12字节内存。每个字符串元素不可以直接转换成ListElement,需要12个额外字节。对于之前的那个有213 557个单词的例子,我们需要5 005 488字节的额外存储。这种情况是:

·主表的规模:1 048 572字节。

·包含116 186个元素的规模:1 394 232字节。

·包含213 557元素的规模:2 562 684字节。

如果你使用Java的String类,那么存储字符串还会需要额外的开销。每个字符串有12个字节的额外开销。因此我们就将增加213 557×12=2 562 684个额外字节,所以,例子中选择的算法需要7 568 172字节的内存来正常运行。单词表中字符串的实际字符数量只有2 099 075。我们的算法还需要大约是字符的存储空间4.6倍的额外存储空间。

我们之后将会讨论一些优化内存使用的变种,当内存价格高高在上时,你可以使用这些变种来节省开支。但是,如果你有足够的内存,还有一个合理的并不会产生太多的冲突的散列函数以及一个可以立即使用的链表实现,那么你为什么不选择JDK提供的解决方案呢?

只要散列函数能够将元素均匀分布,那么基于散列的查找将会得到非常好的性能。查找一个元素的平均时间将是常数,也就是O(1)。

有其他的影响实现的因素。主要都是关于如何处理集合的静态或者动态特性。在我们的例子中,我们知道我们的单词表有多大,并且我们不会在单词表中添加或者删除单词,至少不会在程序执行时操作。但是,如果我们有一个动态的集合,会做很多插入或者删除元素的操作,我们必须为散列表选择一个合适的数据结构来优化性能。在我们的例子中,我们的冲突处理策略工作得非常好,因为在链表中插入元素只需要常数时间,删除元素的时间和链表长度成比例。如果散列函数能够较好地分布元素,那么每一个链表相对都比较短。