Алгоритми ділення та перемоги - чому б не розділити на більшу частину, ніж дві?


33

У алгоритмах розділення та підкорення, таких як кваксорт і злиття, введення, як правило, (принаймні у вступних текстах) розділяється надвоє , а два менші набори даних обробляються рекурсивно. Для мене має сенс, що це дозволяє швидше вирішити проблему, якщо обидві половинки займають менше половини роботи над вирішенням всього набору даних. Але чому б не розділити набір даних на три частини? Четверо? n ?

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

Я також бачив багато посилань на тристоронній кваксор. Коли це швидше? Що використовується на практиці?


Спробуйте створити алгоритм, подібний до quicksort, який розбиває масив на три частини.
gnasher729

Відповіді:


49

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

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

Але чому б не розділити набір даних на три частини? Четверо? n?

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

Наприклад, якщо ви робите сортування тристороннього злиття, то на етапі рекомбінації вам зараз доведеться знайти найбільший з 3 елементів для кожного елемента, для чого потрібно 2 порівняння замість 1, тож ви зробите вдвічі більше порівнянь . В обмін на це ви зменшуєте глибину рекурсії в коефіцієнт ln (2) / ln (3) == 0,63, тому у вас на 37% менше свопів, але на 2 * 0,63 == 26% більше порівнянь (і доступу до пам'яті). Добре це чи погано, залежить від того, яка вартість вашої апаратури дорожча.

Я також бачив багато посилань на тристоронній кваксор. Коли це швидше?

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

Що використовується на практиці?

У наші дні навряд чи хтось програмує власні алгоритми сортування; вони використовують один, наданий бібліотекою. Наприклад, API Java 7 фактично використовує подвійний шарнір.

Люди, які насправді програмують свій алгоритм сортування з якихось причин, як правило, будуть дотримуватися простого двостороннього варіанту, оскільки менший потенціал помилок перевищує 20% кращу ефективність більшості часу. Пам'ятайте: на сьогоднішній день найважливішим підвищенням продуктивності є те, коли код переходить від "не працює" до "працює".


1
Невелика примітка: Java 7 використовує Dual-Pivot quicksort лише під час сортування примітивів. Для сортування об'єктів використовується Timsort.
Бакуріу

1
+1 за "У наші дні навряд чи хтось більше програмує власні алгоритми сортування" та (що ще важливіше) "Пам'ятайте: на сьогоднішній день найважливішим підвищенням продуктивності є те, коли код переходить від" не працює "до" працюючого ". Однак я хотів би дізнатися, чи все-таки це накладне банальне значення, якщо, наприклад, хтось розбиває набір даних на багато-багато частин. Як це так буває, так що є інші люди: bealto.com/gpu-sorting_intro.html stackoverflow.com/questions/1415679 / ... devgurus.amd.com/thread/157159
AndrewJacksonZA

Я трохи повільний. Чи може хтось пояснити, чому для порівняння потрібно 2 * 0,69? Не впевнений, звідки взявся 0,69.
jeebface

@jeebface на жаль, це був помилковий помилок (зараз виправлено). Це 0,63 (зменшення глибини рекурсії), тоді також виходить результат на 26% більше.
Майкл Боргвардт

30

Асимптотично кажучи, це не має значення. Наприклад, двійковий пошук робить приблизно  порівняння журналу 2 n, а потрійний пошук робить приблизно  порівняння журналу 3 n. Якщо ви знаєте свої логарифми, ви знаєте, що log a  x = log b  x / log b  a, тому двійковий пошук складає лише близько 1 / log 3 2 ≈ в 1,5 рази більше порівнянь, ніж потрійний пошук. Це також причина, коли ніхто ніколи не визначає основу логарифму у великій О-нотації: це завжди постійний фактор, віддалений від логарифму в заданій базі, незалежно від того, яка база фактична. Таким чином, розбиття проблеми на кілька підмножин не покращує часову складність і практично недостатньо, щоб перевершити складнішу логіку. Насправді ця складність може негативно впливати на практичну ефективність, збільшуючи тиск кешу або роблячи мікрооптимізацію менш неможливою.

З іншого боку, в деяких структурах даних про дерева використовуються високий коефіцієнт розгалуження (набагато більший за 3, часто 32 і більше), хоча зазвичай з інших причин. Це покращує використання ієрархії пам’яті: структури даних, що зберігаються в оперативній пам’яті, краще використовують кеш, структури даних, що зберігаються на диску, вимагають меншої кількості зчитування HDD-> ОЗУ.


Так, знайдіть октрис для конкретного застосування структури, що перевищує двійкове дерево.
daaxix

@daaxix btree, мабуть, частіше.
Жуль

4

Існують алгоритми пошуку / сортування, які ділять не на два, а на N.

Простий приклад - пошук за допомогою хеш-кодування, який займає час O (1).

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

Принциповим є питання, коли програма вивчає деякі дані, а потім вводить деякі наступні стани, скільки наступних станів існує і наскільки близькі до рівних їхні ймовірності?

Коли комп’ютер робить порівняння двох чисел, скажімо, а потім або стрибає, чи ні, якщо обидва шляхи однаково ймовірні, програмний лічильник "знає" ще один біт інформації на кожному шляху, тому в середньому він "навчився" одного біт. Якщо проблема вимагає засвоєння M бітів, то, використовуючи бінарні рішення, вона не може отримати відповідь менше, ніж M рішення. Так, наприклад, пошук числа в відсортованій таблиці розміром 1024 не може бути виконано за меншими, ніж 10 бінарними рішеннями, хоча б тому, що будь-яка менша кількість не матиме достатньої кількості результатів, але це, безумовно, може бути зроблено в більшій кількості, ніж це.

Коли комп'ютер бере одне число і перетворює його в індекс в масив, він «вчиться» до лог-бази 2 числа елементів масиву, і це робить це в постійний час. Наприклад, якщо є таблиця стрибків з 1024 записів, все більш-менш однаково вірогідна, то перехід через цю таблицю "дізнається" 10 біт. Це основна хитрість хеш-кодування. Сортуючим прикладом цього є те, як можна сортувати колоду карт. Майте 52 бункери, по одному на кожну карту. Перекиньте кожну карту у свій контейнер, а потім вичерпайте їх усі. Не потрібне підрозділення.


1

Оскільки це питання про загальний поділ і підкорення, а не просто сортування, я здивований, що ніхто не підніс Теорему Магістра

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

Алгоритм Карацуби для множення використовує тристороннє ділення та підкорення для досягнення часу запуску O (3 n ^ log_2 3), який б'є O (n ^ 2) для звичайного алгоритму множення (n - кількість цифр у числа).


У теоремі Master кількість створених підзадач не є єдиним фактором. У Карацуби та його двоюрідного брата Страссена поліпшення насправді відбувається завдяки розумному злиття рішень деяких підпроблем, тому ви зменшуєте кількість рекурсивних викликів підпроблем. Коротше кажучи, bосновна теорема, що піднімається, вимагає, aщоб ви проходили повільніше, щоб ви покращили подальший поділ.
ПоінформованоAA

-4

Через свою двійкову природу комп'ютер дуже ефективний при поділі речей на 2, а не на 3. Ви отримуєте поділ на 3, розділивши їх на 2, а потім розділіть одну з частин ще раз на 2. Отже, якщо вам потрібно розділити на 2, щоб отримати 3 поділу, ви також можете розділити на 2.

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