【发布时间】:2011-06-24 23:21:24
【问题描述】:
在各种哈希表实现中,我看到了可变哈希表何时应该调整大小(增长)的“神奇数字”。通常,这个数字介于每个分配的插槽所添加的值的 65% 到 80% 之间。我假设权衡是更高的数字会带来更多冲突的可能性,而更低的数字会以使用更多内存为代价。
我的问题是这个数字是怎么得出的?
这是任意的吗?基于测试?基于其他一些逻辑?
【问题讨论】:
在各种哈希表实现中,我看到了可变哈希表何时应该调整大小(增长)的“神奇数字”。通常,这个数字介于每个分配的插槽所添加的值的 65% 到 80% 之间。我假设权衡是更高的数字会带来更多冲突的可能性,而更低的数字会以使用更多内存为代价。
我的问题是这个数字是怎么得出的?
这是任意的吗?基于测试?基于其他一些逻辑?
【问题讨论】:
据推测,大多数人至少开始从一本书(例如,Knuth,第 3 卷)中的数字开始,这些数字是通过测试产生的。根据情况,有些人可能会在之后进行测试,并做出相应的调整——但据我所知,这些可能是少数。
正如我在previous answer 中概述的那样,“正确”数字还很大程度上取决于您如何解决冲突。无论好坏,这一事实似乎被广泛忽视——人们通常不会选择特别适合他们使用的碰撞解决方案的数字。
OTOH,我在测试中发现的另一点是它很少会产生很大的不同。您可以在相当广泛的范围内选择数字并获得非常相似的整体速度。最重要的是要小心避免将数字推得太高,尤其是当您使用线性探测之类的方法来解决冲突时。
【讨论】:
我认为您不想考虑表“有多满”(总桶中有多少“桶”具有值),而是要考虑为新项目找到位置可能需要的冲突次数。
几年前我读过一些编译器书籍(不记得标题或作者),建议只使用链表,直到您拥有超过 10 到 12 个项目。这似乎支持超过 10 次碰撞意味着需要重新调整大小。
The Design and Implementation of Dynamic. Hashing for Sets and Tables in Icon 建议平均哈希链长度为 5(在该算法中,平均冲突数)足以触发重新哈希。似乎得到了测试的支持,但我不确定我是否正确阅读了论文。
看来resize的情况主要是测试的结果。
【讨论】:
这取决于键。如果您知道您的哈希函数对所有可能的键都是完美的(例如,使用gperf),那么您知道您只会遇到很少的冲突,因此数量会更高。
但大多数时候,除了它们是文本之外,您对键了解不多。在这种情况下,您必须猜测,因为您甚至没有测试数据来提前弄清楚您的哈希函数的行为。
所以你希望最好。如果您的哈希函数对键非常不利,那么您将有很多冲突并且永远无法达到增长点。在这种情况下,选择的数字是无关紧要的。
如果您的哈希函数足够,那么它应该只会产生少量冲突(小于 50%),因此 65% 到 80% 之间的数字似乎是合理的。
也就是说:除非您的哈希表必须是完美的(= 巨大的大小或大量的访问),否则不要打扰。如果你有十个元素,那么考虑这些问题就是浪费时间。
【讨论】:
据我所知,这个数字是基于经验测试的启发式方法。
由于散列值的分布相当好,看起来神奇的负载因子——正如你所说的——通常在 70% 左右。较小的负载因子意味着您在浪费空间而没有真正的好处;更高的负载因子意味着您将使用更少的空间,但会花费更多时间来处理哈希冲突。
(当然,如果您知道哈希值是完美分布的,那么您的负载因子可以是 100%,您仍然不会浪费空间,也不会发生哈希冲突。)
【讨论】:
碰撞高度依赖于数据和使用的哈希函数。
大多数数字基于启发式或关于哈希值正态分布的假设。 (大约 70% 的 AFAIK 值是可扩展哈希表的典型值,但总是可以构造这样的数据流,这样您会得到更多/更少的冲突)
【讨论】: