Java 哈希表get(kType键)的时间复杂度是如何分摊的O(1)而不是O(logn)?

Java 哈希表get(kType键)的时间复杂度是如何分摊的O(1)而不是O(logn)?,java,data-structures,time-complexity,hashtable,amortized-analysis,Java,Data Structures,Time Complexity,Hashtable,Amortized Analysis,在哈希表的最简单实现中,与键关联的索引通常按以下方式检索: size++; int hash = hashcode(key); int index = hash % size; size++; int hash = hashcode(key); int index = hash % size; 对于任意键,我们可以说索引将是范围[0,size-1]内的整数,每个结果的概率相等。下表描述了添加N个元素后前5个指数的概率 Index | 0 1

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

size++;
int hash = hashcode(key);
int index = hash % size;
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处应收集多少元素。之后的每个链都应该具有统计上较少的元素

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)
也是第N项之前(包括第N项)谐波级数的值。虽然没有描述谐波级数的广义闭合形式,但可以使用以下公式非常精确地近似该值

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

参考:

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


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

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

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

size++;
int hash = hashcode(key);
int index = hash % size;
size++;
int hash = hashcode(key);
int index = hash % size;
实际上,大多数哈希表并不是这样实现的。相反,大多数哈希表使用如下策略:

  • 选择一些固定的插槽起始数量(例如,8或16等)
  • 添加元素时,将该元素放入插槽
    hashcode(key)%tablesize
  • 一旦表中的项目数与插槽数的比率超过一个称为加载系数的阈值,执行重新刷新:将表的大小加倍,并通过使用新的表大小重新计算
    hashcode(key)%tablesize
    来重新分布现有元素。(最后一步确保在表已调整大小的情况下仍能找到项,并确保项分布在整个表中,而不仅仅是前几个插槽中。)
  • 对其速度的准确分析将取决于您如何实现哈希表。如果使用链式散列(将每个项放入一个槽中,然后存储在包含该槽中所有项的单独数组或链表中),并且散列函数是“或多或少”一致随机的,那么(直观地)这些项可能或多或少均匀地分布在表槽中。通过假设确实有一个随机散列函数,您可以对此进行正式分析,在这种情况下,任何一个插槽中的预期项目数最多是表的负载因子(项目数与插槽数的比率)。负载因子通常被选择为一个常数,这意味着每个插槽的预期项数是该常数的上限,因此声明了O(1)个预期查找时间。(对于线性探测哈希表的预期查找成本,您也可以得到类似的O(1)界限,但数学方面要复杂得多。)


    “摊销”部分是由于重新灰化步骤而产生的。这个想法是,大多数情况下,插入不会将负载因子推到重新加载所需的阈值之上,因此它们非常快。但有时您确实需要重建表。假设表的大小是原来的两倍,则可以显示每次重新刷新都是通过线性数量的插入来进行的,而这些插入不会触发重新刷新,因此可以将重新刷新所需的工作量倒扣到以前的操作中。

    当然,此计算中至少需要两个数字:存储的元素数量,以及哈希表中的桶数。铲斗数量越大,链条的预期长度越短。公式中的桶数在哪里?这是否回答了您的问题?您还忽略了一个事实,即可以随时调整哈希表的大小,以确保只获取一小部分索引(并随后减少每个索引的链接),即“算法X具有复杂性Y”是不明确的。有(至少)三种复杂情况:最坏情况、预期情况和最佳情况。一个典型的哈希表的最坏情况get(即所有键都有相同的哈希)是O(n)。最好的情况通常是O(1)(即所有键都有不同的散列)。预期的大小写取决于预期的散列分布和相对于n的桶数。无限桶将给出O(1),而1桶将给出O(n)。您假设的存储桶数为n+1对于该算法来说并不典型。@khelwood在这个非常简单的实现中,存储桶数为
    size