컬렉션에 추가 한 다음 정렬하거나 정렬 된 컬렉션에 추가하는 것이 더 빠릅니까?


79

나는이있는 경우 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());

결과 컬렉션은 수정되지 않으므로 정렬은 한 번만 수행하면됩니다.


입력 데이터의 순서에 따라 다릅니다. 많은 행을 가져오고 ORDER BY를 사용하는 경우 임의의 guid 집합이있는 경우 다른 경우입니다.
보리스 Treukhov

대신 TreeMap을 사용하지 않는 이유는 무엇입니까?
Thorbjørn Ravn Andersen

TreeMap은 ComparableObject키 ( Integer)가 아닌 값 ( ) 에서 정렬이 이루어져야하기 때문에 여기서 도움이되지 않습니다 .
gutch 2010-08-31

3
또한 세트는 고유 항목 만 지원합니다. 반면에 HashMap의 "값"컬렉션은 중복을 포함 할 수 있습니다. 그 각도에서 TreeSet은 좋은 솔루션이 아닙니다.
rompetroll

@gutch, " stackoverflow.com/questions/3759112/… " 에서 내 대답을 찾을 수 있습니다.
Richard

답변:


87

TreeSet은 메서드에 log(n)대한 시간 복잡도를 보장 add()/remove()/contains()합니다. 정렬 ArrayListn*log(n)작업을 수행하지만 add()/get()작업 만 1수행합니다.

따라서 주로 검색하고 자주 정렬하지 않는 ArrayList경우 더 나은 선택입니다. 자주 정렬하지만 그렇게 많이 검색하지 않으면 TreeSet더 나은 선택이 될 것입니다.


제 경우에는 결과 컬렉션을 반복하기 만하면되며 절대 수정되지 않습니다. 따라서 귀하의 답변 ArrayList에 따라 여기에 더 나은 선택이 있습니다.
gutch 2010-08-31

또한 배열 정렬은 병렬로 수행 될 수 있으며 캐시 성능이 훨씬 더 좋습니다.
kaiser

21

이론적으로는 마지막 정렬이 더 빨라야합니다. 프로세스를 통해 정렬 된 상태를 유지하려면 추가 CPU 시간이 필요할 수 있습니다.

CS 관점에서 두 작업 모두 NlogN이지만 1 개의 정렬은 더 낮은 상수를 가져야합니다.


4
+1 이론과 현실이 단절되는 사례 중 하나. :) 내 경험상, 마지막에 정렬하는 것이 훨씬 더 빠른 경향이 있습니다 ...
stevevls

정수 데이터의 경우 인 O (N)이 아니라면. 우선 순위 대기열에는 삽입, 제거 및 관리를위한 O (log N) 작업도 포함됩니다.
Richard

10

두 세계의 장점을 모두 사용하지 않으시겠습니까? 다시 사용하지 않는 경우 TreeSet을 사용하여 정렬하고 내용으로 ArrayList를 초기화하십시오.

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

편집하다:

나는 세 가지 접근 방식 (ArrayList + Collections.sort, TreeSet 및 두 세계의 최선의 접근 방식)을 테스트하기 위해 벤치 마크를 만들었으며 ( pastebin.com/5pyPMJav 에서 액세스 할 수 있습니다 ) 내 것이 항상 승리합니다. 테스트 파일은 값이 의도적으로 끔찍한 비교기를 갖는 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 빌더에 대해 아파치 커먼즈 / lang에 종속되지만 리팩토링하기가 쉽습니다)


3
실제로 두 세계 모두 최악이 아닐까요? 내가 필요한 것은 자연스러운 순서의 컬렉션 new TreeSet<ComparableObject>(map.values())뿐입니다. 그것을에 감싸면 ArrayList불필요한 작업이 추가됩니다.
gutch 2010-08-31

1
최종 목표는 분류 된 Collection어떤 ... TreeSet이다. 여기에 세트를 목록으로 변환하는 값이 없습니다.
Gunslinger47

랩핑이 아니라 초기화 중입니다. 과와의 ArrayList는 TreeSet의 정렬에 더 나은 동안 검색에 더 나은
숀 패트릭 플로이드를

4
벤치 마크를 작성하는 데 쏟은 노력에 감사드립니다! 그러나 나는 그것에 결함이 있다고 생각합니다. JVM은 이전 Transformer인스턴스보다 목록의 후반에있는 인스턴스를 더 빠르게 실행하는 것으로 보입니다 . BestOfBothWorldsTransformer먼저 넣으면 갑자기 훨씬 느리게 실행됩니다. 그래서 무작위로 변압기를 선택하고 결과를 평균화하기 위해 벤치 마크를 다시 작성했습니다. 내 테스트에서 TreeSetTransformer일관되게 뛰었습니다 BestOfBothWorldsTransformer. 일관되게 뛰었 ArrayListTransformer습니다. 하지만 그 차이는 작습니다. 참조 pastebin.com/L0t5QDV9
gutch

1
다음 질문이 무엇인지 알고 있습니다. PriorityQueueTransformer는 어떻습니까? 다른 것보다 엄청나게 빠르지 않습니까? 예, 주문이 정확하지 않지만 너무 나쁩니다! 위의 코드에서 각 변환기에 의해 생성 된 목록을 살펴보면 PriorityQueueTransformer가 실제로 순서가 아님을 알 수 있습니다! PriorityQueue잘못 사용하고 있습니까? 실제로 올바르게 정렬하는 예가 있습니까?
gutch

6

B)를 구현하기로 선택한 경우 하단의 TreeSet에 대한 내 의견을 읽으십시오.

앱이 가끔 정렬을 수행하지만이를 많이 반복하는 경우 간단한 정렬되지 않은 목록을 사용하는 것이 가장 좋습니다. 한 번만 정렬하면 더 빠른 반복의 이점을 얻을 수 있습니다. 반복은 배열 목록에서 특히 빠릅니다.

그러나 정렬 순서가 항상 보장되기를 원하거나 요소를 자주 추가 / 제거하는 경우 정렬 된 컬렉션을 사용하고 반복 작업을 수행하십시오.

따라서 귀하의 경우에는 A)가 더 나은 옵션이라고 말할 것입니다. 목록은 한 번 정렬되고 변경되지 않으므로 배열이되는 이점이 있습니다. 반복은 특히 ArrayList 를 알고 있고 Iterator 대신 ArrayList.get ()을 직접 사용할 수있는 경우 매우 빠릅니다 .

또한 TreeSet은 정의에 따라 개체가 고유하다는 것을 의미하는 집합이라는 것을 추가합니다. TreeSet은 Comparator / Comparable에서 compareTo를 사용하여 동등성을 결정합니다. compareTo가 0의 값을 반환하는 두 개체를 추가하려고하면 누락 된 데이터를 쉽게 찾을 수 있습니다. 예를 들어 TreeSet에 "C", "A", "B", "A"를 추가하면 "A", "B"가 반환됩니다. ", "씨"


1
에 대한 좋은 지적 TreeSet잠재적 compareTo와 나는이 특별한 경우에 compareTo와 구현이 0 반환하지 않습니다 것으로 확인 0을 반환하는 경우, 누락 된 데이터를 모두 이렇게 TreeSetArrayList동일하게 동작합니다. 그러나 나는 그 전에 그 문제로 잡혔으므로 상기시켜 주셔서 감사합니다!
gutch

PriorityQueue는 아마도 TreeSet보다 목록을 정렬하는 데 더 좋습니다.
locka

네, 내 벤치 마크에서 (내 대답 참조) PriorityQueue는 TreeSet보다 600 ~ 700 % 우수합니다.
Sean Patrick Floyd

PriorityQueue실제로 더 빨리 수행되지만, 시도했을 때 값이 실제로 정렬되지 않았습니다. 분명히 왜 그렇게 빨랐습니까! PriorityQueue 사용 방법을 잘못 해석했을 수 있습니다. 실제로 작동하는 예가 유용 할 것입니다.
gutch

PriorityQueue는 비교기 / 비교 테스트가있는 대기열입니다. 큐에 항목을 추가 ()하면 삽입은 새 항목을 이미있는 항목과 비교하여 삽입 할 위치를 결정합니다. 큐를 poll ()하거나 반복하면 내용이 이미 정렬되어 있습니다. 삽입은 일종의 재귀 알고리즘을 통해 수행 될 것으로 예상합니다. 즉, 목록을 두 개로 분할하고 삽입 할 절반을 결정하고 다시 두 개로 분할하는 등의 방식으로 성능이 이론적으로 동일한 O (log N)가 될 것입니다. TreeSet / TreeMap,하지만 구현이 더 빨라질 수 있습니다.
locka

1

Collections.sort O (nlog n)가있는 mergeSort를 사용합니다.

TreeSetRed-Black 트리를 기본으로하고 기본 작업에는 O (logn)가 있습니다. 따라서 n 개의 요소도 O (nlog n)를 갖습니다.

따라서 둘 다 동일한 big O 알고리즘입니다.


6
사실처럼 들리지만 몇 가지 중요한 비용을 충당합니다. MergeSort는 O (n log n) 시간에 작동하지만 Red-Black은 삽입 및 제거를 위해 O (n log n)가 필요합니다. big-O 표기법은 알고리즘의 중요한 차이점을 숨 깁니다.
Richard

0

SortedSet에 삽입하는 것은 O (log (n))입니다 (그러나 마지막 n이 아니라 현재 n). 목록에 삽입하는 것은 1입니다.

SortedSet에서의 정렬은 이미 삽입에 포함되어 있으므로 0입니다. List에서의 정렬은 O (n * log (n))입니다.

따라서 SortedSet 총 복잡도는 마지막을 제외한 모든 경우에 대해 O (n * k), k <log (n)입니다. 대신 목록 총 복잡도는 O (n * log (n) + n)이므로 O (n * log (n))입니다.

따라서 SortedSet은 수학적으로 최고의 성능을 발휘합니다. 그러나 결국에는 List 대신 Set이 있고 (SortedList가 존재하지 않기 때문에) Set는 List보다 적은 기능을 제공합니다. 제 생각에 사용 가능한 기능과 성능에 대한 최상의 솔루션은 Sean Patrick Floyd가 제안한 솔루션입니다.

  • 삽입을 위해 SortedSet을 사용하고,
  • 반환 할 List를 만들기위한 매개 변수로 SortedSet을 넣습니다.

0

훌륭한 질문과 훌륭한 답변. 고려해야 할 몇 가지 사항을 추가 할 것이라고 생각했습니다.

  1. 예를 들어, 정렬 할 컬렉션이 메서드에 대한 인수로 사용되는 짧은 수명이고 메서드 내에서 정렬 된 목록이 필요한 경우 Collections.sort (collection)를 사용합니다. 또는 수명이 긴 개체이지만 매우 드물게 정렬해야하는 경우.

근거 : 정렬 된 컬렉션은 특정 항목에 필요하며 자주 추가하거나 제거하지 않을 것입니다. 따라서 일단 정렬되면 컬렉션의 요소에 대해 신경 쓰지 않습니다. 당신은 기본적으로 :

정렬-> 사용-> 잊어

정렬 된 컬렉션에 새 요소를 추가하는 경우 새 요소를 삽입 할 때 순서가 보장되지 않으므로 컬렉션을 다시 정렬해야합니다.

  1. 당신의 컬렉션은 수명이 긴한다 분류 될 경우 및 / 또는 클래스 내에서 필드이며, 당신이 그것을에 정렬해야하는 경우 항상 다음과 같은 TreeSet의로 정렬 된 데이터 구조를 사용한다.

정당화 : 귀하는 항상 수금 순서에 관심이 있습니다. 항상 정렬되기를 원합니다. 따라서 지속적으로 요소를 추가하거나 제거하면 컬렉션이 정렬된다는 보장이 있습니다. 그래서 기본적으로:

삽입 / 제거-> 사용 (컬렉션이 정렬된다는 보장이 항상 있음)

컬렉션을 정렬해야하는 특정 순간이 없습니다. 대신 컬렉션이 항상 정렬되기를 원합니다.

TreeSet 사용의 단점은 정렬 된 컬렉션을 유지하는 데 필요한 리소스입니다. Red-black 트리를 사용하며 get, put 작업에 O (log n) 시간 비용이 필요합니다.

ArrayList와 같은 간단한 컬렉션을 사용하는 경우 get, add 작업은 O (1) 상수 시간입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.