Алгоритми сортування, які приймають випадковий компаратор


22

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

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

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

Які ще алгоритми сортування (крім сортування об'єднань) працюватимуть, як описано у випадковому компараторі?


  1. Для довідки, компаратор - це відношення до порядку, якщо воно є належною функцією (детермінованою) і відповідає аксіомам відношення порядку:

    • це детерміновано: compare(a,b)для конкретного aі bзавжди повертає той самий результат.
    • це перехідно: compare(a,b) and compare(b,c) implies compare( a,c )
    • це антисиметричне compare(a,b) and compare(b,a) implies a == b

(Припустимо, що всі вхідні елементи відрізняються, тому рефлексивність не є проблемою.)

Випадковий компаратор порушує всі ці правила. Однак є порівняльники, які не є порядковими відносинами, але не є випадковими (наприклад, вони можуть порушити, можливо, лише одне правило, і лише для окремих елементів у наборі).


(1) Що ви маєте на увазі під стабільним функцією порівняння? (2) Чи є "нестабільними" та "випадковими" синонімами?
Цуйосі Іто

"бігати за їх типовою цитованою часовою складністю (на відміну від приниження до найгіршого сценарію" - типово цитується часова складність є найгіршим випадком! "упорядкування буде справедливим випадковим впорядкуванням" - "справедливим" ви маєте на увазі рівномірне? Чи вважаєте ви, що і порівняльник є рівномірним?
Рафаель

Можливо, не у формальній теорії, але на практиці (мови програмування) багато речей цитуються за амортизованим часом. Наприклад, квакісорт часто відображається як але насправді є . O ( n 2 )O(logn)O(n2)
edA-qa mort-ora-y

4
@ edA-qamort-ora-y: (1) Ви маєте на увазі , а не . (2) Це не те, що означає " амортизований час "; ви маєте на увазі " очікуваний час " або менш формально "типовий час". O ( журнал n )O(nlogn)O(logn)
JeffE

1
Ніхто не звертався до (мені) більш цікавого питання, поставленого вище: які алгоритми сортування (якщо такі є) мають властивість, що якщо компаратор перевернути монету, то результат - рівномірна перестановка.
Джо

Відповіді:


13

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

int Compare(object a, object b) { return Random.Next(-1,1); }

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

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

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

Наприклад, SelectionSort повторює через підпис списків несортованих елементів, знаходить «найменший» та / або «найбільший» елемент (порівнюючи кожен із найбільших дотепер), розміщує його у правильному положенні та повторює. Як результат, навіть із недетермінованим компаратором, наприкінці кожної ітерації алгоритм знайде значення, яке, на його думку, є найменшим або найбільшим, замінює його елементом у позиції, яку він намагається визначити, і ніколи не враховує цей елемент знову, тому він підпорядковується умові 2. Однак під час цього процесу A і B можна порівняти кілька разів (як найбільш крайній приклад, розглянемо кілька проходів SelectionSort на масиві, відсортованому у зворотному порядку), тому він порушує Умова 1 .

MergeSort підкоряється умові 1, але не 2; у міру злиття підмасивів елементи в одному підмасиві (зліва чи справа) не порівнюються один з одним, оскільки вже визначено, що елементи на тій стороні масиву є в порядку між собою; алгоритм порівнює лише найменш занурений елемент кожного підмасиву з іншим, щоб визначити, який є меншим і повинен перейти далі у списку, що об'єднався. Це означає, що будь-які два унікальні об’єкти A і B будуть порівнюватися один з одним максимум один раз, але "остаточний" індекс елемента в повній колекції не відомий до завершення алгоритму.

InsertionSort підкоряється лише умові 1, навіть незважаючи на те, що його загальна стратегія та складність нагадують SelectionSort. Кожен несортований елемент порівнюється з відсортованими елементами, найбільшими-першими, поки не буде знайдено той, який менше, ніж досліджуваний елемент. елемент вставляється в цій точці, а потім розглядається наступний елемент. Результатом є те, що відносний порядок будь-яких A і B визначається одним порівнянням, і подальше порівняння між A і B ніколи не проводиться, але остаточне положення будь-якого елемента не може бути відоме, поки всі елементи не будуть розглянуті.

QuickSort підкоряється обомУмови. На кожному рівні шарнір вибирається і розташовується таким чином, що "ліва" сторона містить елементи менше, ніж шарнір, а "права" сторона містить елементи, що перевищують шарнір. Результатом цього рівня є QuickSort (зліва) + шарнір + QuickSort (праворуч), що в основному означає, що положення шарнірного елемента відоме (один індекс більший за довжину лівої сторони), шарнір ніколи не порівнюється з будь-яким іншим елементом після того, як він був обраний як шарнірний (він, можливо, був порівняний з попередніми шарнірними елементами, але ці елементи також відомі і не включаються в жодні підриви), і будь-які A і B, які закінчуються на протилежних сторонах повороту, ніколи не будуть порівнювали. У більшості реалізацій чистого QuickSort базовий випадок є одним з елементів, і в цей момент його поточний індекс є його кінцевим індексом, і подальше порівняння не проводиться.

(2/3)N1). Зі збільшенням максимального абсолютного значення результату компаратора ймовірність будь-якого порівняння повернути негатив або нуль зменшується до .5, що робить шанс закінчити алгоритм набагато менш імовірним (шанс 99 монет перевернути всі посадочні голови , що в основному доводиться до 1 до 1,2 * 10 30 )

РЕДАКТУВАННЯ ДОВГОГО ЧАСУ: Є декілька "різновидів", розроблених спеціально як приклади того, що не потрібно робити, які містять випадковий компаратор; мабуть, найвідоміший - BogoSort. Msgstr "Якщо список наданий, якщо список не в порядку, перетасуйте його та перевірте ще раз". Теоретично він врешті-решт потрапить на правильну перестановку значень, як і "неоптимізований BubbleSort" вище, але середній випадок є фактичним часом (N! / 2), і через проблему з днем ​​народження (після достатньої кількості випадкових перестановок ви більше шансів зустріти дублюючі перестановки, ніж унікальні), існує ненульова можливість алгоритму, який ніколи не завершується офіційно, алгоритм не обмежений часом.


Чи буде умова 2 також охоплювати швидке сортування? Або більше третьої умови про те, щоб кожна ітерація була меншою, ніж остання.
edA-qa mort-ora-y

На мою думку, QuickSort би охоплювався обома умовами. У ефективних програмах QuickSorts ви вибираєте шарнір, потім порівнюєте кожен елемент з ним і міняєте елементами, що знаходяться на неправильній "стороні" шарніра. Після впорядкування елементів функція повертає QuickSort (ліворуч) + шарнір + QuickSort (праворуч) і шарнір не передається на нижчі рівні. Отже, обидві умови вірні; ви ніколи не порівнюєте жодного унікального a і b більше, ніж один раз, і визначили індекс зрізу до того часу, як закінчите впорядкувати інші елементи.
KeithS

Чудова відповідь, але я не згоден з вами щодо BubbleSort. Використовуючи послідовний компаратор, на i-й ітерації BubbleSort знає, що останні елементи i-1 знаходяться на останньому місці, і будь-яка розумна реалізація BubbleSort буде проходити через менше елементів у кожній ітерації, тому вона також повинна зупинятися після n ітерацій .
Борис Трайвас

Після ще однієї думки я схильний би погодитися з вами; після проходження X найбільші значення X знаходяться на своєму належному місці, тож ви можете зменшити проблемний простір на кожному проході, і тому ефективний алгоритм буде підкорятися
Умові

Вам доведеться бути обережними з реалізацією Quicksort. Можливо, існує припущення, що пошук елемента, не меншого за зріз, закінчиться, коли ми зустрінемося із стрижнем або елементом, більшим за зведений; це не обов'язково так.
gnasher729

10

O(n2)

n


Редагувати: Проблема є цікавішою, як я думав спочатку, тому ось подальший коментар:

comparecompare(x,y)=true1/2false1/2

insert x [] = [x]
insert x y:ys = if x < y then x:y:ys
                else y:insert x ys

sort_aux l e = match l with
                 [] -> e
                 x:xs -> sort_aux xs (insert x ys)

sort l = sort_aux l []

k=1nf(k)nlf(k)insertk:

compare

i=1ki2ii=1i2i=2

O(2n)O(n2)

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


Quicksort може повторювати порівняння, якщо один і той же елемент обраний як стрижневий більше одного разу (у списку це може траплятися кілька разів).
Рафаель

2
@Raphael: Мій вибір слів був поганим: я мав на увазі повторне порівняння між входженнями елементів, які не зустрічаються більше ніж один раз у Quicksort.
коді

1
@Gilles: я можу помилятися, але я не вірю, що транзитивність порівняння має вирішальне значення для виконання більшості алгоритмів сортування; правильність, безумовно, але це не було предметом питання.
коді

@Gilles: ОП не запитує про алгоритми, які насправді сортують. Він запитує про те, що відбувається зі стандартними алгоритмами сортування, коли всі порівняння замінені на монетки. Отримані алгоритми не сортують (за винятком крихітної ймовірності), але вони все ще добре визначені алгоритми.
JeffE

@JeffE Зараз я це розумію. Це не так, як я читав питання спочатку, але, враховуючи коментарі запитувача, саме це малося на увазі.
Жил "ТАК - перестань бути злим"

2

Злиття з справедливим випадковим компаратором не є справедливим. Я не маю доказів, але у мене ДУЖЕ міцні емпіричні докази. (Ярмарок означає рівномірно розподілений.)

module Main where

import Control.Monad
import Data.Map (Map)
import qualified Data.Map as Map
import System.Random (randomIO)

--------------------------------------------------------------------------------

main :: IO ()
main = do
  let xs = [0..9]
  xss <- replicateM 100000 (msortRand xs)
  print $ countFrequencies xss

msortRand :: [a] -> IO [a]
msortRand = msort (\_ _ -> randomIO)

countFrequencies :: (Ord a) => [[a]] -> [Map a Int]
countFrequencies [] = []
countFrequencies xss = foldr (\k m -> Map.insertWith (+) k 1 m) Map.empty ys : countFrequencies wss
  where
    ys = map head xss
    zss = map tail xss
    wss = if head zss == []
      then []
      else zss

--------------------------------------------------------------------------------

msort :: (Monad m) => (a -> a -> m Bool) -> [a] -> m [a]
msort (<) [] = return []
msort (<) [x] = return [x]
msort (<) xs = do
  ys' <- msort (<) ys
  zs' <- msort (<) zs
  merge (<) ys' zs'
  where
    (ys, zs) = split xs

merge :: (Monad m) => (a -> a -> m Bool) -> [a] -> [a] -> m [a]
merge (<) [] ys = return ys
merge (<) xs [] = return xs
merge (<) (x:xs) (y:ys) = do
  bool <- x < y
  if bool
    then liftM (x:) $ merge (<) xs (y:ys)
        else liftM (y:) $ merge (<) (x:xs) ys

split :: [a] -> ([a], [a])
split [] = ([], [])
split [x] = ([x], [])
split (x:y:zs) = (x:xs, y:ys)
  where
    (xs, ys) = split zs

Зараз Haskell чи Caml у моді?
Yai0Phah

Я поняття не маю. Але Haskell - моя улюблена мова, тому я запрограмував це на ній; Узгодження шаблону спростило це.
Томас Едінг

0

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

Цитування з реферату:

...

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

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

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