Це швидше додати до колекції, а потім відсортувати її, або додати до відсортованої колекції?


79

Якщо у мене 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());

Зверніть увагу, що отриманий збір ніколи не змінюється, тому сортування потрібно провести лише один раз.


Це залежить від порядку введення даних - напр. якщо ви вибираєте багато рядків і використовуєте ORDER BY, то це один випадок - якщо у вас є випадковий набір рекомендацій - інший.
Борис Треухов

Чому б не використовувати TreeMap замість цього?
Thorbjørn Ravn Andersen

TreeMap тут не допоможе, оскільки сортування має відбуватися за значеннями ( ComparableObject), а не за ключем ( Integer).
gutch

3
Також зверніть увагу, що набір підтримує лише унікальні записи. З іншого боку, колекція "значень" HashMap може містити дублікати. З цього боку, TreeSet не є хорошим рішенням.
rompetroll

@gutch, ти можеш знайти мою відповідь на " stackoverflow.com/questions/3759112/… " як корисну.
Річард,

Відповіді:


87

TreeSet має log(n)гарантію складності часу на add()/remove()/contains()методи. Сортування an ArrayListзаймає n*log(n)операції, але add()/get()займає лише 1операцію.

Отже, якщо ви переважно отримуєте і не сортуєте часто, ArrayListто кращий вибір. Якщо ви часто сортуєте, але не отримуєте такого, TreeSetнайкращим вибором буде.


У моєму випадку нам потрібно лише переглядати отриману колекцію, вона ніколи не змінюється. Отже, на основі вашої відповіді ArrayListтут кращий вибір.
gutch

Крім того, сортування масивів може здійснюватися паралельно і має набагато кращу продуктивність кешу.
kaiser

21

Теоретично сортування в кінці має бути швидшим. Підтримання відсортованого стану в процесі може зажадати додаткового часу процесора.

З точки зору CS, обидві операції є NlogN, але 1 сортування має мати нижчу константу.


4
+1 Один із тих випадків, коли теорія та реальність роз'єднуються. :) З мого досвіду, сортування в кінці, як правило, на порядок швидше ...
stevevls

Якщо вони не O (N), що було б у випадку цілочисельних даних. Пріоритетні черги також включають операції O (журнал N) для вставки, видалення та управління.
Річард,

10

Чому б не використовувати найкраще з обох світів? Якщо ви більше ніколи не використовуєте його, сортуйте за допомогою TreeSet та ініціалізуйте ArrayList із вмістом

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

РЕДАГУВАТИ:

Я створив орієнтир (ви можете отримати до нього доступ за адресою pastebin.com/5pyPMJav ), щоб перевірити три підходи (ArrayList + Collections.sort, TreeSet та мій найкращий з обох світів), і мій завжди виграє. Тестовий файл створює карту з 10000 елементами, значення яких мають навмисно жахливий компаратор, і тоді кожна з трьох стратегій отримує шанс а) сортувати дані та б) перебирати їх. Ось декілька зразків результатів (ви можете самі перевірити):

EDIT: Я додав аспект, який реєструє виклики до 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 в ітерації, чи є у мене помилка у моєму тесті?)

Застереження: Я знаю, що це, мабуть, жахливий орієнтир, але це допомагає донести точку до вас, і я, звичайно, не маніпулював ним, щоб перемогти у своєму підході.

(Код має залежність від apache commons / lang для будівельників equals / hashcode / compareTo, але його слід легко переформатувати)


3
Хіба це насправді не буде найгіршим з обох світів? Мені потрібна лише колекція в природному порядку, що і new TreeSet<ComparableObject>(map.values())повертається. Обгортання цього в an ArrayListпросто збирається додати непотрібні операції.
gutch

1
Кінцевою метою було відсортовано Collection... що TreeSetє. Я бачу, що тут жодне значення не перетворює набір у список.
Gunslinger47

це не обгортання, це ініціалізація. and and arraylist краще отримувати, тоді як набір дерев краще сортувати
Шон Патрік Флойд

4
Я сподіваюся на зусилля, які ви доклали для написання еталону! Однак я думаю, що в цьому є недолік. Здається, JVM запускає Transformerекземпляри, які пізніше у списку швидше, ніж попередні: поставте BestOfBothWorldsTransformerпершим, і він раптом працює набагато повільніше. Тож я переписав ваш орієнтир для випадкового вибору трансформатора та усереднення результатів. У моєму тесті TreeSetTransformerпослідовно б'ється BestOfBothWorldsTransformer, що послідовно б'ється ArrayListTransformer- зовсім не те, що я очікував! Однак різниця незначна. Дивіться pastebin.com/L0t5QDV9
gutch

1
Я знаю, яким буде ваше наступне запитання: а як щодо PriorityQueueTransformer? Хіба це не значно швидше, ніж інші? Ну так, це дуже шкода, хоча це не робить порядок правильним! Погляньте на списки, створені кожним трансформатором у моєму коді вище, і ви побачите, що PriorityQueueTransformer насправді не в порядку! Можливо, я використовую PriorityQueueнеправильно? У вас є приклад того, як ви насправді правильно сортуєте?
gutch

6

Обов’язково прочитайте мій коментар про TreeSet внизу, якщо ви вирішите реалізувати B)

Якщо ваша програма виконує лише епізодичні сортування, але багато разів перебирає їх, я б сказав, що вам краще використовувати прямий невідсортований список. Відсортуйте його один раз, а потім скористайтеся швидшою ітерацією. Ітерація особливо швидка в списку масивів.

Однак, якщо ви хочете, щоб порядок сортування був гарантований постійно, або, можливо, ви часто додаєте / видаляєте елементи, використовуйте відсортовану колекцію та виконайте хіт на ітерації.

Тож у вашому випадку я б сказав, що А) - кращий варіант. Список сортується один раз, не змінюється і тому виграє від того, що він масив. Ітерація повинна бути дуже швидкою, особливо якщо ви знаєте її ArrayList і можете безпосередньо використовувати ArrayList.get () замість ітератора.

Я б також додав, що TreeSet за визначенням є набором, що означає, що об'єкти є унікальними. TreeSet визначає рівність за допомогою порівнянняTo на вашому Comparator / Comparable. Ви можете легко виявити відсутні дані, якщо спробувати додати два об’єкти, порівняння яких повертає значення 0. Наприклад, додавання "C", "A", "B", "A" до TreeSet поверне "A", "B "," С "


1
Хороше запитання про TreeSetпотенційно відсутніх даних , якщо CompareTo повертає 0. Я визначив , що в даному конкретному випадку реалізація CompareTo ніколи не буде повертати 0, так як TreeSetі ArrayListбуде вести себе так само. Однак ця проблема мене вже виявила раніше, тож дякую за нагадування!
gutch

PriorityQueue, мабуть, краще для сортування списку, ніж TreeSet.
locka

так, у моєму тесті (див. мою відповідь) PriorityQueue перевершує TreeSet на 600-700%.
Шон Патрік Флойд,

PriorityQueueдійсно працює швидше, але коли я спробував, значення насправді не були відсортовані - очевидно, чому це було так швидко! Можливо, я неправильно витлумачив, як використовувати PriorityQueue ... приклад його фактичної роботи був би корисним.
gutch

PriorityQueue - це просто черга з компаратором / порівняльним тестом. Коли ви додаєте () елементи до черги, вставка порівнює новий елемент із уже наявними, щоб визначити позицію для вставки. Коли ви опитуєте () чергу або повторюєте її, вміст уже сортується. Я очікую, що вставка здійснюється за допомогою якогось рекурсивного алгоритму, тобто розділити список на дві частини і визначити, в яку половину його вставити, розділити на дві частини і так далі, тому продуктивність буде O (log N), що теоретично є таким самим, як TreeSet / TreeMap, але реалізація може зробити це швидшим.
locka

1

Collections.sort використовує mergeSort, який має O (nlog n).

TreeSetмає червоно-чорне дерево в основі, основні операції має O (logn). Звідси n елементів також має O (nlog n).

Отже, обидва є однаковим великим алгоритмом O.


6
Хоча це звучить правдою, воно покриває деякі важливі витрати. MergeSort працює за час O (n log n), але Red-Black вимагатиме O (n log n) для вставки та знову для видалення. Позначення big-O приховує важливі відмінності в алгоритмах.
Річард,

0

Вставка в SortedSet є O (log (n)) (АЛЕ! Поточний n, а не остаточний n). Вставка в список - 1.

Сортування за набором SortedSet вже включено до вставки, тому воно дорівнює 0. Сортування у списку - O (n * log (n)).

Отже, загальна складність SortedSet становить O (n * k), k <log (n) для всіх випадків, крім останнього. Натомість загальна складність списку - O (n * log (n) + n), отже O (n * log (n)).

Отже, SortedSet математично має найкращі показники. Але врешті-решт, у вас є набір замість списку (оскільки SortedList не існує), і Set надає вам менше функцій, ніж List. Отже, на мій погляд, найкращим рішенням для доступних функцій та продуктивності є те, що запропонував Шон Патрік Флойд:

  • використовуйте SortedSet для вставки,
  • помістіть SortedSet як параметр для створення списку для повернення.

0

Чудове запитання та чудові відповіді. Просто думав, що додам кілька моментів, щоб врахувати:

  1. Якщо ваша Колекція для сортування недовговічна, наприклад, використовується як аргумент методу, і вам потрібен список, відсортований у методі, тоді використовуйте Collections.sort (колекція). Або якщо це довгожитель, але сортувати його потрібно дуже рідко.

Обґрунтування: Відсортований збірник потрібен для чогось конкретного, і ви, ймовірно, не будете часто додавати чи видаляти. Тож ви насправді не піклуєтесь про елементи колекції, як тільки вона буде відсортована. Ви в основному:

сортувати -> використовувати -> забути

Якщо ви додасте новий елемент до відсортованої колекції, вам доведеться сортувати колекцію ще раз, оскільки порядок не гарантується при вставці нового елемента.

  1. Якщо ваша Колекція, яку потрібно відсортувати, є довгожителем та / або якщо це поле в класі, і Вам потрібно її сортувати постійно, тоді слід використовувати відсортовану структуру даних, таку як TreeSet.

Обґрунтування: Ви завжди дбаєте про порядок збору. Ви хочете, щоб його постійно сортували. Отже, якщо ви постійно додаєте або видаляєте елементи, ви маєте гарантію, що колекція сортується. Отже, в основному:

вставити / видалити -> використовувати його (весь час ви маєте гарантію, що колекція сортується)

Не існує конкретного моменту, коли вам потрібно сортувати колекцію, натомість ви хочете, щоб колекція постійно сортувалась.

Недоліком використання TreeSet є ресурси, необхідні для збереження відсортованої колекції. Він використовує червоно-чорне дерево, і йому потрібні O (log n) витрати часу на операції get, put.

Тоді як якщо ви використовуєте просту колекцію, таку як ArrayList, операції get, add мають O (1) постійний час.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.