【问题标题】:O(1) hash look ups?O(1) 哈希查找?
【发布时间】:2010-07-21 17:17:37
【问题描述】:

我遇到了一个断言,即 HashSet.Contains() 是一个 O(1) 操作。这让我很惊讶,因为我遇到的每一次关于散列的讨论都提到了冲突的可能性,可能会导致 O(n) 的运行时间。

出于好奇,我查看了 HashSet.Contains 和 HashTable.Contains 的文档。两种方法的文档都提出了相同的声明。

当我查看反射器时,HashSet.Contains() 是通过一个 for 循环实现的,它遍历包含具有相同哈希值的值的插槽列表。

诚然,那些关于散列的讨论也提到了一个好的散列算法可以避免冲突,在这种情况下,查找确实是 O(1)。但我对大 O 表示法的理解是,它是最坏情况下的运行时间,而不是最好的。

那么 O(1) 声明不正确吗?还是我错过了什么?

【问题讨论】:

  • 我讨厌大 O 表示法 =]
  • @Luiscencio 大 O 表示法只是让您告诉其他程序员函数将如何扩展的词。您认为哪些词可以快速让其他程序员对给定函数的扩展程度有一个半准确的认识?
  • [笑话] 你的“函数是 f*****g 吃掉 f*****g 处理器”
  • 我会很高兴发布一个断言,即哈希表查找具有 O(n!) 时间复杂度,这在技术上是正确的,尽管有点误导,看看它得到了多少反对票。

标签: c# .net hash


【解决方案1】:

但我对大 O 表示法的理解是,它是最坏情况下的运行时间,而不是最好的。

不幸的是,Big-O 在描述算法时没有“标准”。通常,它用于描述一般或一般情况,而不是最坏的情况。

来自Wikipedia

...这个符号现在也经常用于算法分析,以描述算法对计算资源的使用:最坏情况或平均情况...

在这种情况下,它描述了一个标准情况,给定了适当的散列。如果你有适当的散列,限制行为对于大小 N 将是恒定的,因此 O(1)。

【讨论】:

  • 是的。另一个突出的例子是快速排序——最坏的情况是 O(n^2),但通常被认为是 O(n log n),因为这是平均复杂度。
  • 我学的时候用大O来表示极限,不考虑最佳/最差/平均情况;但是,当最佳、最差和平均案例之间存在显着脱节时,通常使用大 O 来进行平均案例分析。在最坏的情况下使用大 theta。
  • 这令人惊讶,我原以为最坏的情况是更典型的用途,尽管(特别是对于散列)经常出现最坏的情况可能是寻找更好算法的动机。我当然可以看到一般/平均情况在哪里有用。在散列的情况下,我希望大部分时间都是 O(1)。
  • 哈希表查找只有 O(1) 摊销,见Staffan's answer。甚至这也需要一些假设。
  • @Gilles:但是,大多数散列结构都没有摊销。他们不会修改原始哈希以防止将来发生冲突,这是必需的。 (有一些可以,但 .NET 不...)
【解决方案2】:

一般,它是 O(1)。

【讨论】:

  • 即使考虑到已知的内置GetHashCode 的性能不佳?我不会依赖它是 O(1)...
  • @Stephen:你在说什么?此外,即使 GetHashCode 需要一个小时才能返回,它仍然是 O(1) - GetHashCode 的性能不会随着集合的大小而扩展。
  • @SLaks,我猜斯蒂芬指的是默认实现对散列的适用性差。见stackoverflow.com/questions/720177/…
  • @Slaks:本是正确的。并不是GetHashCode 需要很长时间才能返回,而是它的哈希算法很差。这会导致碰撞。这将“O(1)”反射答案推向了不正确的方向,因为它不再是平均的。
  • HashSet 使用IEqualityComparer<T>GetHashCode 方法,您可以在构造函数中指定该方法并影响性能(无论好坏)。
【解决方案3】:

对于正确实现的哈希表,查找具有amortized 恒定时间复杂度。

在实践中,如您所说,在发生冲突的情况下,单次查找可能是 O(n)。但是,如果您执行大量查找,则每个操作的平均时间复杂度是恒定的。

引用维基百科:

摊销分析与平均情况表现的不同之处在于不涉及概率;摊销分析保证每次操作的时间超过最坏情况的性能。

该方法需要知道哪些系列操作是可能的。这在数据结构中最常见,其状态在操作之间持续存在。基本思想是,最坏情况操作可以改变状态,使最坏情况在很长一段时间内不会再次发生,从而“摊销”其成本。

【讨论】:

  • 确实,在对哈希表复杂度的良好描述中必须提到摊销复杂度。但请注意,摊销 O(1) 复杂度需要假设密钥是充分随机分布的。如果攻击者选择要添加到散列中的密钥,他可以每次都强制发生冲突。这可以通过使用加密散列来避免,但是这些非常昂贵,因此您将获得具有惊人大常数的常数时间。另一种方法是在哈希中包含一个随机种子(perl 在某些时候这样做)。
【解决方案4】:

不,Big O 没有定义“最坏情况”,它定义了一个限制。随着项目数量的增加,基于散列的查找(具有提供有效值分布和低冲突率的良好散列算法)朝着一个恒定值前进(它们永远不会达到或那个恒定值,但这就是它的限制点)。

【讨论】:

    【解决方案5】:

    我相信这意味着平均 O(1)。

    【讨论】:

      【解决方案6】:

      不,Big-O 表示法不一定限于最坏的情况。通常,您会看到针对最佳情况、平均情况和最坏情况发布的 Big-O。只是大多数人倾向于关注最坏的情况。除了在哈希表的情况下,最坏情况很少发生,因此使用平均情况往往更有用。

      是的,一个好的散列函数可以降低发生冲突的概率。错误的哈希函数可能会导致聚类效应(其中不同的值哈希到完全相同的值或接近相同的值)。很容易证明,HashSet 确实可以通过实现 GetHashCode 函数使其始终返回相同的值的方式变为 O(n)。

      简而言之,是的,HashSetDictionary 可以描述为具有 O(1) 运行时复杂度,因为重点是在平均情况下。

      顺便说一句,Big-O 也可用于分析摊销复杂度。摊销的复杂性是一系列单独的(有时甚至是不同的)操作组合在一起时的行为方式,就好像它们是一个大操作一样。例如,据说一棵展开树具有平均 O(log(n)) 的搜索、插入和删除复杂性,即使每个的最坏情况可能是 O(n),而最好情况是 O(1)。

      【讨论】:

        【解决方案7】:

        我对 Big Oh 的理解是,“最坏情况”通常是指涉及的元素数量。因此,如果一个函数对 10 个元素执行 O(n),但对 100 个或更多元素执行 O(n squared)(不确定是否确实存在这样的算法),则该算法被认为是 O(n squared)。

        【讨论】:

          【解决方案8】:

          O(1) 并不一定意味着“最坏情况”。对于散列,人们通常说“预期”查找时间为 O(1),因为散列冲突的概率很小。

          【讨论】:

          • 这让我感到惊讶——我发现在各个地方提到查找的措辞并没有说“预期”或“典型”。他们说“是”,这意味着永远。
          【解决方案9】:

          哈希表不仅具有平均情况下的性能 O(1),而且如果哈希函数是随机的,对于任何给定的百分比 P

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-11-10
            • 1970-01-01
            • 2012-07-17
            • 2017-02-23
            • 2021-12-18
            • 2018-02-13
            • 2018-07-14
            • 1970-01-01
            相关资源
            最近更新 更多