Чому кваксор краще, ніж злиття?


354

Мені було задано це питання під час інтерв'ю. Вони обидва O (nlogn), і все ж більшість людей використовують Quicksort замість Mergesort. Чому так?


91
Це не дуже вдале питання для інтерв'ю. Дані в реальному світі не змішуються: вони часто містять багато замовлень, якими може скористатися розумний сорт, і хоча жоден алгоритм не робить це автоматично, простіше зламати сортування злиття, щоб зробити це, ніж швидкодію. GNU libc qsort, Python list.sortта Array.prototype.sortJavaScript у Firefox - це всі супутні види злиття. (GNU STL sortвикористовує замість Introsort, але це може бути тому, що в C ++, обмін потенційно виграє велику кількість копіювання.)
Jason Orendorff

3
@Jason Orendorff: Чому це "easier to hack a mergesort to do it than a quicksort"? Якийсь конкретний приклад, який ви можете навести?
Лазер

16
@eSKay Сортування сортування починається з групування початкових даних у відсортовані підриси. Якщо масив спочатку містить деякі вже відсортовані регіони, ви можете заощадити багато часу, виявивши, що вони є там, перш ніж почати. І ви можете це зробити в O (n) час. Для конкретних прикладів дивіться вихідний код трьох згаданих мною проектів! Найкращим прикладом може бути Timsort Python, детально описаний тут: svn.python.org/view/python/trunk/Objects/… та реалізований у svn.python.org/view/python/trunk/Objects/… .
Джейсон Орендорф

4
@JasonOrendorff: Я не впевнений, що я купую ваш аргумент про те, що злиття може бути легше модифіковане, щоб скористатись уже відсортованими розділами. Етап розділення швидкості розбиття може бути тривіально модифікований, щоб потім перевірити, чи обидва отримані розділи відсортовані, і зупинити рекурсію, якщо вони є. Це потенційно вдвічі збільшує кількість порівнянь, але не змінює часову складність O (n) цього кроку.
j_random_hacker

3
@j_random_hacker: вірно, це я мав на увазі. Але врахуйте: {10, 2, 3, 4, 5, 6, 7, 8, 1, 9} Незважаючи на те, що вже майже повністю відсортовано, перевіряйте, перш ніж розділ не знайде його, ні після цього. І розділ викрутить його, перш ніж наступні дзвінки перевірять його. Тим часом сорти з’єднання перевіряють відсортовані послідовності на етапах поділу перед тим, як будь-який буде переміщений, а розумні шукатимуть такі прогони конкретно під час кроку поділу (див .: Сортування Тіма)
Mooing Duck

Відповіді:


275

У Quicksort є O ( n 2 ) найгірший час виконання та O ( n log n ) середній час виконання. Однак у багатьох сценаріях перевага сортування злиття є тим, що багато факторів впливають на час роботи алгоритму, і, приймаючи їх разом, швидко виграє.

Зокрема, часто цитуються алгоритми сортування стосуються кількості порівнянь або кількості замінів, необхідних для сортування даних. Це дійсно хороший показник продуктивності, тим більше, що він не залежить від базової конструкції обладнання. Однак інші речі - наприклад, місце розташування посилань (тобто читай ми багато елементів, які, ймовірно, знаходяться в кеші?) - також грають важливу роль у поточному апаратному забезпеченні. Зокрема, Quicksort вимагає небагато додаткового місця та має гарну локалізацію кешу, і це робить його швидшим, ніж сортування об'єднань у багатьох випадках.

Крім того, дуже просто уникнути найгіршого часу запуску O ( n 2 ) у найгіршому випадку , використовуючи відповідний вибір шарніра - такий, як його вибір випадковим чином (це відмінна стратегія).

На практиці багато сучасних реалізацій quicksort (зокрема, libstdc ++ 's std::sort) є насправді інтросортом , теоретичним найгіршим випадком якого є O ( n log n ), таким же, як і тип злиття. Це досягається обмеженням глибини рекурсії та переключенням на інший алгоритм ( велику частину ), коли він перевищує log n .


4
У статті Вікіпедії зазначено, що вона переходить на кучу, а не на злиття ... просто на вигадку.
Sev

3
@Sev:… як і оригінальний папір. Дякуємо, що вказали на помилку. - Мало того, що це насправді має значення, оскільки їх асимптотичний час роботи однаковий.
Конрад Рудольф

110
чому це обрано як правильну відповідь ?. Все, що пояснюється, полягає в тому, як швидко усунути проблеми сортування. Він все ще не каже, чому швидкий сорт використовується більше, ніж інші ?. Чи є відповідь "швидкий сорт використовується більше, ніж інший, тому що через одну глибину ви можете перейти до великої кількості"? .. чому б тоді не використати гипсорт спочатку? .. просто намагаюся зрозуміти ...
codeObserver

16
@ p1 Добре запитання. Справжня відповідь полягає в тому, що в середньому, для середніх даних, quicksort швидше, ніж сортування злиття (і сортування купи, з цього приводу), і хоча найгірший випадок короткої швидкості повільніше, ніж сорти злиття, цей гірший випадок можна пом'якшити дуже легко (звідси моя відповідь).
Конрад Рудольф

4
Quicksort також кращий з точки зору пам’яті.
Шашват

287

Як зауважували багато людей, середня ефективність випадку для кваксорту швидше, ніж об'єднання. Але це справедливо лише в тому випадку, якщо ви передбачаєте постійний час для доступу до будь-якого фрагмента пам'яті на вимогу.

У оперативній пам’яті це припущення, як правило, не надто погано (воно не завжди відповідає дійсності кеш-пам'яток, але не дуже погано). Однак якщо ваша структура даних досить велика, щоб жити на диску, то отримує quicksort вбивається тим, що ваш середній диск робить щось на кшталт 200 випадкових прагнень в секунду. Але той самий диск не має проблем з читанням або записом мегабайт в секунду послідовно. Це саме те, що робить злиття.

Тому, якщо дані мають бути відсортовані на диску, ви дійсно хочете скористатися певним варіантом на mergesort. (Як правило, ви швидко перетворюєте підспіси, а потім починаєте об’єднувати їх між деяким порогом розміру.)

Крім того, якщо вам доведеться щось робити з наборами даних такого розміру, добре подумайте, як уникнути прагнення до диска. Наприклад, тому стандартна порада скидати індекси перед великими завантаженнями даних у базі даних, а потім індексувати індекс згодом. Підтримання індексу під час завантаження означає постійно прагнути до диска. На відміну від цього, якщо ви скинете індекси, то база даних може відновити індекс, спершу сортувавши інформацію, яку потрібно обробляти (звичайно, використовуючи об'єднання об'єднань!), А потім завантаживши її в структуру даних BTREE для індексу. (BTREE природно утримуються в порядку, тому ви можете завантажити один із відсортованого набору даних з кількома прагненнями до диска.)

Було багато випадків, коли розуміння того, як уникнути пошуку диска, дозволило мені зробити завдання з обробки даних займати години, а не дні чи тижні.


1
Дуже приємно, не думав про припущення, зроблені для доступу до структури даних. Гарне розуміння :)
chutsu

2
Чи можете ви пояснити, що ви маєте на увазі під "прагнення до диска", це означає пошук якогось одного значення, коли дані зберігаються на диску?
James Wierzba

8
@JamesWierzba Я вважаю, що він означає "пошук місця на диску". "Шукати" на пристрої, що обертається, означає, забрати прочитану головку і перемістити її на нову абсолютну адресу, що є дуже повільною роботою. Коли ви отримуєте доступ до даних у тому порядку, в якому вони були збережені, апаратне забезпечення диска не потрібно шукати, воно просто розорається з великою швидкістю, читаючи елементи послідовно.
nclark

1
Чи можуть деякі пояснити це трохи більше? Ось як я це бачу: Quicksort: Якщо ми йдемо з випадковим стрижнем, стек викликів має фрагменти масиву, розподіленого випадковим чином. Для цього потрібен випадковий доступ. Однак для кожного дзвінка в стеку лівий і правий покажчики переміщуються послідовно. Я припускаю, що вони зберігатимуться в кеші. Свопи знову - це операції над інформацією, яка знаходиться в кеші (і врешті-решт записується на диск). (продовження в моєму наступному коментарі)
сам

1
Тільки внесок, що дозволяє уникнути дорогого режиму читання / запису диска : Коли сортуєте дуже великі дані, яким потрібен доступ до диска, вигідно перемикати напрямок сортування для кожного проходу. Тобто на самому верхньому рівні петлі, як тільки ви переходите 0назустріч, nі наступного разу ви переходите nназустріч 0. Це приносить перевагу відступу (сортування) блоків даних, які вже є в пам'яті (кеш-пам'яті) та двічі атакують лише для доступу до одного диска. Я думаю, що більшість СУБД використовують цю методику оптимізації.
ssd

89

Власне, QuickSort - це O (n 2 ). Його середній випадок час роботи становить O (Nlog (п)), але в гіршому випадку це O (п 2 ), яке відбувається , коли ви запускаєте його в списку , який містить кілька унікальних предметів. Рандомізація займає O (n). Звичайно, це не міняє його найгірший випадок, це просто заважає зловмисному користувачеві змусити ваш вид тривати довго.

QuickSort є більш популярним, оскільки:

  1. Є на місці (MergeSort вимагає додаткової лінійної пам’яті до кількості елементів для сортування).
  2. Має невелику приховану константу.

4
Насправді, є реалізація QuickSort, які є O (n * log (n)), а не O (n ^ 2) в гіршому випадку.
jfs

12
Це також залежить від архітектури комп'ютера. Quicksort виграє кеш, в той час як MergeSort цього не робить.
Крістіан Цюпіту,

4
@JF Себастьян: Це, швидше за все, інтросортовані реалізації, а не quicksort (introsort починається як quicksort і перемикається на Heapsort, якщо він перестане бути n * log (n)).
CesarB

44
Ви можете здійснити злиття на місці.
Марцін

6
Сортування об'єднань може бути реалізовано таким чином, що вимагає лише додаткового зберігання O (1), але більшість із цих реалізацій сильно страждають з точки зору продуктивності.
Ясніший

29

"і все ж більшість людей використовують Quicksort замість Mergesort. Чому це?"

Одна з психологічних причин, яка не була дана, - це просто те, що Квіксорс більш спритно названий. тобто хороший маркетинг.

Так, Quicksort з потрійним розділенням, мабуть, є одним з найкращих алгоритмів сортування загального призначення, але це не перешкоджає тому, що сортування "Швидкий" звучить набагато потужніше, ніж сортування "Об'єднання".


3
Не відповідає на питання про те, що краще. Назва алгоритму не має значення для визначення того, що краще.
Нік

18

Як зазначали інші, найгірший випадок Квіксорта - це O (n ^ 2), тоді як злиття та гипсорт залишаються в O (nlogn). У середньому випадку, однак, усі три є O (nlogn); тому вони для переважної більшості випадків порівнянні.

Що робить Quicksort кращим в середньому, це те, що внутрішня петля передбачає порівняння декількох значень з одним, тоді як для двох інших термінів обидва умови відрізняються для кожного порівняння. Іншими словами, Quicksort робить удвічі більше прочитаних, ніж інші два алгоритми. На сучасних процесорах продуктивність сильно переважає час доступу, тому врешті-решт Quicksort виявляється чудовим першим вибором.


9

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

Але, правду кажучи, у практичних ситуаціях більшість людей потребує лише хорошої середньої продуктивності, а швидка швидкість - це швидко =)

Усі алгоритми сортування мають свої злети і падіння. Дивіться статтю Вікіпедії для сортування алгоритмів для гарного огляду.


7

З запису Вікіпедії на Quicksort :

Quicksort також конкурує з mergesort, іншим рекурсивним алгоритмом сортування, але з перевагою найгіршого Θ (nlogn) часу роботи. Mergesort - це стабільний сорт, на відміну від quicksort і gpsort, і його можна легко пристосувати для роботи в пов'язаних списках і дуже великих списках, що зберігаються на носіях з повільним доступом, таких як дисковий накопичувач або мережеве сховище. Хоча quicksort може бути написаний для роботи у зв'язаних списках, він часто страждає від поганого вибору варіантів без випадкового доступу. Основним недоліком об'єднання об'єднань є те, що при роботі з масивами він вимагає Θ (n) допоміжного простору в кращому випадку, тоді як варіант кваксорбування з місцевим розділенням та хвостовою рекурсією використовує лише Θ (logn) простір. (Зверніть увагу, що, працюючи в пов'язаних списках, об'єднанню потрібна лише невелика, постійна кількість допоміжного сховища.)


7

Му! Quicksort не кращий, він добре підходить для іншого типу додатків, ніж mergesort.

Mergesort варто задуматися, якщо швидкість є сутнісною, погані показники поганих ситуацій не можна допустити, і є додаткове місце. 1

Ви заявили, що вони «Вони обидва O (nlogn) […]». Це неправильно. «Quicksort використовує приблизно n ^ 2/2 порівняння в гіршому випадку». 1 .

Однак на моєму досвіді найважливішою властивістю є легка реалізація послідовного доступу, який ви можете використовувати під час сортування при використанні мов програмування з імперативною парадигмою.

1 Sedgewick, Алгоритми


Mergesort можна реалізувати на місці, щоб він не потребував додаткового місця. Наприклад , з подвійним пов'язаним списком: stackoverflow.com/questions/2938495 / ...
lanoxx

6

Quicksort - це найшвидший алгоритм сортування на практиці, але він має ряд патологічних випадків, які можуть змусити його працювати так само погано, як O (n2).

Гайморт гарантовано працює в O (n * ln (n)) і вимагає лише обмеженого додаткового зберігання. Але є багато цитат тестів реального світу, які показують, що в середньому гипсорт значно повільніший, ніж кікспорт.


5

Пояснення Вікіпедії:

Як правило, quicksort на практиці значно швидший, ніж інші Θ (nlogn) алгоритми, оскільки його внутрішній цикл може бути ефективно реалізований у більшості архітектур, а в більшості реальних даних можна зробити вибір дизайну, який мінімізує ймовірність вимагати квадратичного часу .

Квікорт

Mergesort

Я думаю, що також є проблеми з обсягом сховища, необхідним для Mergesort (який є Ω (n)), якого не має реалізацій quicksort. У гіршому випадку - це однаковий алгоритмічний час, але злиття вимагає більше сховища.


Найгірший випадок швидкості - O (n), злиття O (n log n) - тому велика різниця там.
paul23

1
найгірший випадок - це O (n ^ 2) - не можу редагувати попередній коментар і робив помилку
paul23

@ paul23 коментарі можна видалити. Також відповідь вже стосується вашої точки зору: "у більшості реальних даних можна зробити вибір дизайну, який мінімізує ймовірність вимагати квадратичного часу"
Джим Балтер

5

Я хотів би додати до існуючих чудових відповідей деяку математику про те, як працює QuickSort, коли відходить від кращого випадку, і наскільки це можливо, що, сподіваюся, допоможе людям трохи краще зрозуміти, чому випадок O (n ^ 2) не є реальним стурбованість більш складними реалізаціями QuickSort.

Поза проблемами випадкового доступу є два основні фактори, які можуть впливати на продуктивність QuickSort, і вони пов'язані з тим, як стрижневий порівняння з даними, відсортованими.

1) Невелика кількість ключів у даних. Набір даних того самого значення буде сортувати за n ^ 2 разів у ванільному 2-роздільному розділі QuickSort, оскільки всі значення, за винятком місця розташування, розміщуються по одній стороні кожного разу. Сучасні реалізації вирішують це за допомогою таких методів, як використання сортування з 3-х розділів. Ці методи виконуються на наборі даних усіх однакових значень за O (n) час. Тож використання такої реалізації означає, що введення з невеликою кількістю клавіш насправді покращує час роботи та вже не викликає занепокоєння.

2) Надзвичайно поганий вибір шарніра може призвести до найгірших показників. В ідеальному випадку шарнір завжди буде таким, що на 50% даних менше, а на 50% - даних більше, так що вхід буде розбитий навпіл під час кожної ітерації. Це дає нам n порівнянь та разів заміни log-2 (n) рекурсій за час O (n * logn).

На скільки впливає неідеальний вибір стрижня на час виконання?

Розглянемо випадок, коли поворот вибирається послідовно таким чином, що 75% даних знаходиться на одній стороні стрижня. Це все ще O (n * logn), але тепер основа журналу змінилася на 1 / 0,75 або 1,33. Співвідношення продуктивності при зміні бази завжди є постійною, представленою log (2) / log (newBase). У цьому випадку ця константа дорівнює 2,4. Тож ця якість вибору шарніра займає в 2,4 рази більше, ніж ідеальна.

Як швидко це погіршується?

Не дуже швидко, поки вибір стрижня не стане (послідовно) дуже поганим:

  • 50% з одного боку: (ідеальний випадок)
  • 75% з одного боку: в 2,4 рази довше
  • 90% з одного боку: 6,6 рази довше
  • 95% з одного боку: 13,5 разів довше
  • 99% з одного боку: 69 разів довше

Коли ми наближаємось до 100% з одного боку, частина журналу виконання наближається до n, а все виконання асимптотично наближається до O (n ^ 2).

У наївній реалізації QuickSort такі випадки, як відсортований масив (для першого елемента повороту) або реверсивно відсортований масив (для останнього зведення елемента) надійно дадуть час виконання O (n ^ 2) у найгіршому випадку. Крім того, реалізація з передбачуваним виборотом вибору може бути піддана атаці DoS за допомогою даних, призначених для виконання найгіршого випадку. Сучасні реалізації уникають цього різноманітними методами, такими як рандомізація даних перед сортуванням, вибір медіани з 3 випадково вибраних індексів і т. Д. З цією рандомізацією в поєднанні ми маємо 2 випадки:

  • Невеликий набір даних Найгірший випадок можливий, але O (n ^ 2) не є катастрофічним, оскільки n досить малий, що n ^ 2 також малий.
  • Великий набір даних Найгірший випадок можливий в теорії, але не на практиці.

Наскільки ймовірно, ми побачимо жахливу виставу?

Шанси суєтно малі . Розглянемо свого роду 5000 значень:

Наша гіпотетична реалізація вибере опорну точку, використовуючи медіану з 3 випадково вибраних індексів. Ми вважатимемо стрижні, що знаходяться в діапазоні 25% -75%, "хорошими", а стрижні, що знаходяться в діапазоні 0% -25% або 75% -100%, "поганими". Якщо подивитися на розподіл ймовірностей, використовуючи медіану з 3 випадкових індексів, кожна рекурсія має шанс 11/16 закінчитися з хорошим стрибком. Зробимо 2 консервативні (і помилкові) припущення для спрощення математики:

  1. Хороші повороти завжди точно розбиті на 25% / 75% і працюють в ідеальному випадку 2,4 *. Ми ніколи не отримуємо ідеального розколу або будь-якого розколу краще, ніж 25/75.

  2. Погані стрижні завжди є найгіршим випадком і по суті нічого не сприяють вирішенню.

Наша реалізація QuickSort зупиниться на n = 10 і перейде до сортування вставки, тому нам потрібні 22 25% / 75% півометних розділів, щоб зламати значення введення 5000 донині. (10 * 1.333333 ^ 22> 5000) Або нам потрібно 4990 найгірших поворотів. Майте на увазі, що якщо ми накопичимо 22 хороших опори в будь-якій точці, сортування завершиться, тому найгірший випадок або що-небудь поруч з ним вимагає надзвичайно удачі. Якщо б нам вдалося здійснити 88 рекурсій, щоб реально досягти 22 хороших поворотів, необхідних для сортування до n = 10, це буде 4 * 2,4 * ідеальний випадок або приблизно в 10 разів більший час виконання ідеального випадку. Наскільки ймовірно, що після 88 рекурсій ми не досягли б потрібних 22 хороших стрибків?

Біноміальні розподіли ймовірностей можуть відповісти на це, а відповідь приблизно 10 ^ -18. (n - 88, k - 21, p - 0,6875) Ваш користувач приблизно в тисячу разів більший за удар блискавкою за 1 секунду, що потрібно натиснути [SORT], ніж вони, щоб побачити, що 5000 сортування елементів працює гірше ніж 10 * ідеальний випадок. Цей шанс стає меншим, оскільки набір даних збільшується. Ось кілька розмірів масиву та їх відповідні шанси працювати довше, ніж 10 * ідеально:

  • Масив з 640 елементів: 10 ^ -13 (потрібно 15 хороших точок зведення з 60 спроб)
  • Масив з 5000 елементів: 10 ^ -18 (потрібно 22 хороших повороту з 88 спроб)
  • Масив з 40000 елементів: 10 ^ -23 (потрібно 29 хороших поворотів із 116)

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

Нарешті, як згадували інші, навіть ці абсурдно малоймовірні випадки можна усунути, перейшовши на сортування купи, якщо стек рекурсії надто глибокий. Таким чином, TLDR полягає в тому, що для хорошої реалізації QuickSort найгірший випадок насправді не існує, оскільки він був розроблений і виконання завершується за O (n * logn) час.


1
"існуючі чудові відповіді" - що це? Я не можу їх знайти.
Джим Балтер

Чи повідомляють якісь варіанти Швидкого сортування функцію порівняння про розділи таким чином, що дозволить їй використовувати ситуації, коли значна частина ключа буде однаковою для всіх елементів у розділі?
supercat

4

Чому Quicksort хороший?

  • QuickSort приймає N ^ 2 в гіршому випадку, а середній - NlogN. Найгірший випадок трапляється при сортування даних. Це можна зменшити випадковим переміщенням перед початком сортування.
  • QuickSort не потребує додаткової пам’яті, яка приймається шляхом злиття.
  • Якщо набір даних великий і є однакові елементи, складність Quicksort зменшується, використовуючи тристоронній розділ. Більше немає однакових елементів, краще сортування. Якщо всі елементи однакові, вони сортуються за лінійним часом. [Це реалізація за замовчуванням у більшості бібліотек]

Чи завжди Quicksort кращий за Mergesort?

Не зовсім.

  • Mergesort стабільний, але Quicksort - ні. Тож якщо вам потрібна стабільність у випуску, ви б використовували Mergesort. Стабільність необхідна у багатьох практичних програмах.
  • Пам'ять сьогодні дешева. Отже, якщо додаткова пам'ять, яку використовує Mergesort, не є критичною для вашої програми, не буде шкоди для використання Mergesort.

Примітка. У java функція Arrays.sort () використовує Quicksort для примітивних типів даних та Mergesort для типів даних об'єктів. Оскільки об'єкти споживають пам'ять накладних витрат, тому для Mergesort додані невеликі накладні витрати можуть не бути проблемою для точки зору продуктивності.

Довідково : Перегляньте відео QuickSort 3-го тижня, Курс алгоритмів Принстона на Coursera


"Це можна пом'якшити випадковим переміщенням перед початком сортування." - Е, ні, це було б дорого. Замість цього використовуйте випадкові повороти.
Джим Балтер

4

Quicksort НЕ кращий, ніж злиття. При O (n ^ 2) (найгірший випадок, що трапляється рідко), швидкий вибір коротко повільніше, ніж O (nlogn) роду злиття. У Quicksort менше накладних витрат, тому з невеликими n та повільними комп’ютерами це краще. Але комп’ютери сьогодні настільки швидкі, що додаткові накладні витрати об'єднаних об'єднань незначні, а ризик дуже повільного швидкого вибору значно переважає незначні накладні витрати об'єднання в більшості випадків.

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


2
У вашому другому реченні сказано: "... злиття потенційно набагато повільніше, ніж ... злиття". Перша посилання, мабуть, повинна бути швидкою.
Джонатан Леффлер

Сортування злиття стабільне лише за умови стабільності алгоритму злиття; це не гарантується.
Ясніше

@Clearer Це гарантовано, якщо <=він використовується для порівняння, а не <, і немає причин цього не робити.
Джим Балтер

@JimBalter Я міг легко придумати нестабільний алгоритм злиття (наприклад, quicksort би виконував цю роль). Причина, чому швидке сортування швидше, ніж сортування об'єднань, у багатьох випадках полягає не в тому, що скорочується накладні витрати, а через те, як quicksort отримує доступ до даних, що набагато більш кеш-кеш, ніж стандартний об'єднання.
Чіткіший

@Clearer quicksort - це не тип злиття ... ваше твердження 21 грудня 14 року, на яке я відповів, суворо стосується сортування об'єднань та того, чи є воно стабільним. швидкий і швидший, це зовсім не стосується вашого коментаря чи моєї відповіді. Кінець дискусії для мене ... знову і поза.
Джим Балтер

3

Відповідь дещо нахилиться до quicksort wrt до змін, внесених із DualPivotQuickSort для примітивних значень. Він використовується в JAVA 7 для сортування в java.util.Arrays

It is proved that for the Dual-Pivot Quicksort the average number of
comparisons is 2*n*ln(n), the average number of swaps is 0.8*n*ln(n),
whereas classical Quicksort algorithm has 2*n*ln(n) and 1*n*ln(n)
respectively. Full mathematical proof see in attached proof.txt
and proof_add.txt files. Theoretical results are also confirmed
by experimental counting of the operations.

Ви можете знайти вкладення JAVA7 тут - http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/util/Arrays.java

Подальше дивовижне читання на DualPivotQuickSort - http://permalink.gmane.org/gmane.comp.java.openjdk.core-libs.devel/2628


3

Загальний алгоритм при злитті сортування:

  1. Сортувати лівий підмасив
  2. Сортуйте правий підмасив
  3. Об'єднайте 2 відсортовані підмасиви

На верхньому рівні об'єднання 2 відсортованих підмасивів передбачає справу з N елементами.

Один рівень нижче цього, кожна ітерація кроку 3 включає взаємодію з N / 2 елементами, але вам потрібно повторити цей процес двічі. Отже, ти все ще маєш справу з 2 * N / 2 == N елементами.

На один рівень нижче цього ви об’єднуєте 4 * N / 4 == N елементів тощо. Кожна глибина в рекурсивному стеку передбачає об'єднання однакової кількості елементів на всіх закликах до цієї глибини.

Розглянемо замість цього алгоритм швидкого сортування:

  1. Виберіть точку зрізу
  2. Розмістіть точку зрізу в потрібному місці в масиві, з усіма меншими елементами зліва, а більшими елементами праворуч
  3. Сортувати лівий підрядок
  4. Сортуйте правий підряд

На верхньому рівні ви маєте справу з масивом розміром N. Потім вибираєте одну точку зведення, ставите її у правильне положення, а потім можете повністю ігнорувати її для решти алгоритму.

Один рівень нижче цього, ви маєте справу з двома підмасивами, які мають комбінований розмір N-1 (тобто віднімайте попередню точку зрізу). Ви вибираєте точку зрізу для кожного підмасиву, яка містить до 2 додаткових точок зведення.

Один рівень нижче цього, ви маєте справу з 4 підмасивами з комбінованим розміром N-3 з тих же причин, що і вище.

Потім N-7 ... Потім N-15 ... Потім N-32 ...

Глибина рекурсивного стека залишається приблизно однаковою (logN). За допомогою сортування злиття ви завжди маєте справу з злиттям N-елементів на кожному рівні рекурсивного стека. При швидкому сортуванні кількість елементів, з якими ви маєте справу, зменшується під час спуску стека. Наприклад, якщо дивитися на глибину середини через рекурсивний стек, кількість елементів, з якими ви маєте справу, становить N - 2 ^ ((logN) / 2)) == N - sqrt (N).

Відмова від відповідальності. Під час сортування об'єднань, оскільки ви поділяєте масив на 2 точно рівні частини, кожен раз рекурсивна глибина точно logN. При швидкому сортуванні, оскільки навпаки точка зведення навряд чи буде точно посередині масиву, глибина рекурсивного стека може бути трохи більшою, ніж logN. Я не зробив математики, щоб побачити, яку велику роль цей фактор і фактор, описаний вище, насправді відіграють у складності алгоритму.


Те, що зсуви не є частиною сортів на наступному рівні, не є тим, чому QS є більш ефективним. Інші відповіді див. Для отримання додаткової інформації.
Джим Балтер

@JimBalter На які "інші відповіді" ви посилаєтесь? Верхня відповідь просто говорить про те, що QS "вимагає небагато додаткового простору та має гарну локальність кешу", але не дає пояснень, чому це так, і не дає жодних цитат. У другій відповіді просто сказано, що сортування злиття краще для більших наборів даних
RvPr

Ви переміщуєте цілі, від чого QS більш сприятливий для пояснення основних фактів про те, як він працює. Відповіді на інші запитання роблять так: stackoverflow.com/questions/9444714/… ... Сподіваюся, цього вам достатньо; Я більше не відповім.
Джим Балтер

3

На відміну від злиття сортування Швидкий сортування не використовує допоміжний простір. Тоді як сортування об'єднань використовує допоміжний простір O (n). Але Злиття Сортування має найгірший за часом складність O (nlogn), тоді як найгірша складність Швидкого Сортування - O (n ^ 2), що відбувається, коли масив вже відсортований.


Ні, найгірший випадок QuickSort не відбувається, коли масив вже відсортований, якщо ви не використовуєте перший або останній елемент як опорний пункт, але ніхто цього не робить.
Джим Балтер

2

Quicksort має кращу середню складність випадку, але в деяких додатках це неправильний вибір. Quicksort вразливий до відмови в сервісних атаках. Якщо зловмисник може вибрати вхід для сортування, він може легко побудувати набір, який займає найгірший час складності o (n ^ 2).

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

З цих причин я більший фанат Мергесорта, ніж я з Кіксорта.


2
Як у Quicksort краща середня складність випадку? Вони обидва O (nlgn). Я б стверджував, що зловмисник не повинен надавати вклад в будь-який алгоритм сортування ... але в інтересах не припускати безпеку через невідомість, припустимо, він міг би. Хоча час роботи n ^ 2 гірше, ніж nlgn, але недостатньо гірше, щоб веб-сервер вийшов з ладу на основі однієї атаки. Насправді аргумент DOS в значній мірі недійсний, тому що будь-який веб-сервер вразливий до DDOS-атаки, і з більшою ймовірністю зловмисник використовує розподілену мережу хостів, усі TCP SYN затоплені.
CaTalyst.X

"Quicksort має кращу середню складність випадку" - ні, це не так.
Джим Балтер

2

Це важко сказати. Найгіршим з MergeSort є n (log2n) -n + 1, що є точним, якщо n дорівнює 2 ^ k (я це вже довів). А для будь-якого n він знаходиться між (n lg n - n + 1) і (n lg n + n + O (lg n)). Але для quickSort найкращим є nlog2n (також n дорівнює 2 ^ k). Якщо ви розділите Mergesort на quickSort, він дорівнює одиниці, коли n нескінченно. це як би гірший випадок MergeSort кращий, ніж найкращий випадок QuickSort, чому ми використовуємо quicksort? Але пам’ятайте, що MergeSort не стоїть на місці, він вимагає 2n мемерой простору. А MergeSort також потрібно робити багато копій масиву, які ми не включайте в аналіз алгоритму. Словом, MergeSort насправді швидше, ніж quicksort у theroy, але насправді вам потрібно врахувати меморіальний простір, вартість копіювання масиву, злиття повільніше, ніж швидке сортування. Я одного разу зробив експериментуйте, коли мені дали 1000000 цифр у java класом Random,і це займало 2610 мс по об'єднанню, 1370 м по кваксорту.


2

Швидке сортування є найгіршим випадком O (n ^ 2), проте середній випадок, який послідовно виконується, виконує сортування злиття. Кожен алгоритм - це O (nlogn), але вам потрібно пам’ятати, що, говорячи про Big O, ми залишаємо нижчі коефіцієнти складності. Швидкий сортування суттєво покращився порівняно з сортуванням злиття, коли мова йде про постійні фактори.

Сортування сортування також вимагає O (2n) пам'яті, тоді як швидке сортування може бути зроблене на місці (вимагає лише O (n)). Це ще одна причина, що швидке сортування в основному віддається перевазі порівнянню порівняння.

Додаткова інформація:

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

[5, 4, 3, 2, 1]

Якщо виворіт обраний як найменше або найбільше число в групі, то швидке сортування буде виконуватися в O (n ^ 2). Імовірність вибору елемента, який знаходиться у найбільшому чи найменшому 25% списку, становить 0,5. Це дає алгоритму 0,5 шансу бути хорошим стрижнем. Якщо ми використовуємо типовий алгоритм вибору стрижня (скажімо, вибір випадкового елемента), у нас є 0,5 шансу вибрати хороший шар для кожного вибору стрижня. Для колекцій великого розміру ймовірність вибору поганого стрижня становить 0,5 * n. На основі цієї ймовірності швидкий сорт є ефективним для середнього (і типового) випадку.


O (2n) == O (n). Правильне твердження полягає в тому, що Mergesort потребує додаткової пам'яті O (n) (точніше, їй потрібна n / 2 допоміжна пам'ять). І це не вірно для пов'язаних списків.
Джим Балтер

@JimBalter Сер, чи не заперечуєте ви поділитися своїми блискучими та гідними ідеями з нами щодо їхніх дій, як відповіді на запитання? Заздалегідь спасибі.
snr

2

Це досить давнє питання, але оскільки я нещодавно розглядався з обома, тут є мій 2с:

Потрібно об'єднати сортування в середньому ~ N log N порівнянь. Для вже (майже) відсортованих відсортованих масивів це зменшується до 1/2 N log N, оскільки під час об’єднання ми (майже) завжди вибираємо "ліву" частину 1/2 N разів, а потім просто копіюємо праву 1/2 N елементи. Додатково я можу припустити, що вже відсортований вхід змушує передбачуваного гілки процесора блищати, але вгадувати майже всі гілки правильно, тим самим запобігаючи затримках конвеєра.

Швидке сортування в середньому вимагає ~ 1,38 N журналу N порівнянь. Це не має великої користі від вже відсортованого масиву з точки зору порівнянь (однак це стосується свопів та, ймовірно, з точки зору прогнозування галузей всередині процесора).

Мої орієнтири щодо досить сучасного процесора показують наступне:

Коли функція порівняння є функцією зворотного виклику (як у qsort () libc реалізації), то quicksort повільніше, ніж об'єднання, на 15% при випадковому введенні та 30% для вже відсортованого масиву для 64-бітових цілих чисел.

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

Однак якщо у вашого (великого) масиву є дуже мало унікальних значень, сортування злиття починає набирати більше швидкості у будь-якому випадку.

Тож, можливо, підсумок: якщо порівняння дороге (наприклад, функція зворотного виклику, порівняння рядків, порівняння багатьох частин структури, головним чином потрапляючи до другої-третьої-четвертої "якщо", щоб змінити значення) - шанси на те, що вам стане краще з сортуванням злиття. Для більш простих завдань швидкий вибір буде швидшим.

Це сказано, що все сказане було вірно: - Quicksort може бути N ^ 2, але Седжевік стверджує, що хороша рандомізована реалізація має більше шансів на комп'ютер, який виконує сортування, вдарившись блискавкою, ніж піти N ^ 2 - Mergesort вимагає додаткового простору


Чи qsort б'є злиття навіть для відсортованих входів, якщо порівняння дешеве?
Еоніл

2

Коли я експериментував з обома алгоритмами сортування, підраховуючи кількість рекурсивних викликів, quicksort послідовно має менше рекурсивних викликів, ніж об'єднання. Це відбувається тому, що у quicksort є повороти, а шарніри не включаються до наступних рекурсивних викликів. Таким чином, квакісорт може досягти рекурсивної базової справи швидше, ніж злиття.


Повороти не мають нічого спільного з тим, чому QS має менше рекурсивних викликів ... це тому, що половина рекурсії QS - це хвостова рекурсія, яку можна усунути.
Джим Балтер

2

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

1- Допоміжний простір: Швидке сортування - алгоритм сортування на місці. Сортування на місці означає, що для сортування не потрібно додаткового місця для зберігання. Сортування сортування з іншого боку вимагає тимчасового масиву для об'єднання відсортованих масивів, а значить, він не є на місці.

2- Найгірший випадок: найгірший випадок кікспорту O(n^2)можна уникнути, використовуючи рандомізований кваксор. Це можна легко уникнути з високою ймовірністю, вибравши правильний шарнір. Отримання середньої поведінки у випадку випадків вибору правильного елемента зведення дозволяє йому імпровізувати продуктивність та стати настільки ж ефективним, як сортування злиття.

3- Локальність посилання: Quicksort, зокрема, демонструє хорошу локальність кешу, і це робить швидше, ніж сортування об'єднань у багатьох випадках, як у середовищі віртуальної пам'яті.

4- Рекурсія хвоста: QuickSort є рекурсивним хвостом, тоді як сортування злиття - ні. Хвостова рекурсивна функція - це функція, де рекурсивний виклик - це останнє, що виконується функцією. Хвостові рекурсивні функції вважаються кращими, ніж не хвостові рекурсивні функції, оскільки хвоста-рекурсія може бути оптимізована компілятором.


1

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

Однак! Особисто я часто використовую варіант злиття або кваксорт, який погіршує стан злиття, коли кексорт робить погано. Пам'ятайте. Quicksort тільки О (п увійти п) в середньому . Найгірший випадок - це O (n ^ 2)! Mergesort завжди O (n log n). У випадках, коли продуктивність або реагування в режимі реального часу є обов'язковим, і ваші вхідні дані можуть надходити з шкідливого джерела, не слід використовувати звичайний швидкодіючий корок.


1

При рівних обставинах я б очікував, що більшість людей використовуватиме те, що є найзручнішим, і це, як правило, qsort (3). Крім того, що квакісорт, як відомо, є дуже швидким на масивах, подібно до того, що об'єднання є загальним вибором для списків.

Мені цікаво, чому так рідко можна побачити радіацію чи відра. Вони O (n), принаймні, у пов'язаних списках, і все, що потрібно, - це певний метод перетворення ключа на порядковий номер. (струнні та поплавці працюють чудово.)

Я думаю, що причина пов'язана з тим, як навчають інформатику. Мені навіть довелося продемонструвати моєму викладачеві з аналізу алгоритму, що дійсно можна сортувати швидше, ніж O (n log (n)). (Він мав доказ того, що ви не можете порівняти сортування швидше, ніж O (n log (n)), що правда.)

В інших новинах поплавці можуть бути відсортовані як цілі числа, але ви повинні згортати негативні числа.

Редагувати: Насправді, ось ще більш порочний спосіб сортування floats-as-integers: http://www.stereopsis.com/radix.html . Зауважте, що біт-гортаючий трюк можна використовувати незалежно від того, який алгоритм сортування ви фактично використовуєте ...


1
Я бачив свою частку радіаційних сортів. Але це досить важко у використанні, оскільки якщо його правильно проаналізувати, час його виконання не є O (n), оскільки це залежить від більшої кількості вхідних елементів. Взагалі, дуже важко зробити такі сильні прогнози, що радіо-сортування має бути ефективним щодо введення даних.
Конрад Рудольф

Це є О (п), де п є сумарний розмір вхідного, тобто, в тому числі розміру елементів. Це правда, що ви можете реалізувати це, так що вам доведеться прокладати багато нулів, але це нісенітниця використовувати погану реалізацію для порівняння. (Сказане, впровадження може бути важким, ммм.)
Андерс Евреніус

Зауважте, що якщо ви використовуєте GNU libc, qsortце тип злиття.
Джейсон Орендорф

Ер, якщо бути точним, це сорт злиття, якщо не вдасться виділити необхідну тимчасову пам'ять. cvs.savannah.gnu.org/viewvc/libc/stdlib/…
Jason Orendorff

1

Невеликі доповнення до швидких та об’єднаних сортів.

Також це може залежати від виду сортування предметів. Якщо доступ до елементів, заміни та порівняння - це не прості операції, як порівняння цілих чисел у площинній пам'яті, то алгоритм злиття може бути кращим.

Наприклад, ми сортуємо елементи за допомогою мережевого протоколу на віддаленому сервері.

Крім того, у спеціальних контейнерах, таких як "пов'язаний список", це не є користю для швидкого сортування.
1. Об’єднати сортування у зв’язаному списку, не потрібна додаткова пам'ять. 2. Доступ до елементів у швидкому сортуванні не є послідовним (у пам'яті)


0

Швидке сортування - це місцевий алгоритм сортування, тому його краще підходить для масивів. З'єднання сортування з іншого боку вимагає додаткового зберігання O (N) і більше підходить для пов'язаних списків.

На відміну від масивів, у список сподобалися ми можемо вставляти елементи посередині з пробілом O (1) та O (1), тому операція злиття у сортуванні злиття може бути реалізована без зайвого місця. Однак виділення та виділення додаткового простору для масивів негативно впливає на час виконання сортування злиття. Сортування об'єднань також надає перевагу пов'язаному списку, оскільки доступ до даних відбувається послідовно, без особливого доступу до пам'яті.

Швидке сортування, з іншого боку, вимагає багато випадкового доступу до пам'яті, і з масиву ми можемо безпосередньо отримати доступ до пам'яті без будь-якого обходу, як того вимагає пов'язаний список. Також швидке сортування при використанні для масивів має хороший орієнтир, оскільки масиви постійно зберігаються в пам'яті.

Незважаючи на те, що обидва алгоритми сортування середньої складності складають O (NlogN), зазвичай люди для звичайних завдань використовують масив для зберігання, і саме тому швидке сортування повинно бути алгоритмом вибору.

EDIT: Я щойно з'ясував, що сортування злиття найгірший / кращий / avg випадок завжди nlogn, але швидке сортування може змінюватися від n2 (найгірший випадок, коли елементи вже відсортовані) до nlogn (avg / кращий випадок, коли pivot завжди ділить масив на два половинки).


0

Розгляньте складність часу та простору. Для сортування злиття: часова складність: O (nlogn), просторова складність: O (nlogn)

Для швидкого сортування: Часова складність: O (n ^ 2), складність простору: O (n)

Тепер вони обидва виграють в одному сценарії кожен. Але, використовуючи випадкову півоту, ви майже завжди можете зменшити складність у режимі швидкого сортування за часом до O (nlogn).

Таким чином, швидке сортування є кращим у багатьох програмах замість сортування з’єднання.


-1

У землі c / c ++, коли не використовуються контейнери stl, я, як правило, використовую quicksort, тому що він вбудований у час виконання, тоді як mergesort - ні.

Тому я вважаю, що у багатьох випадках це просто шлях найменшого опору.

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


3
Насправді, якщо це функція бібліотеки qsort (), про яку ви говорите, вона може бути або не може бути реалізована як quicksort.
Томас Падрон-Маккарті

3
Конраде, вибачте, що я трохи поцікавився з цього приводу, але де ви це знайдете? Я не можу знайти його в стандарті ISO C або в стандарті C ++.
Томас Падрон-Маккарті

2
GNU libc's qsortє об'єднанням, якщо кількість елементів не є справді гігантською або тимчасова пам'ять не може бути виділена. cvs.savannah.gnu.org/viewvc/libc/stdlib/…
Jason Orendorff

-3

Одна з причин - більш філософська. Quicksort - це філософія Top-> Down. З п елементами для сортування, є n! можливості. З двома перегородками m & nm, які взаємно виключаються, кількість можливостей зменшується на кілька порядків. м! * (нм)! менший на кілька порядків, ніж n! поодинці. уявіть 5! проти 3! * 2 !. 5! має в 10 разів більше можливостей, ніж 2-х розділів по 2 і 3 у кожній. і екстраполювати до 1 мільйона фабричних проти 900 К! * 100 К! vs. Отже, замість того, щоб турбуватися про встановлення будь-якого порядку в межах діапазону або розділу, просто встановіть порядок на більш широкому рівні в розділах і зменшіть можливості в розділі. Будь-який порядок, встановлений раніше в межах діапазону, буде порушений пізніше, якщо самі розділи не будуть взаємовиключними.

Будь-який підхід до порядку "знизу вгору", наприклад сортування злиття або сортування купи, схожий на підхід робітників або службовців, коли можна починати порівнювати на мікроскопічному рівні рано. Але цей порядок повинен бути втрачений, як тільки пізніше знайдеться елемент між ними. Ці підходи дуже стабільні та надзвичайно передбачувані, але роблять певну додаткову роботу.

Швидкий сортування - це такий, як Управлінський підхід, коли спочатку не турбується жодне замовлення, а лише виконання широкого критерію, не враховуючи порядок. Потім перегородки звужуються, поки ви не отримаєте відсортований набір. Справжня проблема в Quicksort - це пошук розділу чи критерію в темряві, коли ви нічого не знаєте про елементи для сортування. Ось чому нам або потрібно докласти певних зусиль, щоб знайти середнє значення, або вибрати 1 навмання, або якийсь довільний "управлінський" підхід. Щоб знайти ідеальну медіану, можна зажадати значних зусиль і знову привести до тупого підходу знизу вгору. Отже, Quicksort каже просто вибрати випадковий стрижень і сподіваємось, що він буде десь посередині або зробити якусь роботу, щоб знайти медіану 3, 5 або щось більше, щоб знайти кращу медіану, але не плануйте бути ідеальним & don ' t не витрачайте часу на початкове замовлення. Це, здається, добре, якщо вам пощастило або іноді погіршується до n ^ 2, коли ви не отримуєте медіани, а просто ризикуєте. Будь-який спосіб є випадковим. правильно. Тож я більше погоджуюся з верхнім -> внизу логічним підходом швидкого вибору, і виявляється, що шанс, який він вимагає щодо вибору стрижнів та порівнянь, який він заощаджує раніше, здається, працює краще, ніж будь-який ретельний та ретельний стабільний низ -> підхід, як злиття сорту. Але порівняння, які він економив раніше, здається, працюють краще, ніж будь-який ретельний та ретельний стабільний підхід знизу -> вгору, як сортування злиття. Але порівняння, які він економив раніше, здається, працюють краще, ніж будь-який ретельний та ретельний стабільний підхід знизу -> вгору, як сортування злиття. Але


quicksort виграє від випадковості вибору стрижня. Випадковий стрижень, природно, прагне до розділу 50:50 і навряд чи буде послідовно спрямований до однієї з крайнощів. Постійний коефіцієнт nlogn є досить низьким, поки середнє розділення становить 60-40 або навіть до 70-30.
Зимова диня

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