Risposte:
Questo documento contiene alcune analisi.
Inoltre, da Wikipedia:
Il concorrente più diretto di quicksort è heapsort. Heapsort è tipicamente un po 'più lento di quicksort, ma il tempo di esecuzione nel caso peggiore è sempre Θ (nlogn). Quicksort è solitamente più veloce, sebbene permanga la possibilità di prestazioni nel caso peggiore tranne nella variante introsort, che passa a heapsort quando viene rilevato un caso negativo. Se è noto in anticipo che heapsort sarà necessario, utilizzarlo direttamente sarà più veloce che aspettare che introsort passi ad esso.
Heapsort è O (N log N) garantito, ciò che è molto meglio del caso peggiore in Quicksort. Heapsort non ha bisogno di più memoria per un altro array per inserire i dati ordinati come richiesto da Mergesort. Allora perché le applicazioni commerciali si attaccano a Quicksort? Cosa ha Quicksort di così speciale rispetto ad altre implementazioni?
Ho testato io stesso gli algoritmi e ho visto che Quicksort ha davvero qualcosa di speciale. Funziona velocemente, molto più velocemente degli algoritmi Heap e Merge.
Il segreto di Quicksort è: quasi non esegue scambi di elementi non necessari. Lo scambio richiede tempo.
Con Heapsort, anche se tutti i tuoi dati sono già ordinati, cambierai il 100% degli elementi per ordinare l'array.
Con Mergesort è anche peggio. Scriverai il 100% degli elementi in un altro array e lo riscriverai in quello originale, anche se i dati sono già ordinati.
Con Quicksort non scambi ciò che è già ordinato. Se i tuoi dati sono completamente ordinati, non scambi quasi nulla! Sebbene ci siano molte preoccupazioni sul caso peggiore, un piccolo miglioramento nella scelta del pivot, oltre a ottenere il primo o l'ultimo elemento dell'array, può evitarlo. Se ottieni un perno dall'elemento intermedio tra il primo, l'ultimo e il mezzo, è sufficiente evitare il caso peggiore.
Ciò che è superiore in Quicksort non è il caso peggiore, ma il caso migliore! Nel migliore dei casi fai lo stesso numero di confronti, ok, ma non cambi quasi nulla. In media, si scambiano parte degli elementi, ma non tutti, come in Heapsort e Mergesort. Questo è ciò che dà a Quicksort il miglior tempo. Meno scambi, più velocità.
L'implementazione di seguito in C # sul mio computer, in esecuzione in modalità di rilascio, batte Array.Sort di 3 secondi con il pivot centrale e di 2 secondi con il pivot migliorato (sì, c'è un sovraccarico per ottenere un buon pivot).
static void Main(string[] args)
{
int[] arrToSort = new int[100000000];
var r = new Random();
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
Console.WriteLine("Press q to quick sort, s to Array.Sort");
while (true)
{
var k = Console.ReadKey(true);
if (k.KeyChar == 'q')
{
// quick sort
Console.WriteLine("Beg quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
QuickSort(arrToSort, 0, arrToSort.Length - 1);
Console.WriteLine("End quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
}
else if (k.KeyChar == 's')
{
Console.WriteLine("Beg Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
Array.Sort(arrToSort);
Console.WriteLine("End Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
}
}
}
static public void QuickSort(int[] arr, int left, int right)
{
int begin = left
, end = right
, pivot
// get middle element pivot
//= arr[(left + right) / 2]
;
//improved pivot
int middle = (left + right) / 2;
int
LM = arr[left].CompareTo(arr[middle])
, MR = arr[middle].CompareTo(arr[right])
, LR = arr[left].CompareTo(arr[right])
;
if (-1 * LM == LR)
pivot = arr[left];
else
if (MR == -1 * LR)
pivot = arr[right];
else
pivot = arr[middle];
do
{
while (arr[left] < pivot) left++;
while (arr[right] > pivot) right--;
if(left <= right)
{
int temp = arr[right];
arr[right] = arr[left];
arr[left] = temp;
left++;
right--;
}
} while (left <= right);
if (left < end) QuickSort(arr, left, end);
if (begin < right) QuickSort(arr, begin, right);
}
Per la maggior parte delle situazioni, avere veloce o un po 'più veloce è irrilevante ... semplicemente non vuoi mai che occasionalmente diventi mooolto lento. Sebbene tu possa modificare QuickSort per evitare situazioni lente, perdi l'eleganza del QuickSort di base. Quindi, per la maggior parte delle cose, in realtà preferisco HeapSort ... puoi implementarlo nella sua piena semplice eleganza e non ottenere mai un ordinamento lento.
Per le situazioni in cui nella maggior parte dei casi si desidera la massima velocità, QuickSort potrebbe essere preferito a HeapSort, ma nessuno dei due potrebbe essere la risposta giusta. Per le situazioni critiche per la velocità, vale la pena esaminare da vicino i dettagli della situazione. Ad esempio, in alcuni dei miei codici critici per la velocità, è molto comune che i dati siano già ordinati o quasi ordinati (si tratta di indicizzare più campi correlati che spesso si muovono su e giù insieme O si muovono su e giù l'uno di fronte all'altro, quindi una volta ordinato per uno, gli altri vengono ordinati o invertiti o chiusi ... entrambi possono uccidere QuickSort). In quel caso, non ho implementato né ... invece, ho implementato SmoothSort di Dijkstra ... una variante HeapSort che è O (N) quando è già ordinata o quasi ordinata ... non è così elegante, non troppo facile da capire, ma veloce ... leggihttp://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF se vuoi qualcosa di un po 'più impegnativo da codificare.
Anche gli ibridi sul posto Quicksort-Heapsort sono davvero interessanti, poiché la maggior parte di essi necessita solo di n * log n confronti nel caso peggiore (sono ottimali rispetto al primo termine degli asintotici, quindi evitano gli scenari peggiori di Quicksort), O (log n) extra-spazio e conservano almeno "la metà" del buon comportamento di Quicksort rispetto al set di dati già ordinato. Un algoritmo estremamente interessante è presentato da Dikert e Weiss in http://arxiv.org/pdf/1209.4214v1.pdf :
Comp. tra quick sort
e merge sort
poiché entrambi sono tipi di ordinamento sul posto, c'è una differenza tra il tempo di esecuzione del caso di wrost del tempo di esecuzione del caso di wrost per l'ordinamento rapido è O(n^2)
e per l'ordinamento di heap è ancora O(n*log(n))
e per una quantità media di dati l'ordinamento rapido sarà più utile. Poiché è un algoritmo randomizzato, la probabilità di ottenere risposte corrette. in meno tempo dipenderà dalla posizione dell'elemento pivot che scegli.
Quindi a
Buona scelta: le taglie di L e G sono ciascuna inferiore a 3s / 4
Cattiva chiamata: uno tra L e G ha una dimensione maggiore di 3s / 4
per piccole quantità possiamo usare l'ordinamento per inserzione e per grandi quantità di dati andare per l'ordinamento dell'heap.
Heapsort ha il vantaggio di avere un caso di esecuzione peggiore di O (n * log (n)), quindi nei casi in cui è probabile che quicksort abbia prestazioni scadenti (per lo più set di dati generalmente ordinati) heapsort è di gran lunga preferito.
Bene, se vai a livello di architettura ... usiamo la struttura dei dati della coda nella memoria cache, quindi ciò che è disponibile in coda verrà ordinato Come nell'ordinamento rapido non abbiamo problemi a dividere l'array in qualsiasi lunghezza ... ma in heap ordina (usando array) può succedere che il genitore non sia presente nel sotto-array disponibile nella cache e quindi deve portarlo nella memoria cache ... il che richiede tempo. Questo è il quicksort è il migliore !! 😀
Heapsort crea un heap e quindi estrae ripetutamente l'elemento massimo. Il suo caso peggiore è O (n log n).
Ma se vedessi il caso peggiore di ordinamento rapido , che è O (n2), ti renderesti conto che l'ordinamento rapido sarebbe una scelta non così buona per dati di grandi dimensioni.
Quindi questo rende l'ordinamento una cosa interessante; Credo che il motivo per cui così tanti algoritmi di ordinamento vivono oggi sia perché tutti sono "migliori" nei loro posti migliori. Ad esempio, l'ordinamento a bolle può eseguire l'ordinamento rapido se i dati vengono ordinati. Oppure, se sappiamo qualcosa sugli articoli da smistare, probabilmente possiamo fare di meglio.
Questo potrebbe non rispondere direttamente alla tua domanda, ho pensato di aggiungere i miei due centesimi.
Heap Sort è una scommessa sicura quando si tratta di input molto grandi. L'analisi asintotica rivela l'ordine di crescita di Heapsort nel peggiore dei casi Big-O(n logn)
, che è migliore di quello di Quicksort Big-O(n^2)
nel peggiore dei casi. Tuttavia, Heapsort è un po 'più lento nella pratica sulla maggior parte delle macchine rispetto a un ordinamento rapido ben implementato. Anche Heapsort non è un algoritmo di ordinamento stabile.
Il motivo per cui heapsort è più lento in pratica di quicksort è dovuto alla migliore località di riferimento (" https://en.wikipedia.org/wiki/Locality_of_reference ") in quicksort, dove gli elementi di dati si trovano all'interno di posizioni di archiviazione relativamente vicine. I sistemi che presentano una forte località di riferimento sono ottimi candidati per l'ottimizzazione delle prestazioni. L'ordinamento degli heap, tuttavia, si occupa di salti più grandi. Ciò rende Quicksort più favorevole per input più piccoli.
Per me c'è una differenza fondamentale tra heapsort e quicksort: quest'ultimo utilizza una ricorsione. Negli algoritmi ricorsivi l'heap cresce con il numero di ricorsioni. Non importa se n è piccolo, ma in questo momento sto ordinando due matrici con n = 10 ^ 9 !!. Il programma richiede quasi 10 GB di RAM e l'eventuale memoria aggiuntiva farà sì che il mio computer inizi a passare alla memoria del disco virtuale. Il mio disco è un disco RAM, ma continuare a cambiarlo fa un'enorme differenza di velocità . Quindi in uno statpack codificato in C ++ che include matrici di dimensioni regolabili, con dimensioni sconosciute in anticipo al programmatore, e tipo di ordinamento statistico non parametrico, preferisco l'heapsort per evitare ritardi nell'utilizzo con matrici di dati molto grandi.
Per rispondere alla domanda originale e indirizzare alcuni degli altri commenti qui:
Ho appena confrontato le implementazioni di selezione, veloce, unione e ordinamento heap per vedere come si sovrappongono l'una contro l'altra. La risposta è che hanno tutti i loro lati negativi.
TL; DR: Quick è il miglior ordinamento generico (ragionevolmente veloce, stabile e per lo più sul posto) Personalmente preferisco l'ordinamento heap, a meno che non mi serva un ordinamento stabile.
Selezione - N ^ 2 - È davvero buono solo per meno di 20 elementi o giù di lì, quindi ha prestazioni migliori. A meno che i tuoi dati non siano già ordinati, o molto, molto quasi. N ^ 2 diventa molto lento molto velocemente.
Veloce, nella mia esperienza, non è in realtà così veloce tutto il tempo. I bonus per l'utilizzo dell'ordinamento rapido come ordinamento generale sono però che è ragionevolmente veloce ed è stabile. È anche un algoritmo sul posto, ma poiché è generalmente implementato in modo ricorsivo, occuperà spazio aggiuntivo nello stack. Inoltre cade da qualche parte tra O (n log n) e O (n ^ 2). Il tempismo su alcuni tipi sembra confermare questo, soprattutto quando i valori rientrano in un intervallo ristretto. È molto più veloce dell'ordinamento della selezione su 10.000.000 di elementi, ma più lento della fusione o dell'heap.
L'ordinamento di unione è garantito O (n log n) poiché il suo ordinamento non dipende dai dati. Fa semplicemente quello che fa, indipendentemente dai valori che gli hai dato. È anche stabile, ma i tipi molto grandi possono far saltare il tuo stack se non stai attento all'implementazione. Esistono alcune complesse implementazioni di merge sort sul posto, ma in genere è necessario un altro array in ogni livello per unire i valori. Se questi array risiedono nello stack, puoi incorrere in problemi.
L'ordinamento dell'heap è max O (n log n), ma in molti casi è più veloce, a seconda di quanto lontano devi spostare i tuoi valori nel log n heap profondo. L'heap può essere facilmente implementato sul posto nell'array originale, quindi non necessita di memoria aggiuntiva ed è iterativo, quindi nessuna preoccupazione per l'overflow dello stack durante la ricorrenza. L' enorme svantaggio dell'ordinamento heap è che non è un ordinamento stabile, il che significa che è giusto se ne hai bisogno.