Розбиття на Quicksort: Хоар проти Ломуто


82

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

Hoare-Partition(A, p, r)
x = A[p]
i = p - 1
j = r + 1
while true
    repeat
        j = j - 1
    until A[j] <= x
    repeat
        i = i + 1
    until A[i] >= x
    if i < j
        swap( A[i], A[j] )
    else
        return j

і:

Lomuto-Partition(A, p, r)
x = A[r]
i = p - 1
for j = p to r - 1
    if A[j] <= x
        i = i + 1
        swap( A[i], A[j] )
swap( A[i +1], A[r] )
return i + 1

Нехтуючи методом вибору стрижня, в яких ситуаціях одна краща для іншої? Я знаю, наприклад, що Lomuto виконує порівняно погано, коли існує високий відсоток повторюваних значень (тобто там, де скажімо, що більше 2 / 3rds масиву - це одне і те ж значення), де Hoare в цій ситуації справляється чудово.

Які інші особливі випадки роблять один метод розділення значно кращим, ніж інший?


2
Я не можу придумати жодної ситуації, в якій Ломуто кращий за Хоара. Схоже, що Ломуто виконує додаткові заміни кожного разу A[i+1] <= x. У відсортованому масиві (з урахуванням обґрунтованих обертів) Хоар майже не проводить свопів, і Ломуто робить тонну (раз j стає достатньо маленькою, то всі A[j] <= x.) Що мені не вистачає?
Блукаюча логіка

2
@WanderingLogic Я не впевнений, але, здається, рішення Кормена використовувати розділ Ломуто у своїй книзі може бути педагогічним - це, здається, має досить прямий цикл інваріант.
Роберт С. Барнс

2
Зауважте, що ці два алгоритми не роблять одне і те ж. Наприкінці алгоритму Хоара зсув не на його остаточному місці. Ви можете додати а swap(A[p], A[j])в кінці Хоара, щоб отримати однакову поведінку для обох.
Aurélien Ooms

Ви також повинні перевірити наявність i < j2-х повторних циклів поділу Хоара.
Aurélien Ooms

@ AurélienOoms Копіюється безпосередньо з книги.
Роберт С. Барнс

Відповіді:


92

Педагогічний вимір

Через свою простоту метод розподілу Ломуто може бути простішим у застосуванні. У « Перлині програмування Джона Бентлі про сортування» є приємний анекдот :

"Більшість дискусій про Quicksort використовують схему розподілу, засновану на двох наближаються індексах [...] [тобто Хоара]. Хоча основна ідея цієї схеми відверта, я завжди вважав деталі складними - одного разу я провів більшу частину двох днів, переслідуючи помилку, ховаючись у короткому циклі перегородки. Читач попереднього проекту скаржився, що стандартний двоіндексний метод насправді простіший, ніж Ломуто, і накреслив якийсь код, щоб зробити свою думку; Я перестав доглядати, коли знайшов двох помилок ».

Вимір продуктивності

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

Як показано нижче, алгоритми поводяться дуже схоже на випадкові перестановки, за винятком кількості свопів . Там Ломуто потрібно втричі більше, ніж Хоаре!

Кількість порівнянь

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

Кількість свопів

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

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

Метод Ломуто

Змінна індексу сканує весь масив і кожного разу, коли ми знайдемо елемент менший за pivot , робимо своп. Серед елементів , рівно менше, ніж , тому ми отримуємо підкачки, якщо шарнір .A [ j ] x 1 , , njA[j]x1,,nx1xx1x

Тоді загальне очікування призводить до усереднення за всіма стрибками. Кожне значення в однаковою ймовірністю стане опорним (а саме з . ), тому у нас є{1,,n}1n

1nx=1n(x1)=n212.

поміняє в середньому для розподілу масиву довжиною методом Ломуто.n

Метод Хоара

Тут аналіз трохи складніше: Навіть фіксуючи pivot , кількість свопів залишається випадковою.x

Точніше: індекси та біжать назустріч один одному до тих пір, поки вони не перетинаються, що завжди відбувається при (правильність алгоритму розподілу Хоара!). Це ефективно ділить масив на дві частини: ліву частину, яку сканує і праву частину, відскановану .ijxij

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

Можна показати, що число цих пар розподілено гіпергеометрично : Для великих елементів ми випадково малюємо їхні позиції у масиві та маємо позиції у ліва частина. Відповідно, очікувана кількість пар дорівнює враховуючи, що шарнір дорівнює .Hyp(n1,nx,x1)nxx1(nx)(x1)/(n1)x

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

1nx=1n(nx)(x1)n1=n613.

(Більш детальний опис можна знайти в магістерській роботі , сторінка 29.)

Шаблон доступу до пам'яті

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

Рівні елементи та вже відсортовані списки

Як уже згадувала Wandering Logic, продуктивність алгоритмів відрізняється більш різко для списків, які не є випадковими перестановками.

У масиві, який вже відсортований, метод Хоара ніколи не змінюється, оскільки немає неправильно встановлених пар (див. Вище), тоді як метод Ломуто все ще робить приблизно підміни!n/2

Наявність рівних елементів вимагає особливої ​​обережності у Quicksort. (Я вступив у цю пастку сам; дивіться магістерську дисертацію , стор. 36, для «Казки про передчасну оптимізацію») Розгляньте як крайній приклад масив, заповнений с. У такому масиві метод Хоара здійснює своп для кожної пари елементів - що є найгіршим випадком для розділення Хоара - але і завжди зустрічаються в середині масиву. Таким чином, ми маємо оптимальний розподіл і загальний час роботи залишається в .i j O ( n журналу n )0ijO(nlogn)

Метод Ломуто поводиться набагато тупіше на всіх масивах : Порівняння завжди буде правдивим, тому ми робимо своп для кожного окремого елемента ! Але ще гірше: після циклу ми завжди маємо , тому спостерігаємо найгірший випадок поділу, зробивши загальну продуктивність погіршенням до !i = n Θ ( n 2 )0A[j] <= xi=nΘ(n2)

Висновок

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


16
Ого, це одна детальна відповідь. Чудово зроблено!
Рафаель

Погодьтеся з Рафаелем, дуже приємна відповідь!
Роберт С. Барнс

1
Я хотів би зробити невелике уточнення: коли відношення унікальних елементів до загальних елементів зменшується, кількість порівнянь, які робить Ломуто, зростає значно швидше, ніж у Хоара. Ймовірно, це пов'язано з поганим розподілом з боку Ломуто і хорошим середнім розподілом з боку Хоара.
Роберт С. Барнс

Чудове пояснення двох методів! Дякую!
v kouk

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

5

Деякі коментарі додали до відмінної відповіді Себастьяна.

Я буду говорити про алгоритм перестановки розділів взагалі, а не про його особливе використання для Quicksort .

Стабільність

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

Елемент доступу до елемента

Алгоритм Ломуто може використовуватися для спільно пов'язаного списку або подібних структур даних, призначених лише для передачі даних. Алгоритм Хоара потребує двонаправленості .

Кількість порівнянь

Алгоритм Ломуто може бути реалізований, виконуючи додатки предиката для поділу послідовності довжиною . (Хоаре теж).n1nn

Але для цього нам потрібно пожертвувати двома властивостями:

  1. Послідовність, яку потрібно розділити, не повинна бути порожньою.
  2. Алгоритм не може повернути точку розділу.

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

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