【发布时间】:2011-02-03 02:38:52
【问题描述】:
有人能解释一下这两种实现的主要区别(优点/缺点)吗?
对于一个库,推荐什么实现方式?
【问题讨论】:
有人能解释一下这两种实现的主要区别(优点/缺点)吗?
对于一个库,推荐什么实现方式?
【问题讨论】:
Wikipedia's article on hash tables 对人们使用的不同哈希表方案提供了明显更好的解释和概述,这比我想象的要好。实际上,您最好阅读那篇文章而不是在这里提出问题。 :)
也就是说……
链式哈希表索引到指向链表头部的指针数组。每个链表单元都有为其分配的键和为该键插入的值。当您想从其键中查找特定元素时,该键的哈希用于计算出要遵循的链表,然后遍历该特定列表以找到您所追求的元素。如果哈希表中的多个键具有相同的哈希,那么您将拥有包含多个元素的链表。
链式散列的缺点是必须遵循指针才能搜索链表。好处是链式哈希表只会随着负载因子(哈希表中元素与桶数组长度的比率)的增加而线性变慢,即使它超过 1。
一个开放寻址哈希表索引到一个指向(键,值)对的指针数组。您可以使用键的散列值来确定首先查看数组中的哪个槽。如果散列表中的多个键具有相同的散列,那么您可以使用某种方案来决定要查找的另一个槽。例如,线性探测是您在选择的一个插槽之后查看下一个插槽,然后再查看其后的下一个插槽,依此类推,直到您找到一个与您正在寻找的键匹配的插槽,或者您找到一个空的插槽(在这种情况下,钥匙不能在那里)。
当负载因子较低时,开放寻址通常比链式散列更快,因为您不必跟踪列表节点之间的指针。如果负载因子接近 1,它会变得非常非常慢,因为您最终通常必须搜索存储桶数组中的许多插槽,然后才能找到您要查找的键或空插槽。此外,哈希表中的元素永远不能多于桶数组中的条目。
为了处理所有哈希表在负载因子接近 1 时至少会变慢(在某些情况下实际上完全崩溃)这一事实,实际的哈希表实现使存储桶数组更大(通过分配一个新的存储桶数组,并且当负载因子超过某个值(通常约为 0.7)时,将元素从旧元素复制到新元素中,然后释放旧元素)。
以上所有内容都有很多变化。再一次,请看维基百科的文章,真的很不错。
对于打算供其他人使用的库,我会强烈建议进行试验。由于它们通常对性能至关重要,因此您通常最好使用已经仔细调整过的其他人的哈希表实现。有很多开源 BSD、LGPL 和 GPL 许可的哈希表实现。
例如,如果您正在使用 GTK,那么您会发现有一个很好的 hash table in GLib。
【讨论】:
我的理解(简单来说)是这两种方法都有利有弊,尽管大多数库都使用链式策略。
链接方式:
这里的哈希表数组映射到一个项目的链表。如果碰撞的数量相当少,这是有效的。最坏的情况是O(n),其中 n 是表中的元素数。
使用线性探针打开寻址:
当碰撞发生时,移动到下一个索引,直到我们找到一个空位。因此,如果碰撞次数较少,这将非常快速且节省空间。这里的限制是表中的条目总数受数组大小的限制。这不是链式的情况。
还有另一种方法是用二叉搜索树链接。在这种方法中,当碰撞发生时,它们被存储在二叉搜索树而不是链表中。因此,这里最坏的情况是O(log n)。在实践中,这种方法最适合分布极不均匀的情况。
【讨论】:
开放寻址与分离链接
如果密钥作为条目保存在哈希表本身中,则线性探测、双重和随机哈希是合适的... 这样做被称为“开放寻址” 它也被称为“封闭哈希”
另一个想法:哈希表中的条目只是指向链表(“链”)头部的指针;链表的元素包含键... 这被称为“分离链接” 它也被称为“开放哈希”
通过单独的链接,冲突解决变得容易:如果它不存在,只需在其链接列表中插入一个键 (可以使用比链表更高级的数据结构;但链表在一般情况下工作得很好,我们将看到) 让我们来分析一下这些策略的时间成本
来源:http://cseweb.ucsd.edu/~kube/cls/100/Lectures/lec16/lec16-25.html
【讨论】:
如果在创建哈希表时不知道要插入到哈希表中的项目数,则链式哈希表比开放式寻址更可取。
增加负载因子(项目数/表大小)会导致开放寻址哈希表的性能大幅下降,但在链式哈希表中性能只会线性下降。
如果您正在处理低内存并希望减少内存使用量,请选择开放寻址。如果您不担心内存并且想要速度,请使用链式哈希表。
如有疑问,请使用链式哈希表。添加比预期更多的数据不会导致性能下降。
【讨论】: