【发布时间】:2015-08-10 20:02:04
【问题描述】:
我在阅读 HashSet 上的 javadocs 时遇到了有趣的声明:
此类为基本操作(添加、删除、包含和大小)提供恒定的时间性能
这让我非常困惑,因为我不明白一个比较操作如何获得恒定时间 O(1) 的性能。以下是我的想法:
如果这是真的,那么无论我将多少数据转储到我的 HashSet 中,我都可以在恒定时间内访问任何元素。也就是说,如果我在我的 HashSet 中放入 1 个元素,则查找它所花费的时间与我有一个 googolplex 元素所花费的时间相同。
但是,如果我有恒定数量的桶或一致的哈希函数,这是不可能的,因为对于任何固定数量的桶,该桶中的元素数量将线性增长(尽管速度很慢,如果number 足够大)与集合中元素的数量。
然后,唯一可行的方法是在每次插入元素时(或每隔几次)更改散列函数。一个从不发生任何冲突的简单散列函数将满足这一需求。字符串的一个玩具示例可能是:获取字符串的 ASCII 值并将它们连接在一起(因为添加可能会导致冲突)。
但是,对于足够大的字符串或数字等,此哈希函数和任何其他此类哈希函数可能会失败。您可以形成的存储桶数量立即受到您拥有的堆栈/堆空间量的限制,等等。因此,不能无限期地允许跳过内存中的位置,因此您最终必须填补空白。
但是如果在某个时刻重新计算哈希函数,这只能与找到一个通过 N 个点的多项式或 O(nlogn) 一样快。
我的困惑由此而来。虽然我相信 HashSet 可以在 O(n/B) 时间内访问元素,其中 B 是它决定使用的桶数,但我看不出 HashSet 如何在 O( 1) 时间。
【问题讨论】:
-
如果我们假设每个操作的 O(N / B) 界限成立,那么很容易显示 O(1) 摊销界限。例如,每次 N 等于 B 时,我们可以将桶的数量翻倍。
-
如果每当 N 达到 B 时将 B 加倍,您将如何创建一个哈希函数均匀地散列到 B 个桶?假设我从 B = 2 和一个巧妙的散列函数开始,它将前 2 个项目均匀地分布在 2 个桶中。但是,一旦我将 B 加倍为 4,我可以使用什么函数来保证接下来的两个元素将落在存储桶 3 和 4 中?如果有“机会”它们会落在 1 和 2 中,那么经过足够的倍增后,桶将变得很重:“较早”创建的桶将具有更多元素。如果您将项目重新映射到存储桶,这将花费非常多的时间。
-
我说的是摊销(不是最坏的情况)恒定时间。是的,当我们将桶数加倍(或增加其他固定常数因子)时,它确实涉及完全重新分配和重新映射。
-
这是常数时间,而不是 O(1)。 HashSet 不支持任意大的哈希表,因此复杂性理论不适用。
标签: algorithm hash theory hashset