【问题标题】:How is the Hashtable get(kType key) amortized time complexity O(1) and not O(log n)?Hashtable get(kType key) 如何摊销时间复杂度 O(1) 而不是 O(log n)?
【发布时间】:2020-11-11 00:13:38
【问题描述】:

与键关联的索引通常在最简单的哈希表实现中通过以下方式检索:

size++;
int hash = hashcode(key);
int index = hash % size;

对于任意键,我们可以说索引将是[0, size - 1] 范围内的整数,每个结果的概率相等。下表描述了添加 N 个元素后前 5 个索引的这些概率。

Index            |   0              1               2              3                4
--------------------------------------------------------------------------------------------
Probabilities    |  1
                 |  1/2             1/2
                 |  1/3             1/3             1/3
                 |  1/4             1/4             1/4             1/4
                 |  1/5             1/5             1/5             1/5             1/5    
                 |                                    ...
                 |  1/N             1/N             1/N             1/N             1/N
____________________________________________________________________________________________
Total            |  H(N)         H(N) - 1        H(N) - 1.5      H(N) - 1.83     H(N) - 2.08

H(N) 描述了应该在索引 0 的链中收集多少元素。之后的每个链在统计上应该有更少的元素。

H(N) 也是直到并包括第 N 项的谐波级数的值。虽然没有广义的封闭形式来描述调和级数,但可以使用以下公式非常准确地近似该值,

H(N) ≈ ln(N) + 0.5772156649 + 1 / (2N) - 1 / (12N^2)

参考:https://math.stackexchange.com/questions/496116/is-there-a-partial-sum-formula-for-the-harmonic-series

“近似”部分可以归因于ln(N) + 0.5772156649之后的术语。 ln(N) 是最大的函数,因此摊销时间复杂度应该是O(log n)

我有什么遗漏吗?非常感谢您在这里澄清。

【问题讨论】:

  • 这个计算肯定需要至少两个数字:你要存储的元素的数量,以及哈希表中的桶数。桶的数量越多,链的预期长度就越短。公式中的桶数在哪里?
  • 这能回答你的问题吗? Is a Java hashmap search really O(1)?
  • 您还错过了可以随时调整哈希表大小以确保仅采用一小部分索引(并随后减少每个索引处的链接)的事实
  • 说“算法 X 具有复杂性 Y”是模棱两可的。存在(至少)三种复杂性:最坏情况、预期情况和最佳情况。典型哈希表的最坏情况获取(即所有键具有相同的哈希)是 O(n)。最好的情况通常是 O(1)(即所有键都有不同的散列)。预期情况取决于预期的散列分布和相对于 n 的桶数。无限桶将给出 O(1),而 1 个桶将给出 O(n)。您假设桶数为 n+1 的假设对于该算法来说并不典型。
  • @khelwood 在这个非常简单的实现中,桶的数量是size

标签: java data-structures time-complexity hashtable amortized-analysis


【解决方案1】:

将我的评论扩展为答案 - 让我们从这里开始:

与键关联的索引通常在最简单的哈希表实现中通过以下方式检索:

size++;
int hash = hashcode(key);
int index = hash % size;

这实际上不是大多数哈希表的实现方式。相反,大多数哈希表使用如下策略:

  1. 选择一些固定的起始槽数(例如,8 或 16 等)
  2. 添加元素后,将该元素放入槽hashcode(key) % tablesize
  3. 一旦表中的项目数与槽数的比率超过称为负载因子的阈值,执行 rehash :将表格大小加倍并通过使用新表格大小重新计算hashcode(key) % tablesize 来重新分配现有元素。 (这最后一步确保在调整表格大小的情况下仍然可以找到项目,并确保项目分布在整个表格中,而不仅仅是前几个槽。)

具体分析速度有多快取决于您实现哈希表的方式。如果您使用链式散列(每个项目被放入一个槽中,然后存储在一个单独的数组或包含该槽中所有项目的链表中)并且您的散列函数“或多或少”是均匀随机的,那么(直观地)项目可能会或多或少均匀地分布在表槽中。您可以通过假设您确实有一个随机散列函数来对此进行正式分析,在这种情况下,任何一个插槽中的预期项目数最多是表的负载因子(项目数与插槽数的比率)。负载因子通常选择为常数,这意味着每个插槽的预期项目数是该常数的上限,因此声称 O(1) 预期查找时间。 (对于线性探测哈希表的查找预期成本,您也可以得到类似的 O(1) 界限,但数学要复杂得多。)

“摊销”部分是由于重新散列步骤而出现的。这个想法是,在大多数情况下,插入不会将负载因子推到重新散列所需的阈值之上,因此它们非常快。但时不时地你必须重建表。假设您将表的大小加倍,您可以证明每次重新哈希都由线性数量的插入进行,这些插入不会触发重新哈希,因此您可以将重新哈希所需的工作回扣到之前的操作中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-09
    • 1970-01-01
    • 2015-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-12
    • 1970-01-01
    相关资源
    最近更新 更多