从另一个数组列表中删除一个数组列表元素的最佳方法
Posted
技术标签:
【中文标题】从另一个数组列表中删除一个数组列表元素的最佳方法【英文标题】:Best way to remove one arraylist elements from another arraylist 【发布时间】:2016-09-19 21:22:15 【问题描述】:在 Java (7,8) 中,从另一个 Arraylist
中消除 integer
元素的最佳性能方法是什么。所有元素在第一个和第二个列表中都是唯一的。
目前我知道API方法removeall
并这样使用:
tempList.removeAll(tempList2);
当我使用超过 10000 个元素的数组列表操作时出现问题。例如,当我删除 65000 个元素时,延迟似乎约为 2 秒。但我需要使用超过 1000000 个元素的更大列表。
这个问题的策略是什么?
也许新的 Stream API 应该可以解决它?
【问题讨论】:
将 tempList2 设为 HashSet,您可能会看到性能显着提升。 您是否考虑过先对两个列表进行排序,然后简单地遍历第一个列表(您要从中删除项目的那个)?编辑:基本上是@Eran 在下面提出的。 相关:Insight into Collections removeAll method 【参考方案1】:好吧,既然removeAll
会检查tempList
的每个元素是否出现在tempList2
中,所以运行时间与第一个列表的大小乘以第二个列表的大小成正比,即@987654324 @ 除非两个列表之一非常小,可以认为是“恒定大小”。
另一方面,如果您预先对列表进行排序,然后通过一次迭代对两个列表进行迭代(类似于合并排序中的合并步骤),则排序将采用 O(NlogN)
和迭代 @987654326 @,总运行时间为O(NlogN)
。这里N
是两个列表中较大者的大小。
如果您可以用排序结构替换列表(可能是TreeSet
,因为您说元素是唯一的),您可以在线性时间内实现removeAll
,因为您不必进行任何排序。
我还没有测试过,但是这样的东西可以工作(假设 tempList
和 tempList2
都已排序):
Iterator<Integer> iter1 = tempList.iterator();
Iterator<Integer> iter2 = tempList2.iterator();
Integer current = null;
Integer current2 = null;
boolean advance = true;
while (iter1.hasNext() && iter2.hasNext())
if (advance)
current = iter1.next();
advance = false;
if (current2 == null || current > current2)
current2 = iter2.next();
if (current <= current2)
advance = true;
if (current == current2)
iter1.remove();
【讨论】:
Eran,谢谢您的回复。你能分享一个你看到的代码sn-p吗? (单次迭代)【参考方案2】:我怀疑从 ArrayList 中删除会影响性能,因为当删除中间的元素时,列表可能会被分割,或者在删除元素后必须压缩列表。这样做可能会更快:
-
创建要删除的元素的“集合”
创建一个您需要的新结果 ArrayList,将其命名为 R。您可以在构造时给它足够的大小。
遍历原始列表,您需要从中删除元素,如果在 Set 中找到该元素,则不要将其添加到 R,否则添加它。
这应该有O(N)
;如果创建 Set 并在其中查找,则假定为常量。
【讨论】:
【参考方案3】:tl;博士:
保持简单。使用
list.removeAll(new HashSet<T>(listOfElementsToRemove));
改为。
正如 Eran 在 his answer 中已经提到的那样:性能低下是因为通用 removeAll
实现的 伪代码 是
public boolean removeAll(Collection<?> c)
for (each element e of this)
if (c.contains(e))
this.remove(e);
因此,在要删除的元素列表上完成的contains
调用将导致 O(n*k) 性能(其中 n
是要删除的元素数,k
是要删除的元素数调用该方法的列表中的元素)。
天真地,可以想象对List
的this.remove(e)
调用也可能有O(k),并且这种实现也将具有二次复杂度。但事实并非如此:您提到列表专门是 ArrayList
实例。并且 ArrayList#removeAll
方法被实现为委托给一个名为 batchRemove
的方法,该方法直接对底层数组进行操作,并且不单独删除元素。
因此,您所要做的就是确保在包含要删除的元素的集合中查找速度很快——最好是 O(1)。这可以通过将这些元素放入Set
来实现。最后可以写成
list.removeAll(new HashSet<T>(listOfElementsToRemove));
旁注:
Eran 的答案恕我直言有两个主要缺点:首先,它需要对列表进行排序,即 O(n*logn) - 而且根本没有必要。但更重要的是(而且很明显):排序可能会改变元素的顺序!如果根本不希望这样做怎么办?
远程相关:removeAll
实现还涉及其他一些微妙之处。例如,HashSet removeAll method is surprisingly slow 在某些情况下。尽管当要删除的元素存储在列表中时,这也归结为 O(n*n),但在这种特殊情况下,确切的行为可能确实令人惊讶。
【讨论】:
以上是关于从另一个数组列表中删除一个数组列表元素的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章