Чому Collections.sort використовує Mergesort, а Arrays.sort - ні?


95

Я використовую JDK-8 (x64). Для Arrays.sort(примітивів) я знайшов таке в документації Java:

Алгоритм сортування є Dual-Pivot Quicksort Володимира Ярославського, Джон Бентлі, і Джошуа Bloch.`

Для Collections.sort(об'єктів) я знайшов цей "Timsort":

Ця реалізація є стабільним, адаптивним, ітеративним злиттям ... Ця реалізація скидає вказаний список у масив, сортує масив та перебирає список, скидаючи кожен елемент із відповідної позиції в масиві.

Якщо Collections.sortвикористовується масив, чому він просто не викликає Arrays.sortабо не використовує двоповоротну QuickSort ? Навіщо використовувати Mergesort ?


8
Це javadoc для масивів примітивів - масиви Об’єктів сортуються за допомогою простого сортування.
assylias

2
mergesort надає ulog завжди, тоді як quicksort може колись дати nlogn2, загалом розмір масивів не такий великий, але колекції легко піднімаються до мільйонів записів, тому ризикувати nlogn2 не варто PS nlogn2 я мав на увазі квадрат n
Кумар Саураб

O (n ^ 2) для швидкого сорту є крайнім гіршим випадком. На практиці це відбувається швидше
James Wierzba

але ти не можеш ігнорувати цих кейсів, роблячи апі
Кумар Саураб

2
Це посилання дуже пов’язане.
qartal

Відповіді:


99

API гарантує стабільне сортування, яке Quicksort не пропонує. Однак при сортуванні примітивних значень за їх природним порядком ви не помітите різниці, оскільки примітивні значення не мають ідентичності. Тому Quicksort може використовуватися для примітивних масивів і буде використовуватися, коли він вважатиметься більш ефективним¹.

Щодо об’єктів, які ви можете помітити, коли об’єкти з різною ідентичністю, які вважаються рівними відповідно до їх equalsреалізації або передбаченого, Comparatorзмінюють їх порядок. Тому Quicksort не є варіантом. Отже, використовується варіант MergeSort , поточні версії Java використовують TimSort . Це стосується обох, Arrays.sortі Collections.sort, хоча в Java 8, Listсам може замінити алгоритми сортування.


Advantage Перевага в ефективності Quicksort вимагає меншої пам’яті при роботі на місці. Але він має найгіршу продуктивність і не може використовувати прогони попередньо відсортованих даних у масиві, що робить TimSort .

Тому алгоритми сортування перероблялись з версії на версію, залишаючись у теперішньому оманливому класі DualPivotQuicksort. Крім того, документація не наздогнала, що показує, що взагалі погана ідея називати внутрішньо використовуваний алгоритм у специфікації, коли це не потрібно.

Поточна ситуація (включаючи Java 8 до Java 11) така:

  • Як правило, методи сортування для примітивних масивів використовуватимуть Quicksort лише за певних обставин. Для більших масивів вони намагатимуться спочатку ідентифікувати прогони попередньо відсортованих даних, як це робить TimSort , і об’єднуватимуть їх, коли кількість прогонів не перевищує певного порогу. В іншому випадку вони повернуться до Quicksort , але з реалізацією, яка повернеться до Insertion sort для малих діапазонів, що впливає не тільки на малі масиви, але і на рекурсію швидкого сортування.
  • sort(char[],…)та sort(short[],…)додайте ще один особливий випадок, щоб використовувати сортування підрахунку для масивів, довжина яких перевищує певний поріг
  • Аналогічним чином, sort(byte[],…)використовуватиметься Counting sort , але з набагато меншим порогом, що створює найбільший контраст з документацією, оскільки sort(byte[],…)ніколи не використовує Quicksort. Він використовує сортування вставки лише для малих масивів та сортування підрахунку в іншому випадку.

1
Хм, цікаво, що Javadoc Collections.sort говорить: "Цей сорт гарантовано стабільний", але оскільки він делегує List.sort, який може бути замінений реалізаціями списків, стабільне сортування не може бути гарантоване Collections.sort для всього списку реалізації. Або я щось пропускаю? А List.sort не вимагає стабільності alogirthm сортування.
Puce

11
@Puce: це просто означає, що відповідальність за цю гарантію тепер лежить на руках тих, хто застосовує переважний List.sortметод. Collections.sortніколи не може гарантувати правильну роботу для кожної Listреалізації, оскільки вона не може гарантувати, наприклад, те, що Listне помилково змінює свій вміст. Все зводиться до того, що гарантія Collections.sortзастосовується лише до правильних Listреалізацій (та правильних Comparatorабо equalsреалізацій).
Holger

1
@Puce: Але ви маєте рацію, Javadoc не однаково явно говорить про це обмеження в обох методах. Але, принаймні, найновіша документація стверджує, що Collections.sortбуде делеговано List.sort.
Holger

@Puce: є маса прикладів цього, де важливі властивості не є частиною типу, а лише згадуються в документації (і, отже, не перевіряються компілятором). Система типів Java просто занадто слабка, щоб виражати якісь цікаві властивості. (У цьому відношенні він мало чим відрізняється від мови, що динамічно набирається, там теж властивості визначені в документації, і програміст повинен переконатися, що вони не порушені.) Це власне йде ще далі: чи помітили ви що Collections.sortнавіть не згадує у своєму підписі типу, що вихід сортується?
Jörg W Mittag

1
У мові з більш виразною системою типів, тип повернення Collections.sortбуде приблизно таким, як "колекція того самого типу і довжини, що і вхідні дані, із властивостями, які: 1) кожен елемент, що присутній у вхідному документі, також присутній у вихідних даних, 2 ) для кожної пари елементів з виходу лівий не перевищує правий, 3) для кожної пари рівних елементів з виходу індекс лівого у вході менший за правий "або щось на зразок що.
Jörg W Mittag

20

Я не знаю про документацію, але реалізація java.util.Collections#sortв Java 8 (HotSpot) виглядає так:

@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

І List#sortмає таку реалізацію:

@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

Отже, зрештою, Collections#sortвикористання Arrays#sort(об’єктних елементів) за лаштунками. Ця реалізація використовує сортування злиття або сортування тим.


16

Згідно з Javadoc, за допомогою Quicksort сортуються лише примітивні масиви. Масиви об’єктів також сортуються за допомогою Mergesort.

Тож Collections.sort, схоже, використовує той самий алгоритм сортування, що і Arrays.sort для об’єктів.

Іншим питанням було б, чому для примітивних масивів використовується інший алгоритм сортування, ніж для масивів Object?


2

Як зазначено у багатьох відповідях.

Quicksort використовується Arrays.sort для сортування примітивних колекцій, оскільки стабільність не потрібна (ви не будете знати чи турбуватись, чи були в сортуванні замінені два однакові вставки)

MergeSort або, більш конкретно, Timsort використовується Arrays.sort для сортування колекцій об'єктів. Потрібна стабільність. Швидкий сорт не забезпечує стабільності, Тимсорт - це.

Collections.sort делегує Arrays.sort, саме тому ви бачите javadoc, що посилається на MergeSort.


1

Швидке сортування має два основні недоліки, коли справа доходить до об’єднання сортування:

  • Це не стабільно, якщо мова йде про не примітивні.
  • Це не гарантує n log n продуктивність.

Стабільність не є проблемою для примітивних типів, оскільки не існує поняття ідентичності на відміну від (ціннісної) рівності.

Стабільність - це велика справа при сортуванні довільних об’єктів. Приємною побічною перевагою є те, що Merge Sort гарантує n log n (час) продуктивність незалежно від того, який вхід. Ось чому сортування злиття вибрано для забезпечення стабільного сортування (об’єднання сортування) для сортування посилань на об’єкт.


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