【问题标题】:Java HashSet worst case lookup time complexityJava HashSet 最坏情况查找时间复杂度
【发布时间】:2021-03-17 12:54:36
【问题描述】:

如果具有封闭哈希的哈希表/映射是最坏情况O(n),HashSet 是否也需要O(n) 时间进行查找,还是恒定时间?

【问题讨论】:

  • 在最坏的情况下,是的,O(n)。

标签: java performance hashmap big-o hashset


【解决方案1】:

我看到很多人说最坏的情况是 O(n)。这是因为旧的 HashSet 实现曾经使用 LinkedList 来处理同一个桶的冲突。但是,这不是一个确定的答案。

在 java 8 中,当存储桶的冲突次数增加时,这种 LinkedList 会被平衡二叉树取代。这将查找的最坏情况性能从 O(n) 提高到 O(log n)。

您可以在此处查看更多详细信息。

【讨论】:

    【解决方案2】:

    如前所述,最坏情况是 O(N),平均和摊销运行时间是恒定的。

    来自 GeeksForGeeks: HashSet 的底层数据结构是 hashtable。因此,HashSet 的添加、删除和查找(包含方法)操作的摊销(平均或通常情况)时间复杂度需要 O(1) 时间。

    【讨论】:

      【解决方案3】:

      如果您查看 HashSet 的实现(例如,来自 OpenJDK 8:https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/HashSet.java),您会发现它实际上只是构建在 HashMap 之上。相关代码sn-p在这里:

      public class HashSet<E>
      
          extends AbstractSet<E>
      
          implements Set<E>, Cloneable, java.io.Serializable
      
      {
          private transient HashMap<E,Object> map;
      
          // Dummy value to associate with an Object in the backing Map
      
          private static final Object PRESENT = new Object();
      
          /**
      
           * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
      
           * default initial capacity (16) and load factor (0.75).
      
           */
      
          public HashSet() {
      
              map = new HashMap<>();
      
          }
      
          public boolean add(E e) {
      
              return map.put(e, PRESENT)==null;
      
          }
      

      HashSet 尝试通过创建一个名为 PRESENT 的单个静态空 Object 值并将其用作 HashMap 中每个键/值条目的值部分来稍微优化内存使用。

      因此,无论使用HashMap 对性能有何影响,HashSet 都会有或多或少相同的性能影响,因为它实际上是在幕后使用HashMap

      直接回答您的问题:在最坏的情况下,是的,正如HashMap 的最坏情况复杂度是O(n)HashSet 的最坏情况复杂度也是O(n)

      值得注意的是,除非你有一个非常糟糕的哈希函数或使用一个小得离谱的哈希表,否则你不太可能在实践中看到最坏的情况下的性能。您必须将每个元素散列到哈希表中完全相同的存储桶中,因此性能本质上会降低到链表遍历(假设哈希表使用链表进行冲突处理,Java 就是这样做的)。

      【讨论】:

      • 嗯,这很有趣。为什么它在底层使用 HashMap?我会认为,如果有的话,也许 HashMap 在引擎盖下使用 HashSet。 HashSet 中的键值对到底是什么?
      • 它本质上是一个值为空对象的键。如果您滚动浏览该源并查看类,您可以看到一个静态成员private static final Object PRESENT = new Object();,然后HashSet 的相应Put(...) 方法就执行public boolean add(E e) { return map.put(e, PRESENT)==null; }。由于内存中只有一个 PRESENT 实例,因此开销非常小,因为所有条目都引用同一个 PRESENT 对象。
      • 至于“为什么”部分,我的猜测是,如果你设计了一个没有价值的HashSet,那么将它重新用于@987654342 会很困难@。另一方面,您可以使用HashSetHashMap 完成所有操作,因此重用它并最大限度地减少键/值对的值部分的内存开销对我来说是有意义的。您几乎可以肯定自己制作一个更高效的HashSet,但我猜想内存与代码重用的讨论Java 开发人员已经开始支持代码重用。 (我从来没有研究过Java本身,所以这只是代表我的猜测)
      【解决方案4】:

      HashMap 中查找元素时,它会执行 O(1) 计算以找到正确的存储桶,然后依次迭代那里的项目,直到找到与请求的键相等的项目,或者检查所有项目。

      在最坏的情况下,地图中的所有项目都具有相同的哈希码,因此存储在同一个桶中。在这种情况下,您需要依次迭代所有这些,这将是一个 O(n) 操作。

      HashSet 只是一个 HashMap,您不关心值,只关心键 - 在引擎盖下,它是一个 HashMap,其中所有值都是虚拟的 Object

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-06-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多