如果我有Map
这样的话:
HashMap<Integer, ComparableObject> map;
我希望获得一个使用自然排序排序的值集合,哪种方法最快?
创建像ArrayList
这样的可排序集合的实例,添加值,然后对其进行排序:
List<ComparableObject> sortedCollection = new ArrayList<ComparableObject>(map.values());
Collections.sort(sortedCollection);
创建有序集合的实例,如TreeSet
,然后添加值:
Set<ComparableObject> sortedCollection = new TreeSet<ComparableObject>(map.values());
请注意,永远不会修改生成的集合,因此只需要进行一次排序。
答案 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进行排序并使用内容
初始化ArrayListList<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提出的解决方案:
答案 6 :(得分:0)
好问题和好答案。只是想我会考虑一些要点:
说明:对特定内容进行排序后的集合是必需的,您可能不会经常添加或删除。因此,对集合中的元素进行排序后,就不必再在意了。您基本上是:
排序->使用它->忘记
如果将新元素添加到已排序的集合中,则必须再次对集合进行排序,因为在插入新元素时不能保证顺序。
合理化:您一直都在关注收款顺序。您希望始终对其进行排序。因此,如果您不断添加或删除元素,则可以保证对集合进行了排序。所以基本上:
插入/删除->使用它(只要能保证对集合进行了排序就可以)
没有特定的时间需要对集合进行排序,相反,您希望一直对集合进行排序。
使用TreeSet的缺点是保留排序的集合所需的资源。它使用一棵红黑树,获取,放置操作需要O(log n)时间成本。
如果使用简单的集合(例如ArrayList),则get,add操作为O(1)恒定时间。