【问题标题】:What is the time and space complexity of method retainAll when used on HashSets in Java?在 Java 的 HashSet 上使用方法 retainAll 的时间和空间复杂度是多少?
【发布时间】:2014-07-15 09:44:07
【问题描述】:

例如在下面的代码中:

public int commonTwo(String[] a, String[] b)
{
    Set common = new HashSet<String>(Arrays.asList(a));
    common.retainAll(new HashSet<String>(Arrays.asList(b)));
    return common.size();
} 

【问题讨论】:

  • rawtype 是怎么回事?
  • 你说的是哪种 rawtype?
  • 没什么特别的,只是使用运行时多态的习惯。
  • @Anirudh 你误会了。你有一个Set 而不是Set&lt;String&gt;。这是一个原始的泛型类型,强烈不鼓励使用它。

标签: java big-o hashset


【解决方案1】:

让我们仔细阅读the coderetainAll 方法继承自 AbstractCollection 并且(至少在 OpenJDK 中)看起来像这样:

public boolean retainAll(Collection<?> c) {
    boolean modified = false;
    Iterator<E> it = iterator();
    while (it.hasNext()) {
        if (!c.contains(it.next())) {
            it.remove();
            modified = true;
        }
    }
    return modified;
}

这里有一个重要的地方要注意,我们遍历this.iterator() 并调用c.contains。所以时间复杂度是n调用c.contains,其中n = this.size()最多n调用it.remove()

重要的是contains 方法在其他 Collection 上调用,因此复杂性取决于其他Collection contains 的复杂性。

所以,同时:

Set<String> common = new HashSet<>(Arrays.asList(a));
common.retainAll(new HashSet<>(Arrays.asList(b)));

应该是O(a.length),因为HashSet.containsHashSet.remove 都是O(1)(摊销)。

如果你打电话

common.retainAll(Arrays.asList(b));

然后由于Arrays.ArrayList 上的O(n) contains 这将变为O(a.length * b.length) - 即通过花费O(n) 将数组复制到HashSet 您实际上可以更快地调用retainAll

就空间复杂度而言,retainAll 不需要额外的空间(Iterator 之外),但是您的调用实际上在空间方面非常昂贵,因为您分配了两个新的 HashSet 实现,它们实际上是完全成熟的HashMap

还有两点需要注意:

  1. 没有理由从a 中的元素分配HashSet - 可以使用更便宜的集合,该集合还具有从中间删除的O(1),例如LinkedList。 (更便宜的内存和构建时间 - 没有构建哈希表)
  2. 当您创建新的集合实例时,您的修改将丢失,并且只返回 b.size()

【讨论】:

  • 感谢您的解释,关于空间复杂性,我认为使用 HashSets 会导致散列值随着方法参数大小的增加而增加。你不认为方法的空间复杂度是线性增加还是 O(n) 增加?
  • @Anirudh 正如我所说的,但是您的调用实际上在空间方面非常昂贵,因为您分配了两个新的HashSet 实现。由于所有卫星数据,这些确实非常占用内存。 retainAll 方法本身会使用 O(1) 我估计的内存。
【解决方案2】:

可以在java.util.AbstractCollection 类中找到实现。它的实现方式是这样的:

public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            if (!c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }

因此它将迭代您的 common 集合中的所有内容,并检查作为参数传递的集合是否包含此元素。

在你的情况下,两者都是HashSets,因此它将是 O(n),因为包含应该是 O(1) 摊销和迭代你的 common 集是 O(n)。

您可以进行的一项改进就是不要将a 复制到新的HashSet 中,因为无论如何它都会被迭代,您可以保留一个列表。

【讨论】:

  • 您最后的评论不起作用,因为如果不是因为 Arrays.ArrayList 不支持 remove(),数组 a 将被对 it.remove() 的调用修改.而Arrays.ArrayList.size() 总是返回包装数组的长度。
猜你喜欢
  • 2021-04-20
  • 2012-04-09
  • 2020-05-03
  • 2018-01-18
  • 2011-05-14
  • 1970-01-01
  • 2012-04-06
  • 2020-09-10
相关资源
最近更新 更多