Швидкий сорт проти кучевого


Відповіді:


60

Ця стаття має певний аналіз.

Також з Вікіпедії:

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


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

@DVK, Відповідно до вашого посилання cs.auckland.ac.nz/~jmor159/PLDS210/qsort3.html , сортування купи займає 2842 порівняння для n = 100, але для n = 500 потрібно 53113 порівнянь. І це означає, що співвідношення між n = 500 та n = 100 дорівнює 18 разів, і це НЕ відповідає алгоритму сортування купи зі складністю O (N logN). Думаю, цілком ймовірно, що їх реалізація сорту кучі має якісь помилки всередині.
DU Jiaen

@DUJiaen - пам'ятайте, що O () - це асимптотична поведінка при великих N і має можливий множник
DVK,

Це НЕ пов'язано з мультиплікатором. Якщо алгоритм має складність O (N log N), він повинен слідувати тенденції Time (N) = C1 * N * log (N). І якщо взяти Time (500) / Time (100), очевидно, що C1 зникне, а результат повинен бути закритий до (500 log500) / (100 log100) = 6.7 Але з вашого посилання це 18, тобто занадто великі масштаби.
DU Jiaen

2
Посилання мертве
PlsWork

125

Гарячий сорт гарантований O (N log N), що набагато краще, ніж найгірший випадок у Quicksort. Heapsort не потребує більше пам'яті для іншого масиву для розміщення впорядкованих даних, як це потрібно Mergesort. То чому комерційні додатки дотримуються Quicksort? Що в Quicksort є таким особливим у порівнянні з іншими реалізаціями?

Я сам протестував алгоритми і переконався, що Quicksort насправді має щось особливе. Він працює швидко, набагато швидше, ніж алгоритми Heap та Merge.

Секрет Quicksort у тому, що він майже не робить непотрібних обмінів елементів. Обмін займає багато часу.

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

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

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

Що перевершує Quicksort - це не найгірший випадок, а найкращий випадок! У кращому випадку ви робите однакову кількість порівнянь, добре, але ви майже нічого не поміняєте. У середньому ви поміняєте місцями частину елементів, але не всі елементи, як у Heapsort та Mergesort. Це те, що надає Quicksort найкращий час. Менше обміну, більша швидкість.

Реалізація нижче в C # на моєму комп'ютері, що працює в режимі звільнення, перевершує Array.Sort на 3 секунди із середнім опором та на 2 секунди з покращеним стрижнем (так, для отримання хорошого стрижня є накладні витрати).

static void Main(string[] args)
{
    int[] arrToSort = new int[100000000];
    var r = new Random();
    for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);

    Console.WriteLine("Press q to quick sort, s to Array.Sort");
    while (true)
    {
        var k = Console.ReadKey(true);
        if (k.KeyChar == 'q')
        {
            // quick sort
            Console.WriteLine("Beg quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            QuickSort(arrToSort, 0, arrToSort.Length - 1);
            Console.WriteLine("End quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
        }
        else if (k.KeyChar == 's')
        {
            Console.WriteLine("Beg Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            Array.Sort(arrToSort);
            Console.WriteLine("End Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
        }
    }
}

static public void QuickSort(int[] arr, int left, int right)
{
    int begin = left
        , end = right
        , pivot
        // get middle element pivot
        //= arr[(left + right) / 2]
        ;

    //improved pivot
    int middle = (left + right) / 2;
    int
        LM = arr[left].CompareTo(arr[middle])
        , MR = arr[middle].CompareTo(arr[right])
        , LR = arr[left].CompareTo(arr[right])
        ;
    if (-1 * LM == LR)
        pivot = arr[left];
    else
        if (MR == -1 * LR)
            pivot = arr[right];
        else
            pivot = arr[middle];
    do
    {
        while (arr[left] < pivot) left++;
        while (arr[right] > pivot) right--;

        if(left <= right)
        {
            int temp = arr[right];
            arr[right] = arr[left];
            arr[left] = temp;

            left++;
            right--;
        }
    } while (left <= right);

    if (left < end) QuickSort(arr, left, end);
    if (begin < right) QuickSort(arr, begin, right);
}

10
+1 для міркувань щодо №. обміну, операції читання / запису, необхідні для різних алгоритмів сортування
ycy

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

1
Мені цікаво, чи це саме той код, який ви запустили для своїх симуляцій між швидким сортуванням, кодованим вручну, та вбудованим в Array.sort C #? Я тестував цей код, і у всіх моїх тестах, у кращому випадку ручне кодування швидкого сортування було таким же, як Array.sort. Одне, що я контролював під час тестування цього, - це зробити дві однакові копії випадкового масиву. Зрештою, дана рандомізація потенційно може бути вигіднішою (схилятися до найкращого випадку), ніж інша рандомізація. Тож я провів однакові набори через кожен. Array.sort кожен раз зв'язаний або битий (звільнення збірки до речі).
Кріс

1
Сортування злиття не повинно копіювати 100% елементів, якщо це не дуже наївна реалізація з підручника. Це просто реалізувати так, що вам потрібно скопіювати лише 50% з них (ліва сторона двох об’єднаних масивів). Також тривіально відкласти копіювання, поки вам насправді не доведеться «міняти місцями» два елементи, тому з уже відсортованими даними у вас не буде зайвих витрат на пам’ять. Отже, навіть 50% - це насправді найгірший випадок, і ви можете мати що завгодно між цим і 0%.
ddekany

1
@MarquinhoPeli Я хотів сказати, що вам потрібно лише на 50% більше доступної пам'яті порівняно з розміром відсортованого списку, а не 100%, що, здається, є поширеною помилкою. Тож я говорив про пікове використання пам'яті. Я не можу дати посилання, але легко зрозуміти, якщо ви спробуєте об’єднати дві вже відсортовані половини масиву на місці (лише ліва половина має проблему, коли ви перезаписуєте елементи, які ви ще не спожили). Інше питання, скільки копіювання пам’яті потрібно зробити протягом усього процесу сортування, але, очевидно, найгірший випадок не може бути нижчим за 100% для будь-якого алгоритму сортування.
ddekany

15

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

У ситуаціях, коли в більшості випадків ВИ бажаєте максимальної швидкості, QuickSort може бути кращим над HeapSort, але жоден з них не може бути правильною відповіддю. У ситуаціях, що мають критичну швидкість, варто уважно вивчити деталі ситуації. Наприклад, у деяких моїх критично важливих для швидкості кодах дуже часто дані сортуються або майже сортуються (це індексація декількох пов’язаних полів, які часто або переміщуються вгору і вниз разом АБО рухаються вгору і вниз навпроти один одного, отже, як тільки ви сортуєте за одним, інші сортуються або зворотно сортуються, або закриваються ... кожен з яких може вбити QuickSort). У цьому випадку я не застосував ні ... натомість, я застосував SmoothSort Дейкстри ... варіант HeapSort, який O (N), коли вже відсортований або майже відсортований ... це не так елегантно, не надто легко зрозуміти, але швидко ... читатиhttp://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF, якщо ви хочете щось більш складне для кодування.


6

Гібриди Quicksort-Heapsort на місці теж справді цікаві, оскільки більшості з них потрібні лише порівняння n * log n у гіршому випадку (вони є оптимальними щодо першого терміну асимптотики, тому вони уникають гірших сценаріїв розвитку подій). Quicksort), O (log n) додатковий простір, і вони зберігають принаймні "половину" належної поведінки Quicksort щодо вже впорядкованого набору даних. Надзвичайно цікавий алгоритм представлений Дікертом та Вайсом у http://arxiv.org/pdf/1209.4214v1.pdf :

  • Виберіть шарнір p як медіану випадкової вибірки елементів sqrt (n) (це можна зробити щонайбільше в 24 порівняннях sqrt (n) за допомогою алгоритму Tarjan & co, або порівняння 5 sqrt (n) через набагато більш звивистий павук -фабричний алгоритм Шонхаге);
  • Розбийте свій масив на дві частини, як на першому кроці Quicksort;
  • Зробіть найменшу частину і використовуйте додаткові біти O (log n) для кодування купи, в якій кожна ліва дочірня частина має значення більше, ніж її брат чи сестра;
  • Рекурсивно витягніть корінь купи, просійте лакуну, залишену коренем, поки не дійде до листа купи, а потім заповніть лакуну відповідним елементом, взятим з іншої частини масиву;
  • Рекурсія за рештою невпорядкованої частини масиву (якщо p вибрано як точну медіану, рекурсії взагалі немає).

2

Комп. між quick sortі merge sortоскільки обидва є типом сортування за місцем, існує різниця між часом запуску випадку гніву, час роботи гнівного випадку, для швидкого сортування становить, O(n^2)а для сортування купи все ще є, O(n*log(n))і для середньої кількості даних швидке сортування буде більш корисним. Оскільки це рандомізований алгоритм, то ймовірність отримання правильних відповідей. менше часу залежатиме від вибраного вами положення опорного елемента.

Отже, a

Хороший дзвінок: розміри L і G кожен менше 3s / 4

Поганий дзвінок: один із L та G має розмір більше 3s / 4

для невеликої кількості ми можемо піти на сортування вставки, а для дуже великої кількості даних - сортування купи.


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

2

Хепсорт має перевагу в тому, що у найгіршому випадку працює O (n * log (n)), тому в тих випадках, коли швидка сортування, ймовірно, буде неефективною (в основному сортуються набори даних), набагато кращою є сортування.


4
Quicksort погано працює з переважно відсортованим набором даних, лише якщо обраний поганий метод вибору повороту. А саме, поганим методом вибору повороту було б завжди обирати перший або останній елемент як зв’язковий. Якщо кожен раз вибирається випадковий шарнір і використовується хороший метод обробки повторюваних елементів, ймовірність найгіршого випадку швидкого сортування дуже мала.
Джастін Піл,

1
@Justin - Це правда, я говорив про наївну реалізацію.
zellio

1
@Justin: Правда, але шанс на велике уповільнення завжди є, хоч і незначний. У деяких програмах я можу захотіти забезпечити поведінку O (n log n), навіть якщо це повільніше.
Девід Торнлі,

2

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


1

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

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

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

Це може не відповісти прямо на ваше запитання, я подумав би додати свої два центи.


1
Ніколи не використовуйте сортування бульбашок. Якщо ви обґрунтовано вважаєте, що ваші дані будуть відсортовані, ви можете скористатися сортуванням вставки або навіть протестувати дані, щоб перевірити, чи вони відсортовані. Не використовуйте бульбашки.
vy32

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

1

Сортування купи - це безпечна ставка при роботі з дуже великими входами. Асимптотичний аналіз виявляє порядок зростання Хепсорту в гіршому випадку Big-O(n logn), що є кращим, ніж у Швидкого сорту , Big-O(n^2)як найгірший випадок. Однак Heapsort на практиці на більшості машин дещо повільніший, ніж добре реалізований швидкий сорт. Хепсорт також не є стабільним алгоритмом сортування.

Причина того, що купірування на практиці повільніше, ніж швидке сортування, пояснюється кращою локальністю посилань (" https://en.wikipedia.org/wiki/Locality_of_reference ") у швидкій сортуванні, де елементи даних знаходяться у відносно близьких місцях зберігання. Системи, які демонструють сильну локальність посилань, є чудовими кандидатами для оптимізації продуктивності. Сортування купи, однак, має справу з більшими стрибками. Це робить швидкі сорти більш сприятливими для менших витрат.


2
Швидке сортування теж не є стабільним.
Сурма

1

Для мене існує дуже принципова різниця між сортовим та швидким сортами: останній використовує рекурсію. У рекурсивних алгоритмах купа зростає із збільшенням кількості рекурсій. Це не має значення, якщо n мало, але зараз я сортую дві матриці з n = 10 ^ 9 !!. Програма займає майже 10 ГБ оперативної пам'яті, і будь-яка зайва пам'ять змусить мій комп'ютер почати обмін на віртуальну пам'ять диска. Мій диск - це диск з оперативною пам’яттю, але все одно його обмін значно впливає на швидкість . Отже, у пакеті статистичних даних, кодованому на C ++, який включає матриці розмірів із регульованими розмірами, розмір яких заздалегідь невідомий програмісту, та непараметричне статистичне сортування, я віддаю перевагу купі сорту, щоб уникнути затримок використання з дуже великими матрицями даних.


1
Вам потрібна лише пам’ять O (logn) в середньому. Накладні витрати на рекурсію є тривіальними, якщо припустити, що вам не пощастить зі стержнями, і в цьому випадку у вас виникають більші проблеми, про які слід турбуватися.
Сурма

-1

Щоб відповісти на оригінальне запитання та звернутися до деяких інших коментарів тут:

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

TL; DR: Швидкий - найкращий сорт загального призначення (досить швидкий, стабільний і в основному на місці). Особисто я віддаю перевагу сортуванню купи, хоча, якщо мені не потрібне стабільне сортування.

Вибір - N ^ 2 - Це дійсно добре лише для 20 елементів або близько того, тоді він перевершує ефективність. Якщо ваші дані вже не відсортовані, або дуже, майже так. N ^ 2 дуже швидко стає дуже повільним.

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

Сортування злиття гарантоване O (n log n), оскільки його сортування не залежить від даних. Він просто робить те, що робить, незалежно від того, які цінності ви йому надали. Він також стабільний, але дуже великі сорти можуть підірвати ваш стек, якщо ви не будете обережні щодо реалізації. Є кілька складних реалізацій сортування на місці, але зазвичай вам потрібен інший масив на кожному рівні, в який слід об’єднати свої значення. Якщо ці масиви живуть у стеку, ви можете зіткнутися з проблемами.

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


Швидке сортування не є стабільним сортуванням. Окрім цього, запитання такого характеру заохочують відповіді на основі думок і можуть призвести до редагування війн та суперечок. Питання, що вимагають відповідей на основі думок, явно не рекомендуються керівними принципами SO. Відповідачі повинні уникати спокуси відповісти на них, навіть якщо вони мають значний досвід і мудрість у цьому. Або позначте їх за закриття, або почекайте, поки хтось із достатньою репутацією позначить і закриє. Цей коментар не є роздумом про ваші знання чи обгрунтованість вашої відповіді.
MikeC
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.