添加到集合然后对其进行排序或添加到已排序的集合是否更快?

时间:2010-08-31 09:12:46

标签: java sorting collections

如果我有Map这样的话:

HashMap<Integer, ComparableObject> map;

我希望获得一个使用自然排序排序的值集合,哪种方法最快?

(A)

创建像ArrayList这样的可排序集合的实例,添加值,然后对其进行排序:

List<ComparableObject> sortedCollection = new ArrayList<ComparableObject>(map.values());
Collections.sort(sortedCollection);

(B)

创建有序集合的实例,如TreeSet,然后添加值:

Set<ComparableObject> sortedCollection = new TreeSet<ComparableObject>(map.values());

请注意,永远不会修改生成的集合,因此只需要进行一次排序。

7 个答案:

答案 0 :(得分:79)

TreeSet对log(n)方法有add()/remove()/contains()时间复杂度保证。 对ArrayList进行排序需要n*log(n)次操作,但add()/get()仅需1次操作。

因此,如果您主要检索并且不经常排序,ArrayList是更好的选择。如果你经常排序,但不要检索那么多TreeSet将是一个更好的选择。

答案 1 :(得分:16)

理论上,最后的排序应该更快。 在整个过程中维护已排序的状态可能需要额外的CPU时间。

从CS的角度来看,两个操作都是NlogN,但是1种应该具有较低的常量。

答案 2 :(得分:8)

为什么不使用两全其美?如果您再也不使用它,请使用TreeSet进行排序并使用内容

初始化ArrayList
List<ComparableObject> sortedCollection = 
    new ArrayList<ComparableObject>( 
          new TreeSet<ComparableObject>(map.values()));

编辑:

我已经创建了一个基准测试(您可以在pastebin.com/5pyPMJav访问它)来测试三种方法(ArrayList + Collections.sort,TreeSet以及我最好的两种方法)并且我总是获胜。测试文件创建一个包含10000个元素的映射,其中的值有一个故意糟糕的比较器,然后三个策略中的每一个都有机会a)对数据进行排序,b)迭代它。这是一些示例输出(您可以自己测试):

编辑:我添加了一个记录Thingy.compareTo(Thingy)调用的方面,我还添加了一个基于PriorityQueues的新策略,它比以前的任何一个解决方案都要快得多(至少在排序方面)。

compareTo() calls:123490
Transformer ArrayListTransformer
    Creation: 255885873 ns (0.255885873 seconds) 
    Iteration: 2582591 ns (0.002582591 seconds) 
    Item count: 10000

compareTo() calls:121665
Transformer TreeSetTransformer
    Creation: 199893004 ns (0.199893004 seconds) 
    Iteration: 4848242 ns (0.004848242 seconds) 
    Item count: 10000

compareTo() calls:121665
Transformer BestOfBothWorldsTransformer
    Creation: 216952504 ns (0.216952504 seconds) 
    Iteration: 1604604 ns (0.001604604 seconds) 
    Item count: 10000

compareTo() calls:18819
Transformer PriorityQueueTransformer
    Creation: 35119198 ns (0.035119198 seconds) 
    Iteration: 2803639 ns (0.002803639 seconds) 
    Item count: 10000

奇怪的是,我的方法在迭代中表现最好(我原本以为迭代中的ArrayList方法没有差异,我的基准测试中是否有错误?)

免责声明:我知道这可能是一个糟糕的基准,但它有助于明确指出你,我当然没有操纵它来让我的方法获胜。

(对于equals / hashcode / compareTo构建器,代码依赖于apache commons / lang,但它很容易重构出来)

答案 3 :(得分:5)

如果您选择实施B)

,请务必阅读我对底部TreeSet的评论

如果您的应用只是偶尔进行排序但经过多次迭代,我会说您最好使用简单的未排序列表。将其排序一次,然后从更快的迭代中获益。迭代在数组列表上特别快。

但是,如果您希望始终保证排序顺序,或者您可能经常添加/删除元素,那么请使用已排序的集合并在迭代时执行命中。

所以在你的情况下我会说A)是更好的选择。该列表排序一次,不会更改,因此可以从阵列中获益。迭代应该非常快,特别是如果你知道它的一个ArrayList并且可以直接使用ArrayList.get()而不是Iterator。

我还要补充一点,根据定义,TreeSet是一个Set,它意味着对象是唯一的。 TreeSet通过在Comparator / Comparable上使用compareTo来确定相等性。如果您尝试添加compareTo返回值为0的两个对象,则可能很容易发现自己缺少数据。将“C”,“A”,“B”,“A”添加到TreeSet将返回“A”,“B”,“C”

答案 4 :(得分:1)

Collections.sort使用具有O(nlog n)的mergeSort。

TreeSet底层有红黑树,基本操作有O(logn)。因此,n个元素也具有O(nlog n)。

所以两者都是相同的大O算法。

答案 5 :(得分:0)

在SortedSet中插入是O(log(n))(BUT!当前n而不是最终n)。 在List中插入是1。

SortedSet中的排序已包含在插入中,因此它为0。 列表中的排序是O(n * log(n))。

因此,SortedSet总复杂度为O(n * k),k <1。 log(n)表示所有情况,但是最后一个。 相反,List总复杂度为O(n * log(n)+ n),因此O(n * log(n))。

因此,SortedSet在数学上具有最佳性能。但最后,你有一个Set而不是List(因为SortedList不存在),而Set提供的功能比List少。 因此,在我看来,可用功能和性能的最佳解决方案是Sean Patrick Floyd提出的解决方案:

  • 使用SortedSet进行插入,
  • 将SortedSet作为创建要返回的List的参数。

答案 6 :(得分:0)

好问题和好答案。只是想我会考虑一些要点:

  1. 例如,如果要排序的Collection是短暂的(例如,用作方法的参数),并且您需要在该方法内对列表进行排序,请使用Collections.sort(collection)。或者,如果它是寿命很长的对象,但是您很少需要对其进行排序。

说明:对特定内容进行排序后的集合是必需的,您可能不会经常添加或删除。因此,对集合中的元素进行排序后,就不必再在意了。您基本上是:

排序->使用它->忘记

如果将新元素添加到已排序的集合中,则必须再次对集合进行排序,因为在插入新元素时不能保证顺序。

  1. 如果要排序的收藏集是长期的,并且/或者如果它是类中的一个字段,并且您需要始终对其进行排序,那么您应该使用排序后的数据结构,例如作为TreeSet。

合理化:您一直都在关注收款顺序。您希望始终对其进行排序。因此,如果您不断添加或删除元素,则可以保证对集合进行了排序。所以基本上:

插入/删除->使用它(只要能保证对集合进行了排序就可以)

没有特定的时间需要对集合进行排序,相反,您希望一直对集合进行排序。

使用TreeSet的缺点是保留排序的集合所需的资源。它使用一棵红黑树,获取,放置操作需要O(log n)时间成本。

如果使用简单的集合(例如ArrayList),则get,add操作为O(1)恒定时间。