是否为了常量时间包含()一个有效的策略,在ArrayList旁边创建一个HashMap?

时间:2015-07-29 15:32:36

标签: java data-structures time-complexity

我有一个ArrayList,可以是0到5000个项目的长度(非常大的对象)。

有一次我将它与另一个ArrayList进行比较,以找到它们的交集。我知道这是O(n ^ 2)。

在这个ArrayList旁边创建一个HashMap,实现恒定时间查找,这里有一个有效的策略,以降低O(n)的复杂度?或者另一个数据结构的开销是不值得的呢?我相信它不会占用额外的空间(除了参考文献)。

(我知道,我确定'这取决于我正在做什么'但我真的很想知道是否有任何缺点使它成为现实毫无意义,或者它实际上是一种常用的策略。是的,我知道有关过早优化的引用。我只是从理论的角度来看好奇。)

1 个答案:

答案 0 :(得分:3)

首先,一个简短的注释:

  

是的,我知道过早优化的引用。

您在这里询问的是“过早优化”!

你不是在谈论用一些奇数位运算替换乘法“因为它们更快(在90年代的PC上,在C程序中)”。您正在考虑应用程序模式的正确数据结构。您正在考虑应用案例(尽管您没有告诉我们有关它们的许多细节)。您正在考虑某种数据结构的选择对算法的渐近运行时间的影响。这是计划,或者 engineering ,但“过早优化”。

话虽如此,并告诉你你已经知道的事情:这取决于。

详细说明一下:这取决于您对这些集合执行的实际操作(方法),您执行的频率,它们的时间关键程度以及应用程序对内存的敏感程度。

(对于5000个元素,后者应该不是问题,因为只存储了引用 - 请参阅注释中的讨论)

一般来说,如果他们总是应该包含相同的元素,我也会对SetList一起存储犹豫不决。这个措辞是有意的:你应该始终了解两个集合之间的差异。主要是:Set只能包含每个元素一次,而List可能包含多次相同的元素。

对于所有提示,建议和注意事项,应牢记这一点。

但即使理所当然地认为列表在您的情况下始终只包含一次元素,那么您仍然必须确保两个集合都维护正确。如果你真的只是存储它们,你很容易造成微妙的错误:

private Set<T> set = new HashSet<T>();
private List<T> list = new ArrayList<T>();

// Fine
void add(T element)
{
    set.add(element);
    list.add(element);
}

// Fine
void remove(T element)
{
    set.remove(element);
    list.remove(element); // May be expensive, but ... well
}

// Added later, 100 lines below the other methods:
void removeAll(Collection<T> elements)
{
    set.removeAll(elements);
    // Ooops - something's missing here...
}

为了避免这种情况,人们甚至可以考虑创建一个专门的集合类 - 类似于FastContainsList,它结合了SetList,然后转发contains致电Set。但是你会注意到,违反与此类集合的CollectionList接口的合同将很难(或者可能不可能),除非该条款“你可能不会两次添加元素”成为合同的一部分......

所以这一切都取决于你想用这些方法做什么,以及你真正需要的接口。如果您不需要List的索引访问权限,那么这很容易。否则,请参考您的示例:

  

有一次我将它与另一个ArrayList进行比较,以找到它们的交集。我知道这是O(n ^ 2)。

您可以通过在本地创建来避免这种情况:

static <T> List<T> computeIntersection(List<T> list0, List<T> list1)
{
    Set<T> set0 = new LinkedHashSet<T>(list0);
    Set<T> set1 = new LinkedHashSet<T>(list1);
    set0.retainAll(set1);
    return new ArrayList<T>(set0);
}

这将有一个O(n)的运行时间。当然,如果您经常这样做,但很少更改列表的内容,可能有选项来避免副本,但由于上述原因,维护所需的数据结构可能会变得棘手。