Algoritmi di ordinamento che accettano un comparatore casuale


22

Gli algoritmi di ordinamento generici generalmente richiedono un set di dati per l'ordinamento e una funzione di confronto che può confrontare due singoli elementi. Se il comparatore è una relazione d'ordine¹, l'output dell'algoritmo è un elenco / array ordinato.

Mi chiedo però quali algoritmi di ordinamento funzionerebbero effettivamente con un comparatore che non è una relazione d'ordine (in particolare uno che restituisce un risultato casuale su ogni confronto). Per "lavoro" intendo qui che continuano a restituire una permutazione del loro input e corrono alla loro complessità temporale tipicamente quotata (al contrario di degradare sempre allo scenario peggiore, o andare in un ciclo infinito o elementi mancanti). L'ordinamento dei risultati sarebbe tuttavia indefinito. Ancora meglio, l'ordinamento risultante sarebbe una distribuzione uniforme quando il comparatore è un lancio di moneta.

Dal mio rozzo calcolo mentale sembra che un tipo di unione andrebbe bene con questo e mantenga lo stesso costo di runtime e produca un ordinamento casuale equo. Penso che qualcosa come una specie veloce sarebbe comunque degenerato, forse non finirà, e non sarebbe giusto.

Quali altri algoritmi di ordinamento (diversi da unisci ordinamento) funzionerebbero come descritto con un comparatore casuale?


  1. Per riferimento, un comparatore è una relazione d'ordine se è una funzione propria (deterministica) e soddisfa gli assiomi di una relazione d'ordine:

    • è deterministico: compare(a,b)per un particolare ae brestituisce sempre lo stesso risultato.
    • è transitivo: compare(a,b) and compare(b,c) implies compare( a,c )
    • è antisimmetrico compare(a,b) and compare(b,a) implies a == b

(Supponiamo che tutti gli elementi di input siano distinti, quindi la riflessività non è un problema.)

Un comparatore casuale viola tutte queste regole. Esistono tuttavia comparatori che non sono relazioni d'ordine ma non sono casuali (ad esempio potrebbero violare forse solo una regola e solo per elementi particolari dell'insieme).


(1) Cosa intendi con la funzione di confronto essendo stabile? (2) "non stabile" e "casuale" sono sinonimi?
Tsuyoshi Ito,

"corri alla loro complessità temporale tipicamente quotata (al contrario del degrado allo scenario peggiore" - la complessità temporale citata tipicamente è il caso peggiore! "l'ordinamento sarebbe un ordinamento casuale equo" - PER "giusto" intendi uniforme? Supponi che anche il comparatore sia uniforme?
Raphael

Forse non nella teoria formale, ma nella pratica (linguaggi di programmazione) molte cose sono citate in tempo ammortizzato. Ad esempio, quicksort viene spesso visualizzato come ma in realtà è O ( n 2 ) . O(logn)O(n2)
edA-qa mort-ora-y

4
@ edA-qamort-ora-y: (1) Intendi , non O ( log n ) . (2) Non è quello che significa " tempo ammortizzato "; intendi " tempo previsto ", o meno formalmente, "tempo tipico". O(nlogn)O(logn)
JeffE,

1
Nessuno ha affrontato (per me) la domanda più interessante posta sopra: quali algoritmi di ordinamento (se presenti) hanno la proprietà che se il comparatore è un lancio di moneta, il risultato è una permutazione uniforme.
Joe,

Risposte:


13

Quindi, in sostanza, vuoi sapere se esiste un algoritmo di ordinamento che non si degraderebbe dal suo caso medio se avesse una funzione di confronto simile a:

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

... dove Random.Next () è un metodo che produrrà un numero intero generato casualmente tra un limite inferiore e superiore inclusi.

La risposta è in realtà che la maggior parte degli algoritmi di ordinamento di base funzionerà secondo il loro caso medio, perché obbediscono ad almeno una delle due seguenti condizioni:

  1. Un confronto tra due elementi unici non viene mai effettuato due volte nell'ordinamento e / o
  2. In ogni iterazione dell'ordinamento, viene determinata la posizione corretta di almeno un elemento e quindi l'elemento non viene mai più confrontato.

Ad esempio, SelectionSort scorre l'elenco secondario di elementi non ordinati, trova l'elemento "minimo" e / o "maggiore" (confrontando ciascuno con il massimo finora), lo posiziona nella posizione corretta e si ripete. Di conseguenza, anche con un comparatore non deterministico, alla fine di ogni iterazione l'algoritmo avrà trovato un valore che ritiene minimo o maggiore, lo scambia con l'elemento nella posizione che sta cercando di determinare e non considera mai quell'elemento di nuovo, quindi obbedisce alla Condizione 2. Tuttavia, A e B possono essere confrontati più volte durante questo processo (come esempio più estremo, considera diversi passaggi di SelectionSort su un array che è ordinato in ordine inverso), quindi viola la Condizione 1 .

MergeSort obbedisce alla Condizione 1 ma non a 2; man mano che i sotto-array vengono uniti, gli elementi nello stesso sotto-array (sul lato sinistro o destro) non vengono confrontati tra loro perché è già stato stabilito che gli elementi su quel lato dell'array sono in ordine tra loro; l'algoritmo confronta solo l'elemento meno immerso di ciascun subarray con l'altro per determinare quale è minore e dovrebbe andare successivamente nell'elenco unito. Ciò significa che due oggetti unici A e B verranno confrontati tra loro al massimo una volta, ma l'indice "finale" di un determinato elemento nell'intera raccolta non è noto fino a quando l'algoritmo non è completo.

InsertionSort obbedisce solo alla Condizione 1, anche se la sua strategia e complessità complessive assomigliano più a SelectionSort. Ogni elemento non ordinato viene confrontato con elementi ordinati, il più grande per primo, fino a quando non ne viene trovato uno inferiore all'elemento in esame. l'elemento viene inserito in quel punto e quindi viene considerato l'elemento successivo. Il risultato è che l'ordine relativo di qualsiasi A e B è determinato da un confronto, e ulteriori confronti tra tale A e B non vengono mai eseguiti, ma la posizione finale di qualsiasi elemento non può essere conosciuta fino a quando non vengono considerati tutti gli elementi.

QuickSort obbedisce ad entrambiCondizioni. Ad ogni livello, un perno viene scelto e disposto in modo tale che il lato "sinistro" contenga elementi inferiori al perno e il lato "destro" contenga elementi maggiori del perno. Il risultato di quel livello è QuickSort (a sinistra) + pivot + QuickSort (a destra) che sostanzialmente significa che la posizione dell'elemento pivot è nota (un indice maggiore della lunghezza del lato sinistro), il pivot non viene mai confrontato con nessun altro elemento dopo che è stato scelto come perno (potrebbe essere stato confrontato con elementi pivot precedenti, ma quegli elementi sono anche noti e non sono inclusi in alcun subarrays), E qualsiasi A e B che finiscono su lati opposti del perno non sono mai rispetto. Nella maggior parte delle implementazioni di QuickSort puro, il caso base è un elemento, a quel punto il suo indice corrente è il suo indice finale e non vengono effettuati ulteriori confronti.

(2/3)N1). All'aumentare del valore assoluto massimo del risultato del comparatore, la probabilità che uno qualsiasi di questi confronti ritorni negativo o zero diminuisce verso 0,5, rendendo la possibilità di terminare l'algoritmo molto meno probabile (la possibilità di 99 gettoni lancia tutte le teste di atterraggio , che è sostanzialmente ciò a cui si riduce, è 1 in 1,2 * 10 30 )

EDIT A LATER LONG TIME: Ci sono alcuni "tipi" progettati specificamente come esempi di cosa non fare che incorporano un comparatore casuale; forse il più famoso è BogoSort. "Dato un elenco, se l'elenco non è in ordine, mescola l'elenco e ricontrolla". Teoricamente alla fine colpirà la giusta permutazione di valori, proprio come il "BubbleSort non ottimizzato" sopra, ma il caso medio è tempo fattoriale (N! / 2), e a causa del problema del compleanno (dopo sufficienti permutazioni casuali tu diventare più probabilità di incontrare permutazioni duplicate rispetto a quelle uniche) c'è una possibilità diversa da zero che l'algoritmo che non si completa ufficialmente non è limitato nel tempo.


La condizione 2 coprirebbe anche l'ordinamento rapido? O sarebbe più di una terza condizione per ogni iterazione più piccola della precedente.
edA-qa mort-ora-y

Secondo me QuickSort sarebbe coperto da entrambe le condizioni. In QuickSorts efficienti, scegli il pivot, quindi confronta ogni elemento con esso e scambia gli elementi che si trovano sul "lato" sbagliato del pivot. Una volta disposti gli elementi, la funzione restituisce QuickSort (a sinistra) + pivot + QuickSort (a destra) e il pivot non viene passato ai livelli inferiori. Quindi, entrambe le condizioni sono vere; non confronti mai un unico aeb più di una volta e hai determinato l'indice del perno quando hai finito di disporre gli altri elementi.
KeithS,

Ottima risposta, ma non sono d'accordo con te su BubbleSort. Quando si utilizza un comparatore coerente, nell'i-esima iterazione BubbleSort sa che gli ultimi elementi di i-1 sono nella loro posizione finale e qualsiasi implementazione ragionevole di BubbleSort passerà attraverso meno elementi ogni iterazione, quindi dovrebbe anche arrestarsi dopo n iterazioni .
Boris Trayvas,

Dopo qualche altro pensiero, tenderei ad essere d'accordo con te; dopo che X passa, i valori X più alti si trovano nella loro posizione corretta, quindi puoi ridurre lo spazio problematico su ogni passaggio e quindi un algoritmo efficiente obbedirà alla Condizione 2.
Modificherò

Dovresti stare attento con l'implementazione di Quicksort. Si può supporre che una ricerca di un elemento non inferiore al perno finirà quando incontriamo il perno o un elemento maggiore del perno; non sarebbe necessariamente così.
gnasher729,

10

O(n2)

n


Modifica: il problema è più interessante come ho pensato per la prima volta, quindi ecco un ulteriore commento:

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)

Sarebbe divertente calcolare i tempi di esecuzione medi per i diversi altri algoritmi data questa funzione di confronto uniforme.


Quicksort può ripetere i confronti se lo stesso elemento viene scelto come perno più di una volta (può verificarsi più volte nell'elenco).
Raffaello

2
@Raphael: La mia scelta delle parole era scarsa: intendevo ripetere i confronti tra occorrenze di elementi, che non si verificano più di una volta in Quicksort.
cody

1
@Gilles: potrei sbagliarmi, ma non credo che la transitività del confronto sia cruciale per il runtime della maggior parte degli algoritmi di ordinamento; la correttezza sicuramente, ma non era questo l'oggetto della domanda.
cody

@Gilles: L'OP non sta chiedendo degli algoritmi che effettivamente ordinano. Sta chiedendo cosa succede agli algoritmi di ordinamento standard quando tutti i confronti vengono sostituiti con lanci di monete. Gli algoritmi risultanti non vengono ordinati (tranne con una probabilità minima), ma sono comunque algoritmi ben definiti.
JeffE,

@JeffE Lo capisco adesso. Inizialmente non è così che ho letto la domanda, ma dati i commenti del richiedente, ecco cosa intendevo.
Gilles 'SO- smetti di essere malvagio' il

2

Mergesort con un comparatore casuale equo non è giusto. Non ho una prova, ma ho prove empiriche MOLTO forti. (Fiera significa uniformemente distribuito.)

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 o Caml sono di moda adesso?
Yai0Phah,

Non ne ho idea. Ma Haskell è la mia lingua preferita, quindi l'ho programmato in esso; la corrispondenza del modello ha reso questo più facile.
Thomas Eding,

0

Una domanda molto correlata trova risposta in Tutti i tipi di permutazioni (perla funzionale) di Christiansen, Danilenko e Dylus. Eseguono un algoritmo di ordinamento nella monade elenco , che simula essenzialmente il non determinismo, restituendo tutte le permutazioni di un determinato elenco di input. La proprietà interessante è che ogni permutazione viene restituita esattamente una volta.

Citando dall'abstract:

...

In questo articolo esaminiamo la combinazione di non determinismo e ordinamento in una luce diversa: data una funzione di ordinamento, la applichiamo a un predicato non deterministico per ottenere una funzione che enumera le permutazioni dell'elenco di input. Arriviamo in fondo alle proprietà necessarie degli algoritmi di ordinamento e dei predicati in gioco e discutiamo delle variazioni del non determinismo modellato.

Inoltre, formuliamo e dimostriamo un teorema che afferma che, indipendentemente dalla funzione di ordinamento che utilizziamo, la funzione di permutazione corrispondente elenca tutte le permutazioni dell'elenco di input. Usiamo teoremi liberi, che sono derivati ​​dal tipo di una sola funzione, per provare l'affermazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.