Perché Collections.sort usa merge sort invece di quicksort?


101

Sappiamo che l'ordinamento rapido è l'algoritmo di ordinamento più veloce.

Il JDK6 collections.sortutilizza l'algoritmo di ordinamento di unione invece dell'ordinamento rapido. Ma Arrays.sort utilizza l'algoritmo di ordinamento rapido.

Qual è il motivo per cui Collections.sort utilizza l'ordinamento di tipo merge invece dell'ordinamento rapido?


3
A meno che tu non riesca a far rispondere un autore JDK, tutto ciò che otterrai sono congetture. Non è una vera domanda.
Marchese di Lorne

4
@EJP Buon punto, ma sicuramente "Non costruttivo" è la giusta ragione di chiusura. Mi è chiaro qual è la domanda qui.
Duncan Jones

2
Perché i ragazzi di Java hanno deciso di farlo in questo modo. Chiediglielo. Non puoi ottenere una risposta legittima qui, credo. E l'ordinamento veloce non è il massimo. È solo il migliore per uso generico .
Adam Arold

4
Un'ipotesi: Quicksort non è stabile, Mergesort lo è. Per le primitive, un ordinamento stabile / non stabile è irrilevante, per gli oggetti potrebbe esserlo (o almeno, potresti ricevere bug segnalati contro un ordinamento instabile).
parsifal

2
@EJP, non c'è nulla che impedisca alle intenzioni degli autori di JDK di essere pubbliche. Una volta che è pubblico, non abbiamo bisogno che l'autore stesso risponda. È infatti possibile ottenere una risposta che è più che ipotetica anche senza che un autore JDK risponda.
Pacerier

Risposte:


187

Molto probabilmente da Josh Bloch § :

Ho scritto questi metodi, quindi suppongo di essere qualificato per rispondere. È vero che non esiste un unico miglior algoritmo di ordinamento. QuickSort ha due principali carenze rispetto a Mergesort:

  1. Non è stabile (come notato da parsifal).

  2. Non garantisce n log n prestazioni; può degradare a prestazioni quadratiche su input patologici.

La stabilità non è un problema per i tipi primitivi, poiché non esiste una nozione di identità distinta dall'uguaglianza (di valore). E la possibilità di un comportamento quadratico non era considerata un problema in pratica per l'implementazione di Bentely e McIlroy (o successivamente per Dual Pivot Quicksort ), motivo per cui queste varianti QuickSort sono state utilizzate per i tipi primitivi.

La stabilità è un grosso problema quando si ordinano oggetti arbitrari. Ad esempio, supponi di avere oggetti che rappresentano i messaggi di posta elettronica e di ordinarli prima per data, poi per mittente. Ti aspetti che vengano ordinati per data all'interno di ciascun mittente, ma ciò sarà vero solo se l'ordinamento è stabile. Ecco perché abbiamo scelto di fornire un ordinamento stabile (Merge Sort) per ordinare i riferimenti agli oggetti. (Tecnicamente parlando, più ordinamenti stabili sequenziali producono un ordinamento lessicografico sulle chiavi nell'ordine inverso degli ordinamenti: l'ordinamento finale determina la sottochiave più significativa.)

È un bel vantaggio collaterale che Merge Sort garantisce n log n (tempo) prestazioni indipendentemente dall'input. Ovviamente c'è un lato negativo: l'ordinamento rapido è un ordinamento "sul posto": richiede solo il log in uno spazio esterno (per mantenere lo stack di chiamate). Unisci, ordina, d'altra parte, richiede O (n) spazio esterno. La variante TimSort (introdotta in Java SE 6) richiede sostanzialmente meno spazio (O (k)) se l'array di input è quasi ordinato.

Inoltre, è rilevante quanto segue :

L'algoritmo utilizzato da java.util.Arrays.sort e (indirettamente) da java.util.Collections.sort per ordinare i riferimenti agli oggetti è un "mergesort modificato (in cui l'unione viene omessa se l'elemento più alto nella sottolista inferiore è minore di l'elemento più basso nella sottolista superiore). " È un ordinamento stabile e ragionevolmente veloce che garantisce prestazioni O (n log n) e richiede O (n) spazio aggiuntivo. Ai suoi tempi (è stato scritto nel 1997 da Joshua Bloch), è stata una buona scelta, ma oggi possiamo fare molto meglio.

Dal 2003, l'ordinamento elenco di Python utilizza un algoritmo noto come timsort (dal nome di Tim Peters, che lo ha scritto). È un mergesort iterativo stabile, adattivo che richiede molto meno di n confronti log (n) quando viene eseguito su array parzialmente ordinati, offrendo prestazioni paragonabili a un mergesort tradizionale quando viene eseguito su array casuali. Come tutti i Mergesort appropriati, il Timsort è stabile e viene eseguito nel tempo O (n log n) (caso peggiore). Nel peggiore dei casi, timsort richiede spazio di archiviazione temporaneo per n / 2 riferimenti a oggetti; nel migliore dei casi, richiede solo una piccola quantità di spazio costante. Confrontalo con l'implementazione corrente, che richiede sempre spazio aggiuntivo per n riferimenti a oggetti, e batte n log n solo su elenchi quasi ordinati.

Timsort è descritto in dettaglio qui: http://svn.python.org/projects/python/trunk/Objects/listsort.txt .

L'implementazione originale di Tim Peters è scritta in C. Joshua Bloch l'ha portata da C a Java e ha testato, valutato e messo a punto ampiamente il codice risultante. Il codice risultante è un sostituto immediato di java.util.Arrays.sort. Su dati altamente ordinati, questo codice può essere eseguito fino a 25 volte più velocemente dell'implementazione corrente (sulla VM del server HotSpot). Su dati casuali, le velocità della vecchia e della nuova implementazione sono comparabili. Per elenchi molto brevi, la nuova implementazione è sostanzialmente più veloce della vecchia anche su dati casuali (perché evita la copia non necessaria dei dati).

Inoltre, vedere Java 7 utilizza Tim Sort per i Method Arrays.Sort? .

Non esiste un'unica scelta "migliore". Come per molte altre cose, si tratta di compromessi.

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.