【问题标题】:How can a HashSet offer constant time add operation?HashSet 如何提供恒定时间添加操作?
【发布时间】: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) 时间。

注意:This postthis post 都没有解决我列出的问题..

【问题讨论】:

  • 如果我们假设每个操作的 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


【解决方案1】:

桶的数量是动态的,大约为 ~2n,其中n 是集合中元素的数量。

请注意,HashSet 给出了amortizedO(1) 的平均时间性能,而不是最坏的情况。这意味着,我们可能会不时遭受O(n) 操作。
因此,当垃圾箱过于拥挤时,我们只需创建一个新的更大的数组,并将元素复制到其中。
这会花费n 操作,并且在集合中的元素数量超过2n/2=n 时完成,因此这意味着此操作的平均成本以n/n=1 为界,这是一个常数。

此外,HashMap 提供的冲突次数也是恒定的平均

假设您要添加一个元素 xh(x) 被一个元素填充的概率是 ~n/2n = 1/2。它被 3 个元素填充的概率是 ~(n/2n)^2 = 1/4(对于较大的 n 值),依此类推。
这使您的平均运行时间为1 + 1/2 + 1/4 + 1/8 + ...。由于该总和收敛到2,这意味着此操作平均花费恒定时间。

【讨论】:

  • 这是一个很好的回复。对摊销复杂性感兴趣的人应该谷歌摊销二进制计数器和数组列表分配,这些是一些简单的例子,说明了它的重要性。
【解决方案2】:

我对散列结构的了解是,要保持 O(1) 的插入删除复杂度,您需要有一个良好的散列函数以避免冲突,并且结构不应该是满的(如果结构是满的,你就会有冲突)。

通常散列结构定义一种填充限制,例如 70%。 当对象的数量使结构被填充超过此限制时,您应该扩展其大小以保持在限制和保修性能以下。通常,当达到限制时,您将结构的大小加倍,以便结构大小的增长速度快于元素数量,并减少要执行的调整大小/维护操作的数量

这是一种维护操作,包括重新散列包含在结构中的所有元素,以在调整大小的结构中重新分配它们。当然,这有一个复杂度为 O(n) 的成本,其中 n 是存储在结构中的元素数量,但此成本未集成在 add 函数中,这将使维护操作需要
我想这就是困扰你的原因。

我还了解到,哈希函数通常取决于用作参数的结构的大小(达到限制的最大元素数是结构大小的质数,以减少碰撞的概率或其他东西像那样)意味着你不改变哈希函数本身,你只是改变它的参数。

要回答您的评论,如果桶 0 或 1 已填充,则不保证当您调整到 4 个新元素时,新元素将进入桶 3 和 4。也许调整到 4 会使元素 A 和 B 现在位于桶 0 和3

当然以上都是理论上的,在现实生活中你没有无限的内存,你可能会发生碰撞和维护有成本等等,这就是为什么你需要知道你将存储的对象的数量和权衡可用内存以尝试选择散列结构的初始大小,这将限制执行维护操作的需要并允许您保持 O(1) 性能

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-12-16
    • 1970-01-01
    • 2015-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多