Perché Quicksort è meglio di altri algoritmi di ordinamento in pratica?


31

Questo è un ripubblicare di una domanda su cs.SE di Janoma . Crediti completi e bottino a lui o cs.SE.

In un corso di algoritmi standard ci viene insegnato che quicksort è O (n log n) in media e O (n²) nel peggiore dei casi. Allo stesso tempo, vengono studiati altri algoritmi di ordinamento che sono O (n log n) nel peggiore dei casi (come mergesort e heapsort ), e persino il tempo lineare nel migliore dei casi (come bubblesort ) ma con qualche necessità aggiuntiva di memoria.

Dopo una rapida occhiata ad alcuni altri tempi di esecuzione , è naturale dire che quicksort non dovrebbe essere efficiente come altri.

Inoltre, considera che gli studenti imparano nei corsi di programmazione di base che la ricorsione non è davvero buona in generale perché potrebbe usare troppa memoria, ecc. Pertanto (e anche se questo non è un vero argomento), questo dà l'idea che Quicksort potrebbe non essere davvero buono perché è un algoritmo ricorsivo.

Perché, quindi, quicksort supera in pratica altri algoritmi di ordinamento? Ha a che fare con la struttura dei dati del mondo reale ? Ha a che fare con il modo in cui la memoria funziona nei computer? So che alcuni ricordi sono molto più veloci di altri, ma non so se questo è il vero motivo di questa performance contro-intuitiva (rispetto alle stime teoriche).


3
La reputazione di Quicksort risale a un momento in cui la cache non esisteva.
AProgrammer,

9
"perché quicksort supera in pratica altri algoritmi di ordinamento?" Sicuro che sia vero? Mostraci la vera implementazione a cui ti riferisci con questa affermazione, e la community ti dirà perché quella specifica implementazione si comporta come fa. Tutto il resto porterà a supposizioni selvagge su programmi inesistenti.
Doc Brown

1
@DocBrown: Molte implementazioni Quicksort (o varianti) sono scelte in molte librerie, probabilmente perché funzionano meglio (lo spero, cioè). Quindi potrebbe esserci qualcosa sull'algoritmo che rende veloce Quicksort, indipendentemente dall'implementazione .
Raffaello

1
Qualcuno deve dirlo per completezza, quindi lo farò: Quicksort non è (di solito) stabile. Per questo motivo, potresti non voler usarlo. Inoltre, per questo motivo, l'ordinamento predefinito potrebbe non essere un Quicksort anche quando è quello che desideri.
RalphChapin

1
@Raphael: Spesso ciò che viene chiamato ordinamento rapido è in realtà una variazione come l'ordinamento introduttivo (usato, afaik, nella libreria standard C ++), non un ordinamento rapido puro.
Giorgio,

Risposte:


21

Non sarei d'accordo sul fatto che quicksort sia meglio di altri algoritmi di ordinamento in pratica.

Per la maggior parte degli scopi, Timsort - l'ibrido tra ordinamento di fusione / inserimento che sfrutta il fatto che i dati ordinati spesso iniziano quasi ordinati o in ordine inverso.

Il quicksort più semplice (nessun pivot casuale) tratta questo caso potenzialmente comune come O (N ^ 2) (riducendo a O (N lg N) con pivot casuali), mentre TimSort può gestire questi casi in O (N).

Secondo questi parametri di riferimento in C # confrontando il quicksort integrato con TimSort, Timsort è significativamente più veloce nei casi per lo più ordinati, e leggermente più veloce nel caso dei dati casuali e TimSort migliora se la funzione di confronto è particolarmente lenta. Non ho ripetuto questi parametri di riferimento e non sarei sorpreso se quicksort battesse leggermente TimSort per una combinazione di dati casuali o se c'è qualcosa di strano nell'ordinamento incorporato di C # (basato su quicksort) che lo sta rallentando. Tuttavia, TimSort presenta vantaggi distinti quando i dati possono essere parzialmente ordinati ed è approssimativamente uguale a quicksort in termini di velocità quando i dati non sono parzialmente ordinati.

TimSort ha anche un ulteriore vantaggio di essere un tipo stabile, a differenza di quicksort. L'unico svantaggio di TimSort utilizza la memoria O (N) rispetto a O (lg N) nella consueta (veloce) implementazione.


18

L'ordinamento rapido è considerato più rapido perché il coefficiente è più piccolo di qualsiasi altro algoritmo noto. Non vi è alcun motivo o prova per questo, solo nessun algoritmo con un coefficiente più piccolo è stato trovato. È vero che anche altri algoritmi hanno O ( n log n ), ma nel mondo reale anche il coefficiente è importante.

Si noti che per l'inserimento di piccoli dati l'ordinamento (quello considerato O ( n 2 )) è più veloce a causa della natura delle funzioni matematiche. Ciò dipende dai coefficienti specifici che variano da macchina a macchina. (Alla fine, solo l'assemblaggio è veramente in esecuzione.) Quindi a volte un ibrido di ordinamento rapido e ordinamento per inserzione è il più veloce in pratica, penso.


7
+ Giusto. Gli insegnanti devono essere più consapevoli (e io ero un insegnante) del fatto che i fattori costanti possono variare a seconda degli ordini di grandezza. Quindi l'abilità del tuning delle prestazioni conta davvero, indipendentemente da big-O. Il problema è che continuano a insegnare gprof , solo perché devono superare quel punto elenco nel curriculum, che è un approccio sbagliato di 180 gradi.
Mike Dunlavey,

2
"Non c'è motivo o pro [o] f per quello": certo che c'è. Se scavi abbastanza in profondità, troverai un motivo.
Gilles 'SO- smetti di essere malvagio'

2
@B Seven: per semplificare molto ... per un algoritmo di ordinamento O (n log n), ci sono (n log n) iterazioni del ciclo di ordinamento per ordinare n elementi. Il coefficiente è la durata di ciascun ciclo del loop. Quando n è veramente grande (almeno migliaia), il coefficiente non conta tanto quanto O () anche se il coefficiente è enorme. Ma quando n è piccolo, il coefficiente è importante e può essere la cosa più importante se si ordinano solo 10 articoli.
Matt Gallagher,

4
@MikeDunlavey - un buon esempio è che la costruzione delle piramidi è O (n) mentre l'ordinamento delle tue foto è O (n ln n) ma che è più veloce!
Martin Beckett,

2
Esistono algoritmi O (n log n) garantiti come heapsort e mergesort, quindi in termini asintotici nel caso peggiore Quicksort non è altrettanto veloce quanto il migliore. Ma nelle prestazioni del mondo reale, alcune varianti di quicksort funzionano molto bene. Tuttavia, dire "il coefficiente è più piccolo" è come dire "è più veloce perché è più veloce". Perché i fattori costanti sono così piccoli? Un motivo chiave è perché quicksort è molto buono in termini di località - fa un ottimo uso delle cache. Anche Mergesort ha una buona località, ma è molto difficile da fare sul posto.
Steve314,

16

Quicksort non supera tutti gli altri algoritmi di ordinamento. Ad esempio, l'heap sort dal basso verso l'alto ( Wegener 2002 ) supera i quicksort per quantità ragionevoli di dati ed è anche un algoritmo sul posto. È anche facile da implementare (almeno, non più difficile di alcune varianti ottimizzate di quicksort).

Non è così noto e non lo trovi in ​​molti libri di testo, il che potrebbe spiegare perché non è così popolare come quicksort.


+1: ho eseguito alcuni test e in effetti unire l'ordinamento era decisamente meglio dell'ordinamento rapido per array di grandi dimensioni (> 100000 elementi). L'ordinamento dell'heap era leggermente peggiore di unisci l'ordinamento (ma unire l'ordinamento richiede più memoria). Penso che ciò che le persone chiamano ordinamento rapido è spesso una variazione chiamata ordinamento introduttivo: ordinamento rapido che ricade nell'ordinamento heap quando la profondità di ricorsione supera un certo limite.
Giorgio,

@Giorgio: quicksort può essere modificato in qualche modo per migliorarlo, vedi ad esempio qui: algs4.cs.princeton.edu/23quicksort Hai provato a migliorare?
Doc Brown,

Interessante, puoi dare un riferimento a un libro \ sito per saperne di più? (preferibilmente un libro)
Ramzi Kahil,

@Martin: vuoi dire heapsort Bottom-Up? Bene, ho dato un riferimento sopra. Se vuoi una risorsa gratuita, la Wikipedia in tedesco ha un articolo a riguardo ( de.wikipedia.org/wiki/BottomUp-Heapsort ). Anche se non parli tedesco, immagino che tu possa ancora leggere l'esempio C99.
Doc Brown,

7

Non dovresti concentrarti solo sul caso peggiore e solo sulla complessità temporale. È più nella media che nella peggiore, e riguarda il tempo e lo spazio.

quicksort:

  • ha una complessità temporale media di Θ ( n log n );
  • può essere implementato con una complessità spaziale di Θ (log n );

Tieni anche conto del fatto che la notazione O grande non tiene conto delle costanti, ma in pratica fa differenza se l'algoritmo è alcune volte più veloce. Θ ( n log n ) significa che l'algoritmo viene eseguito in K  n  log ( n ), dove K è costante. Quicksort è l'algoritmo di ordinamento comparativo con il K più basso .


1
@Gilles: ha K basso, perché è un algoritmo semplice.
Vartec,

5
WTF? Questo non ha alcun senso. La semplicità di un algoritmo non ha alcuna relazione con la sua velocità di marcia. L'ordinamento della selezione è più semplice di quicksort, il che non lo rende più veloce.
Gilles 'SO- smetti di essere malvagio'

1
@Gilles: l'ordinamento per selezione è O (n ^ 2) in ogni caso (peggiore, medio e migliore). Quindi non importa quanto sia semplice. Quicksort è O (n log n) per il caso medio, e tra tutti gli algoritmi con O (n log n) avg è il più semplice.
Vartec,

1
@Gilles: a parità di altre condizioni, la semplicità aiuta le prestazioni. Supponiamo che tu stia confrontando due algoritmi che ciascuno prende (K n log n) iterazioni dei rispettivi loop interni: l'algoritmo che deve fare meno cose per loop ha un vantaggio in termini di prestazioni.
comingstorm

1
@comingstorm: Frase come quella della tua affermazione è una tautologia, ma non si riferisce alla "semplicità". Esistono, ad esempio, varianti più complicate di Quicksort (distinzioni tra maiuscole e minuscole!) Che si traducono in una minore autonomia (sia in teoria che in pratica).
Raphael,

5

Quicksort è spesso una buona scelta in quanto è ragionevolmente veloce e ragionevolmente veloce e facile da implementare.

Se sei seriamente intenzionato a ordinare grandi quantità di dati molto rapidamente, probabilmente stai meglio con qualche variazione su MergeSort. Questo può essere fatto per sfruttare l'archiviazione esterna, può utilizzare più thread o persino processi ma non sono banali da codificare.


1

Le prestazioni effettive degli algoritmi dipendono dalla piattaforma, nonché dal linguaggio, dal compilatore, dall'attenzione del programmatore ai dettagli di implementazione, dallo sforzo di ottimizzazione specifico, eccetera. Pertanto, il "vantaggio di fattore costante" di quicksort non è molto ben definito: è un giudizio soggettivo basato sugli strumenti attualmente disponibili e una stima approssimativa di "sforzo di implementazione equivalente" da parte di chiunque effettivamente esegua lo studio comparativo delle prestazioni. .

Detto questo, credo che quicksort funzioni bene (per input randomizzato) perché è semplice e perché la sua struttura ricorsiva è relativamente favorevole alla cache. D'altra parte, poiché il suo caso peggiore è facile da innescare, qualsiasi uso pratico di un quicksort dovrà essere più complesso di quanto indicherebbe la sua descrizione da manuale: quindi, versioni modificate come introsort.

Nel tempo, quando la piattaforma dominante cambia, diversi algoritmi possono guadagnare o perdere il loro vantaggio relativo (mal definito). La saggezza convenzionale sulle prestazioni relative potrebbe essere in ritardo rispetto a questo spostamento, quindi se non si è davvero sicuri dell'algoritmo più adatto alla propria applicazione, è necessario implementare entrambi e testarli.


Immagino che la "costante più piccola" a cui gli altri la collegano sia quella dell'analisi formale, cioè sul numero di confronti o scambi. Questo è molto ben definito ma non è chiaro come questo si traduca in runtime. Un collega attualmente fa alcune ricerche su questo, in realtà.
Raffaello

La mia impressione era che si trattava di prestazioni generalizzate, ma non avrei fatto affidamento su nessuno dei due. Hai ragione, però: se il tuo confronto è particolarmente costoso, puoi cercare il numero di confronti previsti ...
prossima tempesta

1
Per il motivo che affermi, parlare delle prestazioni complessive (in termini di tempo) non è significativo nel caso generale, in quanto troppi dettagli tengono conto. Il motivo per contare solo le operazioni selezionate non è che sono costose, ma che si verificano "il più delle volte "nel senso della notazione Landau (Big-Oh), quindi contare quelli ti dà i tuoi asintotici grezzi. Non appena si considerano le costanti e / o il runtime, questa strategia è molto meno interessante.
Raffaello

Una buona implementazione di QuickSort verrà compilata in modo tale che i valori pivot rimangano in un registro CPU per tutto il tempo necessario. Questo è spesso sufficiente per superare un ordinamento teoricamente più veloce con tempi Big-O comparabili.
Dan Lyons,

Diversi algoritmi di ordinamento hanno caratteristiche diverse rispetto al numero di confronti e al numero di scambi che fanno. E @DanLyons nota che un ordinamento tipico in una libreria esegue i suoi confronti tramite funzioni fornite dall'utente, e mantenere i valori nei registri attraverso molte chiamate di funzioni è piuttosto complicato.
Punta a punta
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.