HashSet如何提供恒定时间添加操作?

时间:2015-05-28 10:47:55

标签: algorithm hash theory hashset

当我看到有趣的声明时,我正在阅读HashSet上的javadoc:

  

此类为基本操作(添加,删除,包含和大小)提供恒定的时间性能

这让我很困惑,因为我不明白人们如何能够获得恒定的时间,O(1),比较操作的表现。以下是我的想法:

如果这是真的,那么无论我将多少数据转​​储到我的HashSet中,我都能够在恒定时间内访问任何元素。也就是说,如果我在我的HashSet中放入1个元素,则需要相同的时间来查找它,就像我有一个元素的googolplex一样。

然而,如果我有一个恒定数量的桶或一致的散列函数,这是不可能的,因为对于任何固定数量的桶,该桶中的元素数量将线性增长(尽管缓慢,如果数量足够大)与集合中的元素数量。

然后,这种方法的唯一方法是每次插入元素时(或每隔几次)更改一个哈希函数。一个简单的哈希函数,永远不会发生任何冲突,满足这种需求。字符串的一个玩具示例可能是:获取字符串的ASCII值并将它们连接在一起(因为添加可能会导致冲突)。

但是,这种散列函数以及此类任何其他散列函数可能会因足够大的字符串或数字等而失败。您可以形成的存储桶数量会立即受到堆栈/堆空间量的限制,因此,不能无限期地允许在内存中跳过位置,所以你最终必须填补空白。

但是如果在某个时刻重新计算哈希函数,这只能与找到通过N个点的多项式或O(nlogn)一样快。

因此到了我的困惑。虽然我会相信HashSet可以在O(n / B)时间内访问元素,其中B是它决定使用的桶的数量,但我没有看到HashSet如何在O中执行添加或获取函数( 1)时间。

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

2 个答案:

答案 0 :(得分:17)

存储桶的数量是动态的,约为〜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,这意味着此操作平均需要

答案 1 :(得分:2)

我对哈希结构的了解是,为了保持O(1)插入删除的复杂性,你需要有一个好的哈希函数来避免冲突,结构不应该是满的(如果结构已满,你会发生冲突)。

通常散列结构定义了一种填充限制,例如70%。 当物体的数量使结构填充超过此限制时,应将其尺寸扩大至低于限制和保修性能。通常,当达到限制时,结构的大小加倍,以便结构大小比元素数量增长更快,并减少要执行的调整大小/维护操作的数量

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

我还了解到,哈希函数通常取决于用作参数的结构的大小(有一些像达到极限的元素的最大数量是结构大小的素数,以减少碰撞的概率或某事就像那样)意味着你不会改变哈希函数本身,只需更改其参数即可。

要回答您的评论,如果填充0或1,那么当您调整大小为4个新元素将进入存储桶3和4时,可能无法保证。也许调整为4使元素A和B现在位于存储桶0和3

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