进行收集减法的最快方法

时间:2010-03-08 12:12:30

标签: javascript actionscript-3 performance

我有两套。 Set bSet a的子集。他们都是非常巨大的集合。 我想从a中减去b,这种常见操作的最佳实践是什么? 我写过很多这样的代码,我觉得它不高效。你有什么想法?

伪代码:(这不是Java API)。

for(int i = 0 ; i < a.size(); i++) {
          for (int j=0 ; j < b.size() ;j++) {
              // do comparison , if found equals ,remove from a
              break;
          }
 }

我想找到一个算法,不仅适用于集合,也适用于数组。

编辑:这里的Set不是JAVA API,它是一种数据结构。所以我不在乎Java API是否有一个removeAll()方法,我想为这个问题找到一个通用的解决方案,当我使用Javascript和Actionscript时,我遇到了很多类似的问题。

8 个答案:

答案 0 :(得分:8)

我认为你不会更快地得到它,但你的代码看起来会更简单,并且不会因a.removeAll(b);而变慢。 removeAll()是Java-API的一部分。

效率分析:你给出的代码示例是O(n ^ 2),它的扩展性不是很好,但也不是地球上最可怕的东西(指数复杂性是你不想要的东西)。只要您不知道Collection中数据的内部组织,就不会获得更好的性能。 removeAll()由类本身实现,并了解内部组织。因此,如果数据是在Hash中组织的,那么您可能会得到更好的结果,如果数据是在未排序的数组中组织的,那么复杂性将是相同的。如果一个新项已经在集合中,那么Set必须有效地查找,所以我怀疑某种Hash是内部表示,特别是如果该实现被称为HashSet。 : - )

编辑: OP改变了它的问题,提到它不仅仅适用于Java。 removeAll()是一个Java-API,所以这个(或类似的东西)可能在其他语言中不可用。如前所述,如果集合是未排序的数组而没有其他限制,则两个for循环已经是最快的解决方案。但如果数据组织不同,您可以选择更快的选项。如果两个集合是排序数据(在我的示例中首先是最小元素),您可以执行以下操作(将复杂性降低到O(n)):

int bIndex = 0;
for(int i = 0 ; i < a.size(); i++) {
          while (a[i] < b[bIndex]) {bIndex++;}
          if (a[i] == b[bIndex]) {markForRemoval(a[i]);} // I mark this only for removal, as the actual removal would make your index incorrect
}

如果数据在两个集合中被组织为散列,则您还只需要一个for循环,直接访问b中的元素。其他可能的数据组织也是可能的。

答案 1 :(得分:1)

最后,除了逐个比较元素并删除两者之外,没有太多选择。

为了做到这一点,你必须做一些奇特的事情,比如给所有集合成员一个唯一的值索引,并构造一个代表每个集合的大量布尔值,然后你可以做位操作从A中减去B考虑到创建唯一值索引和操纵非常大的位掩码的开销,我不知道是否会更快。

我知道你不关心Java解决方案,但是由于其他人推荐了removeAll(),我想指出它仍然在基本上做同样的事情。检查HashSet的源代码。

答案 2 :(得分:1)

如果维护集合使得元素在任何给定时间按排序顺序可用,则可以对两个集合执行单个线性传递,并在O(n)时间内创建差异。现在,再次, if ,您可以获得免费的元素的有序列表 - 也就是说维护(即add-element和remove-element操作)这些集合支付了按排序顺序保持元素可用的成本。

任何依赖于执行查找的“removeAll”操作都必然会比O(n)更糟。

(在我看来,差异集的构造 - 也就是说,通过两个列表的线性传递构造的答案 - 如果你不是非常小心,可能是O(n log n)。)

答案 3 :(得分:1)

嗯,已经指出了正确的想法:该集应该使用哈希来实现。哈希理想情况下具有O(1)访问成本,因此假设您可以确定哪个集更大(例如在插入/删除操作期间维护计数器),则整个操作可能会花费O(min(m,n))

在动作3中,您将使用Dictionary。只需使用元素作为键和值。

删除如下所示:

for each (var key:* in set2) {//a simple for-in loop will also do the trick, since keys and values are equal, but for-each-in loops perform faster
    delete set1[key];
}
在JavaScript中,您需要在插入时提供条目ID,因此您可以将这些ID用作地图中的键。只需将ID映射到原始值。

删除如下所示:

for (var key in set2) {
    delete set1[key];
}

答案 4 :(得分:1)

鉴于b是我的一个子集,我不知道为什么你的伪代码有2个循环。我只是:

foreach b in B
    remove b from A

实际上,运行时间与运行时间的比较取决于您如何将集合实现为数据结构。

答案 5 :(得分:0)

您在Set界面中看到了removeAll方法吗?

另请查看this stack overflow question

答案 6 :(得分:0)

我相信你会发现java.util.HashSet.removeAll(Collection toRemove)表现良好。 另一方面,如果您没有但是已经对已整理的集合进行了排序,那么您可以做得更好。

答案 7 :(得分:0)

编写时的操作是O(N ^ 2),但如果这些集很大,则可能需要使用散列。

// A is some kind of array, O(1) iteration
// B is a hash containing elements to remove, O(1) contains(elt)
List<T> removeAll(List<T> A, Set<T> B) {
  List<T> result; // empty, could preallocate at |A|
  for (elt : A) { // for each 'elt' belonging to A, hence O(|A|)
    if (! B.contains(elt) ) { // O(1) thanks to hash
      C.add(elt) ; // ensure this is O(1) with preallocation or linked list
    }
  }
  return result;
}

这需要索引集合B,因此您需要一个哈希函数。 在Java中,您可以在时间和内存中使用Set<T> Bh = new HashSet<T>(B);,即O(| B |)。 总的来说,我们在时间上得到O(| A | + | B |)并且在内存中大致为O(2 | A | +2 | B |))。 当然击败了removeAll的二次方,您将感受到差异(TM)。

将元素复制到新数组中可能更好(如伪代码中所做的那样),因为如果按顺序保留元素,则直接从A中删除元素可能会导致开销(A中的左移元素代价很高)。 / p>

相关问题