Perché quicksort è meglio di mergesort?


354

Mi è stata posta questa domanda durante un'intervista. Sono entrambi O (nlogn) e tuttavia la maggior parte delle persone usa Quicksort invece di Mergesort. Perché?


91
Questa non è un'ottima domanda per l'intervista. I dati del mondo reale non vengono mischiati: spesso contengono molto ordine di cui può fare uso un ordinamento intelligente, e sebbene nessuno degli algoritmi lo faccia automaticamente, è più facile hackerare un ordinamento di fusione per farlo rispetto a un quicksort. Le librerie GNU libc qsort, Python list.sorte Array.prototype.sortJavaScript di Firefox sono tutte unite in modo confuso. (GNU STL sortusa invece Introsort, ma ciò potrebbe essere dovuto al fatto che in C ++ lo scambio potenzialmente vince molto rispetto alla copia.)
Jason Orendorff il

3
@Jason Orendorff: Perché è "easier to hack a mergesort to do it than a quicksort"? Qualche esempio specifico che puoi citare?
Lazer,

16
@eSKay Un ordinamento di unione inizia raggruppando i dati iniziali in sottoarray ordinati. Se l'array contiene inizialmente alcune aree già ordinate, è possibile risparmiare molto tempo rilevando che sono lì prima di iniziare. E puoi farlo in O (n) tempo. Per esempi specifici, vedi il codice sorgente dei tre progetti che ho citato! Il miglior esempio potrebbe essere Timsort di Python, descritto in dettaglio qui: svn.python.org/view/python/trunk/Objects/… e implementato in svn.python.org/view/python/trunk/Objects/… .
Jason Orendorff,

4
@JasonOrendorff: Non sono sicuro di acquistare la tua argomentazione secondo cui la fusione può essere modificata più facilmente per sfruttare le sezioni già ordinate. La fase di partizionamento di quicksort può essere banalmente modificata per verificare in seguito se entrambe le partizioni risultanti sono ordinate e, se lo sono, arrestare la ricorsione. Questo potenzialmente raddoppia il numero di confronti, ma non altera la complessità temporale O (n) di quel passaggio.
j_random_hacker il

3
@j_random_hacker: giusto, questo è quello che stavo insinuando. Ma considera: {10, 2, 3, 4, 5, 6, 7, 8, 1, 9} Nonostante sia già quasi completamente ordinato, il controllo prima della partizione non lo troverà, né dopo. E la partizione lo rovinerà prima che le chiamate successive lo controllino. Nel frattempo, unisci gli ordinamenti per controllare le sequenze ordinate nei passaggi di divisione prima che vengano spostati, e quelli intelligenti cercheranno corse come questa in particolare durante il passaggio di divisione (vedi: Tim Sort)
Mooing Duck,

Risposte:


275

Quicksort ha O ( n 2 ) runtime nel caso peggiore e O ( n log n ) runtime nel caso medio. Tuttavia, è superiore unire l'ordinamento in molti scenari perché molti fattori influenzano il tempo di esecuzione di un algoritmo e, nel loro insieme, vincono quicksort.

In particolare, il runtime spesso citato degli algoritmi di ordinamento si riferisce al numero di confronti o al numero di swap necessari per eseguire l'ordinamento dei dati. Questa è davvero una buona misura delle prestazioni, soprattutto perché è indipendente dal design hardware sottostante. Tuttavia, altre cose - come la località di riferimento (ovvero leggiamo molti elementi che sono probabilmente nella cache?) - svolgono anche un ruolo importante sull'hardware corrente. Quicksort in particolare richiede poco spazio aggiuntivo ed esibisce una buona localizzazione della cache, e questo lo rende più veloce di unire l'ordinamento in molti casi.

Inoltre, è molto facile evitare il tempo di esecuzione nel caso peggiore di quicksort di O ( n 2 ) quasi interamente usando una scelta appropriata del perno - come sceglierlo a caso (questa è una strategia eccellente).

In pratica, molte implementazioni moderne di quicksort (in particolare libstdc ++ std::sort) sono in realtà un introsort , il cui caso peggiore teorico è O ( n log n ), uguale a merge sort. Ciò si ottiene limitando la profondità di ricorsione e passando a un algoritmo diverso ( heapsort ) una volta superato il log n .


4
L'articolo di Wikipedia afferma che passa a heapsort, non a mergesort ... solo FYI.
Sev

3
@Sev: ... come fa il documento originale. Grazie per aver segnalato l'errore. - Non è importante, dato che il loro tempo di esecuzione asintotico è lo stesso.
Konrad Rudolph,

110
perché questa è selezionata come risposta corretta? Tutto ciò che spiega è come correggere rapidamente i problemi di sorta. Non dice ancora perché l'ordinamento rapido viene utilizzato più di altri? La risposta "L'ordinamento rapido viene utilizzato più di altri perché dopo una profondità è possibile passare a heapsort"? .. perché non usare heapsort in primo luogo allora? ..
sto

16
@ p1 Buona domanda. La vera risposta è che, in media, per i dati medi, quicksort è più veloce di unire l'ordinamento (e l'heap sort, per quella materia), e anche se il caso peggiore di quicksort è più lento di unire l'ordinamento, questo caso peggiore può essere mitigato molto facilmente (da qui la mia risposta).
Konrad Rudolph,

4
Quicksort è migliore anche in termini di memoria.
Shashwat,

287

Come molte persone hanno notato, le prestazioni medie del case per quicksort sono più veloci di mergesort. Ma questo è vero solo se si presuppone un tempo costante per accedere a qualsiasi pezzo di memoria su richiesta.

Nella RAM questo presupposto non è generalmente troppo negativo (non è sempre vero a causa delle cache, ma non è poi così male). Tuttavia, se la tua struttura di dati è abbastanza grande per vivere su disco, allora quicksort viene ucciso dal fatto che il tuo disco medio fa qualcosa come 200 ricerche casuali al secondo. Ma quello stesso disco non ha problemi a leggere o scrivere megabyte al secondo di dati in sequenza. Questo è esattamente ciò che fa Mergesort.

Pertanto, se i dati devono essere ordinati su disco, si desidera davvero utilizzare alcune varianti su mergesort. (In genere si effettuano elenchi rapidi, quindi si inizia a fonderli insieme al di sopra di una soglia di dimensioni.)

Inoltre, se devi fare qualcosa con set di dati di quelle dimensioni, rifletti attentamente su come evitare le ricerche su disco. Ad esempio, questo è il motivo per cui è consigliabile eliminare gli indici prima di eseguire grandi carichi di dati nei database e quindi ricostruire l'indice in un secondo momento. Mantenere l'indice durante il caricamento significa cercare costantemente su disco. Al contrario, se si rilasciano gli indici, il database può ricostruire l'indice ordinando prima le informazioni da trattare (usando un mergesort ovviamente!) E quindi caricandole in una struttura dati BTREE per l'indice. (I BTREE sono naturalmente tenuti in ordine, quindi puoi caricarne uno da un set di dati ordinato con poche ricerche su disco.)

Ci sono state diverse occasioni in cui la comprensione di come evitare le ricerche su disco mi ha permesso di fare in modo che i processi di elaborazione dei dati richiedessero ore anziché giorni o settimane.


1
Molto bello, non ho pensato alle ipotesi fatte per accedere alla struttura dei dati. Buona visione :)
Chutsu,

2
Puoi spiegare cosa intendi con "cerca su disco" significa cercare un singolo valore quando i dati sono memorizzati su disco?
James Wierzba,

8
@JamesWierzba Prendo dal contesto che intende "cercare in una posizione sul disco". "Cercare" su un dispositivo a disco rotante significa prendere la testina di lettura e spostarla su un nuovo indirizzo assoluto, operazione notoriamente lenta. Quando si accede ai dati nell'ordine in cui sono stati archiviati, l'hardware del disco non deve cercare, si sposta semplicemente ad alta velocità, leggendo gli elementi in sequenza.
nclark,

1
Qualcuno può spiegarlo un po 'di più? Ecco come lo vedo: Quicksort: se stiamo andando con pivot casuale, lo stack di chiamate ha frammenti dell'array partizionati in modo casuale. Ciò richiede un accesso casuale. Tuttavia, per ogni chiamata nello stack, i puntatori sinistro e destro si spostano in sequenza. Suppongo che questi sarebbero mantenuti nella cache. Gli swap sono di nuovo operazioni su informazioni contenute nella cache (e infine scritte su disco). (continua nel mio prossimo commento)
sam

1
Solo un contributo che evita il dispendioso sovraccarico di lettura / scrittura del disco : quando si ordinano dati molto grandi che richiedono l'accesso al disco, è vantaggioso cambiare la direzione dell'ordinamento per ogni passaggio. Cioè, al livello più alto del ciclo, una volta che vai da 0verso ne la prossima volta che vai da nverso 0. Questo porta il vantaggio di ritirare (ordinare) i blocchi di dati che sono già disponibili nella memoria (cache) e attaccare due volte per un solo accesso al disco. Penso che la maggior parte dei DBMS usi questa tecnica di ottimizzazione.
SSD

89

In realtà, QuickSort è O (n 2 ). Il suo tempo medio di esecuzione del caso è O (nlog (n)), ma il caso peggiore è O (n 2 ), che si verifica quando lo esegui in un elenco che contiene pochi elementi univoci. La randomizzazione richiede O (n). Ovviamente, questo non cambia il caso peggiore, ma impedisce a un utente malintenzionato di fare in modo che il tuo ordinamento richieda molto tempo.

QuickSort è più popolare perché:

  1. È sul posto (MergeSort richiede una memoria aggiuntiva lineare al numero di elementi da ordinare).
  2. Ha una piccola costante nascosta.

4
In realtà, ci sono implementazioni di QuickSort che sono O (n * log (n)), non O (n ^ 2) nel peggiore dei casi.
jfs,

12
Dipende anche dall'architettura del computer. Quicksort trae vantaggio dalla cache, mentre MergeSort no.
Cristian Ciupitu,

4
@JF Sebastian: Queste sono probabilmente implementazioni di introsort, non quicksort (introsort inizia come quicksort e passa a heapsort se sta per smettere di essere n * log (n)).
CesarB,

44
È possibile implementare un mergesort in atto.
Marcin,

6
Unire l'ordinamento può essere implementato in un modo che richiede solo O (1) spazio di archiviazione aggiuntivo, ma la maggior parte di tali implementazioni risentono notevolmente in termini di prestazioni.
Più chiaro il

29

"eppure la maggior parte delle persone usa Quicksort invece di Mergesort. Perché?"

Una ragione psicologica che non è stata data è semplicemente che Quicksort è più intelligente. vale a dire un buon marketing.

Sì, Quicksort con triplo partizionamento è probabilmente uno dei migliori algoritmi di ordinamento per scopi generici, ma non si può superare il fatto che l'ordinamento "Rapido" suona molto più potente dell'ordinamento "Unisci".


3
Non risponde alla domanda su quale sia meglio. Il nome dell'algoritmo è irrilevante nel determinare quale è meglio.
Nick Gallimore,

18

Come altri hanno notato, il caso peggiore di Quicksort è O (n ^ 2), mentre mergesort e heapsort rimangono a O (nlogn). Nel caso medio, tuttavia, tutti e tre sono O (nlogn); quindi sono per la stragrande maggioranza dei casi comparabili.

Ciò che rende Quicksort in media migliore è che il ciclo interno implica il confronto di più valori con uno singolo, mentre negli altri due entrambi i termini sono diversi per ciascun confronto. In altre parole, Quicksort esegue la metà delle letture degli altri due algoritmi. Nelle moderne CPU le prestazioni sono fortemente dominate dai tempi di accesso, quindi alla fine Quicksort diventa un'ottima scelta.


9

Vorrei aggiungere quello dei tre algoritmi menzionati finora (mergesort, quicksort e heap sort) che solo mergesort è stabile. Cioè, l'ordine non cambia per quei valori che hanno la stessa chiave. In alcuni casi questo è desiderabile.

A dire il vero, in situazioni pratiche la maggior parte delle persone ha bisogno solo di buone prestazioni medie e quicksort è ... quick =)

Tutti gli algoritmi di ordinamento hanno i loro alti e bassi. Vedi l' articolo di Wikipedia per gli algoritmi di ordinamento per una buona panoramica.


7

Dalla voce di Wikipedia su Quicksort :

Quicksort compete anche con mergesort, un altro algoritmo di ordinamento ricorsivo ma con il vantaggio del tempo di esecuzione worst (nlogn) nel caso peggiore. Mergesort è un tipo stabile, a differenza di quicksort e heapsort, e può essere facilmente adattato per operare su elenchi collegati e elenchi molto grandi memorizzati su supporti ad accesso lento come archiviazione su disco o archiviazione collegata in rete. Sebbene Quicksort possa essere scritto per funzionare su elenchi collegati, spesso soffrirà di scarse scelte pivot senza accesso casuale. Il principale svantaggio di mergesort è che, quando si opera su array, richiede Θ (n) spazio ausiliario nel migliore dei casi, mentre la variante di quicksort con partizionamento sul posto e ricorsione della coda utilizza solo space (logn) spazio. (Si noti che quando si opera su elenchi collegati, mergesort richiede solo una piccola quantità costante di memoria ausiliaria.)


7

Mu! Quicksort non è migliore, è adatto per un diverso tipo di applicazione, rispetto a mergesort.

Vale la pena considerare Mergesort se la velocità è essenziale, non è possibile tollerare cattive prestazioni nel caso peggiore ed è disponibile spazio aggiuntivo. 1

Hai affermato che «Sono entrambi O (nlogn) [...]». Questo è sbagliato. «Quicksort utilizza circa n ^ 2/2 confronti nel peggiore dei casi.» 1 .

Tuttavia, la proprietà più importante secondo la mia esperienza è la facile implementazione dell'accesso sequenziale che è possibile utilizzare durante l'ordinamento quando si utilizzano linguaggi di programmazione con il paradigma imperativo.

1 Sedgewick, algoritmi


Mergesort può essere implementato sul posto, in modo tale da non richiedere spazio aggiuntivo. Per esempio con una lista collegata doppia: stackoverflow.com/questions/2938495/...
lanoxx

6

Quicksort è l'algoritmo di ordinamento più veloce in pratica, ma ha una serie di casi patologici che possono farlo funzionare male come O (n2).

Heapsort è garantito per funzionare in O (n * ln (n)) e richiede solo memoria aggiuntiva finita. Ma ci sono molte citazioni di test del mondo reale che dimostrano che heapsort è significativamente più lento di quicksort in media.


5

La spiegazione di Wikipedia è:

In genere, quicksort è significativamente più veloce nella pratica rispetto ad altri algoritmi Θ (nlogn), poiché il suo loop interno può essere implementato in modo efficiente sulla maggior parte delle architetture e nella maggior parte dei dati del mondo reale è possibile effettuare scelte progettuali che minimizzano la probabilità di richiedere tempo quadratico .

quicksort

mergesort

Penso che ci siano anche problemi con la quantità di memoria necessaria per Mergesort (che è Ω (n)) che le implementazioni di quicksort non hanno. Nel peggiore dei casi, sono la stessa quantità di tempo algoritmico, ma mergesort richiede più spazio di archiviazione.


Il caso peggiore di quicksort è O (n), mergesort O (n log n) - quindi c'è una grande differenza lì.
paul23,

1
nel peggiore dei casi quicksort è O (n ^ 2) - non posso modificare il mio commento precedente e ho fatto un refuso
paul23

I commenti di @ paul23 possono essere eliminati. Inoltre, la risposta ha già affrontato il tuo punto: "nella maggior parte dei dati del mondo reale è possibile effettuare scelte progettuali che minimizzano la probabilità di richiedere un tempo quadratico"
Jim Balter,

5

Vorrei aggiungere alle grandi risposte esistenti alcune considerazioni matematiche su come QuickSort si comporta quando si discosta dal caso migliore e quanto è probabile, che spero aiuterà le persone a capire un po 'meglio perché il caso O (n ^ 2) non è reale preoccupazione per le implementazioni più sofisticate di QuickSort.

Al di fuori dei problemi di accesso casuale, esistono due fattori principali che possono influire sulle prestazioni di QuickSort e sono entrambi correlati al modo in cui il pivot confronta con i dati ordinati.

1) Un piccolo numero di chiavi nei dati. Un set di dati dello stesso valore verrà ordinato in n ^ 2 volte su un QuickSort a 2 partizioni vaniglia perché tutti i valori tranne la posizione pivot vengono posizionati su un lato ogni volta. Le moderne implementazioni affrontano questo con metodi come l'uso di un ordinamento a 3 partizioni. Questi metodi vengono eseguiti su un set di dati dello stesso valore in O (n) time. Pertanto, l'utilizzo di un'implementazione del genere significa che un input con un numero limitato di chiavi migliora effettivamente i tempi delle prestazioni e non è più un problema.

2) La selezione estremamente pivot può causare prestazioni nel caso peggiore. In un caso ideale, il perno sarà sempre tale che il 50% dei dati sia più piccolo e il 50% che i dati siano più grandi, in modo che l'input venga interrotto a metà durante ogni iterazione. Questo ci dà n confronti e tempi di scambio log-2 (n) ricorsioni per il tempo O (n * logn).

Quanto influisce la selezione del pivot non ideale sui tempi di esecuzione?

Consideriamo un caso in cui il pivot viene scelto coerentemente in modo tale che il 75% dei dati si trovi su un lato del pivot. È ancora O (n * logn) ma ora la base del registro è cambiata in 1 / 0,75 o 1,33. La relazione nelle prestazioni quando si cambia base è sempre una costante rappresentata da log (2) / log (newBase). In questo caso, quella costante è 2.4. Quindi questa qualità della scelta del perno richiede 2,4 volte più a lungo dell'ideale.

Quanto velocemente peggiora?

Non molto velocemente fino a quando la scelta del perno diventa (costantemente) molto cattiva:

  • 50% su un lato: (custodia ideale)
  • 75% su un lato: 2,4 volte più a lungo
  • 90% su un lato: 6,6 volte più lungo
  • 95% su un lato: 13,5 volte di più
  • 99% su un lato: 69 volte più a lungo

Mentre ci avviciniamo al 100% da un lato, la parte log dell'esecuzione si avvicina a n e l'intera esecuzione si avvicina asintoticamente a O (n ^ 2).

In un'implementazione ingenua di QuickSort, casi come un array ordinato (per il pivot del primo elemento) o un array in ordine inverso (per il pivot dell'ultimo elemento) produrranno in modo affidabile un tempo di esecuzione O (n ^ 2) nel caso peggiore. Inoltre, le implementazioni con una prevedibile selezione pivot possono essere soggette all'attacco DoS da dati progettati per produrre l'esecuzione nel caso peggiore. Le implementazioni moderne lo evitano con una varietà di metodi, come randomizzare i dati prima dell'ordinamento, scegliere la mediana di 3 indici scelti casualmente, ecc. Con questa randomizzazione nel mix, abbiamo 2 casi:

  • Piccolo set di dati. Il caso peggiore è ragionevolmente possibile ma O (n ^ 2) non è catastrofico perché n è abbastanza piccolo che n ^ 2 è anche piccolo.
  • Set di dati di grandi dimensioni. Il caso peggiore è possibile in teoria ma non in pratica.

Quanto è probabile che assistiamo a prestazioni terribili?

Le possibilità sono minuscole . Consideriamo una sorta di 5.000 valori:

La nostra ipotetica implementazione sceglierà un perno usando una mediana di 3 indici scelti casualmente. Considereremo "buoni" i pivot compresi tra il 25% e il 75% e i pivot compresi tra 0% -25% o tra il 75% e il 100%. Se osservi la distribuzione di probabilità usando la mediana di 3 indici casuali, ogni ricorsione ha una probabilità 11/16 di finire con un buon perno. Facciamo 2 ipotesi conservative (e false) per semplificare la matematica:

  1. I buoni pivot sono sempre esattamente con una divisione del 25% / 75% e funzionano nel caso ideale 2.4 *. Non otteniamo mai una divisione ideale o una divisione migliore di 25/75.

  2. I perni difettosi sono sempre i casi peggiori e essenzialmente non contribuiscono alla soluzione.

La nostra implementazione di QuickSort si fermerà a n = 10 e passerà a un ordinamento di inserzione, quindi abbiamo bisogno di 22 partizioni pivot del 25% / 75% per abbattere il valore di 5.000 immessi fino a quel punto. (10 * 1.333333 ^ 22> 5000) Oppure abbiamo bisogno di 4990 perni nel caso peggiore. Tieni presente che se accumuliamo 22 buoni pivot in qualsiasi momento, l'ordinamento verrà completato, quindi il caso peggiore o qualcosa di simile richiede estremamente sfortuna. Se ci volessero 88 ricorsioni per raggiungere effettivamente i 22 buoni pivot richiesti per ordinare fino a n = 10, sarebbe il caso ideale 4 * 2,4 * o circa 10 volte il tempo di esecuzione del caso ideale. Quante probabilità ci sono che avremmo non ottenere i richiesti 22 buoni perni dopo 88 ricorsioni?

Le distribuzioni di probabilità binomiale possono rispondere a questa domanda e la risposta è circa 10 ^ -18. (n è 88, k è 21, p è 0,6875) Il tuo utente ha circa mille volte più probabilità di essere colpito da un fulmine nel giro di 1 secondo che serve per fare clic su [ORDINE] di quanto non lo siano per vedere che l'ordinamento di 5.000 articoli peggiora di 10 * custodia ideale. Questa possibilità si riduce con l'aumentare del set di dati. Ecco alcune dimensioni dell'array e le relative possibilità di esecuzione più lunghe di 10 * ideali:

  • Matrice di 640 articoli: 10 ^ -13 (richiede 15 buoni punti pivot su 60 tentativi)
  • Matrice di 5.000 articoli: 10 ^ -18 (richiede 22 buoni pivot su 88 tentativi)
  • Matrice di 40.000 articoli: 10 ^ -23 (richiede 29 buoni pivot su 116)

Ricorda che questo è con 2 ipotesi conservative che sono peggiori della realtà. Quindi le prestazioni effettive sono ancora migliori e l'equilibrio delle probabilità rimanenti è più vicino all'ideale che no.

Infine, come altri hanno già detto, anche questi casi assurdamente improbabili possono essere eliminati passando a un tipo di heap se lo stack di ricorsione va troppo in profondità. Quindi il TLDR è che, per le buone implementazioni di QuickSort, il caso peggiore non esiste davvero perché è stato progettato e l'esecuzione è stata completata in tempo O (n * logn).


1
"le grandi risposte esistenti" - quali sono quelle? Non riesco a trovarli.
Jim Balter,

Alcune variazioni di Ordinamento rapido notificano la funzione di confronto in merito alle partizioni, in modo da consentirle di sfruttare situazioni in cui una parte sostanziale della chiave sarà la stessa per tutti gli elementi in una partizione?
supercat

4

Perché Quicksort è buono?

  • QuickSort prende N ^ 2 nel caso peggiore e NlogN nel caso medio. Il caso peggiore si verifica quando i dati vengono ordinati. Questo può essere mitigato da shuffle casuale prima dell'inizio dell'ordinamento.
  • QuickSort non richiede ulteriore memoria occupata dall'unione dell'ordinamento.
  • Se il set di dati è grande e ci sono elementi identici, la complessità di Quicksort si riduce utilizzando la partizione a 3 vie. Più il numero di articoli identici è meglio il tipo. Se tutti gli elementi sono identici, si ordina in tempo lineare. [Questa è l'implementazione predefinita nella maggior parte delle librerie]

Quicksort è sempre meglio di Mergesort?

Non proprio.

  • Mergesort è stabile ma Quicksort non lo è. Quindi, se hai bisogno di stabilità nell'output, useresti Mergesort. La stabilità è richiesta in molte applicazioni pratiche.
  • La memoria è economica al giorno d'oggi. Quindi, se la memoria aggiuntiva utilizzata da Mergesort non è fondamentale per la tua applicazione, non vi è alcun danno nell'uso di Mergesort.

Nota: in java, la funzione Arrays.sort () utilizza Quicksort per tipi di dati primitivi e Mergesort per tipi di dati oggetto. Poiché gli oggetti consumano overhead di memoria, l'aggiunta di un piccolo overhead per Mergesort potrebbe non costituire un problema per il punto di vista delle prestazioni.

Riferimento : guarda i video QuickSort della settimana 3, Princeton Algorithms Course a Coursera


"Questo può essere mitigato dal riordino casuale prima dell'inizio dell'ordinamento." - Ehm, no, sarebbe costoso. Invece, usa i perni casuali.
Jim Balter,

4

Quicksort NON è meglio di mergesort. Con O (n ^ 2) (caso peggiore che si verifica raramente), quicksort è potenzialmente molto più lento di O (nlogn) dell'ordinamento di tipo merge. Quicksort ha un sovraccarico minore, quindi con computer piccoli e lenti è meglio. Ma i computer sono così veloci oggi che l'overhead aggiuntivo di un mergesort è trascurabile, e il rischio di un quicksort molto lento supera di gran lunga l'insignificante overhead di un mergesort nella maggior parte dei casi.

Inoltre, un mergesort lascia gli articoli con chiavi identiche nel loro ordine originale, un attributo utile.


2
La tua seconda frase dice "... mergesort è potenzialmente molto più lento di ... mergesort". Il primo riferimento dovrebbe essere presumibilmente quello di quicksort.
Jonathan Leffler,

L'ordinamento di unione è stabile solo se l'algoritmo di unione è stabile; questo non è garantito.
Più chiaro il

@Clearer È garantito se <=viene utilizzato per i confronti anziché <, e non c'è motivo di non farlo.
Jim Balter

@JimBalter Potrei facilmente elaborare un algoritmo di fusione instabile (quicksort, ad esempio, servirebbe quel ruolo). Il motivo per cui l'ordinamento rapido è più veloce dell'unione ordinamento in molti casi non è a causa del sovraccarico ridotto ma del modo in cui quicksort accede ai dati, che è molto più intuitivo per la cache rispetto a un mergesort standard.
Più chiaro il

@Clearer quicksort non è un tipo di unione ... la tua dichiarazione del 21 dicembre 14 a cui ho risposto riguardava rigorosamente l'ordinamento di fusione e se è stabile. quicksort e che è più veloce non è affatto rilevante per il tuo commento o la mia risposta. Fine della discussione per me ... ancora e ancora.
Jim Balter,

3

La risposta si inclinerebbe leggermente verso quicksort rispetto alle modifiche apportate con DualPivotQuickSort per i valori primitivi. Viene utilizzato in JAVA 7 per ordinare in java.util.Arrays

It is proved that for the Dual-Pivot Quicksort the average number of
comparisons is 2*n*ln(n), the average number of swaps is 0.8*n*ln(n),
whereas classical Quicksort algorithm has 2*n*ln(n) and 1*n*ln(n)
respectively. Full mathematical proof see in attached proof.txt
and proof_add.txt files. Theoretical results are also confirmed
by experimental counting of the operations.

È possibile trovare l'implmentation Java7 qui - http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/util/Arrays.java

Ulteriore fantastica lettura su DualPivotQuickSort - http://permalink.gmane.org/gmane.comp.java.openjdk.core-libs.devel/2628


3

In merge-sort, l'algoritmo generale è:

  1. Ordina l'array secondario sinistro
  2. Ordina l'array secondario giusto
  3. Unire i 2 sotto-array ordinati

Al livello più alto, l'unione dei 2 sotto-array ordinati implica la gestione di N elementi.

Un livello inferiore a quello, ogni iterazione del passaggio 3 comporta la gestione di N / 2 elementi, ma è necessario ripetere questo processo due volte. Quindi hai ancora a che fare con 2 * N / 2 == N elementi.

Un livello inferiore a quello, stai unendo 4 * N / 4 == N elementi e così via. Ogni profondità nello stack ricorsivo comporta l'unione dello stesso numero di elementi, in tutte le chiamate per quella profondità.

Considera invece l'algoritmo di ordinamento rapido:

  1. Scegli un punto di articolazione
  2. Posizionare il punto di articolazione nel punto corretto dell'array, con tutti gli elementi più piccoli a sinistra e gli elementi più grandi a destra
  3. Ordina il subarray sinistro
  4. Ordina il subarray destro

Al livello superiore, hai a che fare con una matrice di dimensioni N. Quindi scegli un punto di articolazione, mettilo nella sua posizione corretta e puoi quindi ignorarlo completamente per il resto dell'algoritmo.

Un livello inferiore a quello, hai a che fare con 2 sotto-array che hanno una dimensione combinata di N-1 (cioè sottrai il punto di perno precedente). Scegli un punto pivot per ciascun sotto-array, che arriva fino a 2 punti pivot aggiuntivi.

Un livello inferiore a quello, hai a che fare con 4 sotto-array con dimensione combinata N-3, per gli stessi motivi di cui sopra.

Quindi N-7 ... Quindi N-15 ... Quindi N-32 ...

La profondità della pila ricorsiva rimane approssimativamente la stessa (logN). Con merge-sort, hai sempre a che fare con una fusione di elementi N, attraverso ogni livello dello stack ricorsivo. Con l'ordinamento rapido, tuttavia, il numero di elementi con cui hai a che fare diminuisce man mano che scendi in pila. Ad esempio, se osservi la profondità a metà della pila ricorsiva, il numero di elementi con cui hai a che fare è N - 2 ^ ((logN) / 2)) == N - sqrt (N).

Disclaimer: su merge-sort, poiché ogni volta si divide l'array in 2 blocchi esattamente uguali, la profondità ricorsiva è esattamente logN. Per l'ordinamento rapido, poiché è improbabile che il punto di articolazione si trovi esattamente nel mezzo dell'array, la profondità dello stack ricorsivo potrebbe essere leggermente maggiore di logN. Non ho fatto i calcoli per vedere quanto sia importante questo ruolo e il fattore sopra descritto, in realtà giocano nella complessità dell'algoritmo.


Il fatto che i perni non facciano parte del genere al livello successivo non è il motivo per cui QS è più performante. Vedi le altre risposte per ulteriori approfondimenti.
Jim Balter,

@JimBalter A quali "altre risposte" ti riferisci? La risposta principale dice semplicemente che QS "richiede poco spazio aggiuntivo ed esibisce una buona localizzazione della cache" ma non fornisce alcuna spiegazione sul perché ciò sia né fornisce citazioni. La seconda risposta dice semplicemente che merge-sort è meglio per set di dati più grandi
RvPr

Stai spostando gli obiettivi, dal perché QS è più performante alla spiegazione dei fatti di base su come funziona. Le risposte ad altre domande lo fanno: stackoverflow.com/questions/9444714/… ... Spero che sia abbastanza per te; Non risponderò più.
Jim Balter,

3

A differenza di Unisci ordinamento L'ordinamento rapido non utilizza uno spazio ausiliario. Mentre Merge Sort utilizza uno spazio ausiliario O (n). Ma Merge Sort presenta la complessità temporale peggiore di O (nlogn) mentre la complessità peggiore di Quick Sort è O (n ^ 2) che si verifica quando l'array è già ordinato.


No, il caso peggiore di QuickSort non si verifica quando l'array è già ordinato, a meno che non si usi il primo o l'ultimo elemento come perno, ma nessuno lo fa.
Jim Balter,

2

Quicksort presenta una complessità media dei casi migliore, ma in alcune applicazioni è la scelta sbagliata. Quicksort è vulnerabile agli attacchi denial of service. Se un attaccante può scegliere l'input da ordinare, può facilmente costruire un set che prende la peggior complessità del tempo di o (n ^ 2).

La complessità media del caso di Mergesort e la complessità del caso peggiore sono le stesse e come tali non presentano lo stesso problema. Questa proprietà di merge-sort la rende anche la scelta migliore per i sistemi in tempo reale - proprio perché non ci sono casi patologici che ne fanno funzionare molto, molto più lentamente.

Sono un grande fan di Mergesort di quanto non lo sia di Quicksort, per questi motivi.


2
In che modo Quicksort ha una migliore complessità media dei casi? Sono entrambi O (nlgn). Direi che un utente malintenzionato non fornirà input a nessun algoritmo di ordinamento ... ma nell'interesse di non assumere la sicurezza per oscurità, supponiamo che potrebbe. Mentre il tempo di esecuzione di n ^ 2 è peggiore di nlgn, non è sufficientemente peggio che un server Web si arresti in modo anomalo in base a un singolo attacco. In effetti, l'argomento DOS è praticamente nullo, poiché qualsiasi server Web è vulnerabile a un attacco DDOS ed è più probabile che un utente malintenzionato utilizzi una rete distribuita di host, tutti inondazioni TCP SYN.
CaTalyst.X,

"Quicksort ha una complessità media dei casi migliore", no.
Jim Balter,

2

È difficile da dire: il peggio di MergeSort è n (log2n) -n + 1, che è preciso se n è uguale a 2 ^ k (l'ho già dimostrato). E per ogni n, è compreso tra (n lg n - n + 1) e (n lg n + n + O (lg n)). Ma per quickSort, il suo meglio è nlog2n (anche n è uguale a 2 ^ k). Se dividi Mergesort per quickSort, è uguale a uno quando n è infinito. è come se il caso peggiore di MergeSort fosse migliore del caso migliore di QuickSort, perché utilizziamo quicksort? Ma ricorda, MergeSort non è a posto, richiede 2n di spazio nella memoria. E MergeSort deve anche fare molte copie dell'array, che noi non includere nell'analisi dell'algoritmo. In una parola, MergeSort è davvero più veloce di quicksort in theroy, ma in realtà è necessario considerare lo spazio memeory, il costo della copia dell'array, la fusione è più lenta dell'ordinamento rapido. esperimento in cui mi sono state date 1000000 cifre in Java dalla classe casuale,e ci sono voluti 2610ms da mergesort, 1370ms da quicksort.


2

L'ordinamento rapido è il caso peggiore O (n ^ 2), tuttavia, il caso medio in modo coerente esegue l'ordinamento di tipo merge. Ogni algoritmo è O (nlogn), ma è necessario ricordare che quando si parla di Big O si lasciano fuori i fattori di complessità inferiore. L'ordinamento rapido ha miglioramenti significativi rispetto all'ordinamento per fusione quando si tratta di fattori costanti.

Unisci ordinamento richiede anche la memoria O (2n), mentre l'ordinamento rapido può essere eseguito sul posto (richiede solo O (n)). Questo è un altro motivo per cui l'ordinamento rapido è generalmente preferito all'ordinamento di tipo merge.

Informazioni extra:

Il caso peggiore di ordinamento rapido si verifica quando il perno viene scelto male. Considera il seguente esempio:

[5, 4, 3, 2, 1]

Se il perno viene scelto come il numero più piccolo o più grande nel gruppo, l'ordinamento rapido verrà eseguito in O (n ^ 2). La probabilità di scegliere l'elemento che si trova nel 25% più grande o più piccolo dell'elenco è 0,5. Questo dà all'algoritmo una probabilità 0,5 di essere un buon perno. Se utilizziamo un tipico algoritmo di scelta pivot (diciamo la scelta di un elemento casuale), abbiamo 0,5 possibilità di scegliere un buon pivot per ogni scelta di un pivot. Per raccolte di grandi dimensioni, la probabilità di scegliere sempre un perno pivot è 0,5 * n. Sulla base di questa probabilità, l'ordinamento rapido è efficiente per il caso medio (e tipico).


O (2n) == O (n). L'affermazione corretta è che Mergesort ha bisogno di memoria aggiuntiva O (n) (più specificamente, ha bisogno di n / 2 memoria ausiliaria). E questo non è vero per gli elenchi collegati.
Jim Balter,

@JimBalter Signore, ti dispiacerebbe condividere con noi le tue idee brillanti e utili sulle loro prestazioni come risposta alla domanda? Grazie in anticipo.
sn

2

Questa è una domanda piuttosto vecchia, ma dal momento che ho affrontato entrambi recentemente ecco il mio 2c:

Unisci le esigenze di ordinamento in media ~ N log N confronti. Per le matrici ordinate già (quasi) ordinate, questo scende a 1/2 N log N, poiché durante l'unione selezioniamo (quasi) sempre la parte "sinistra" 1/2 N di volte e quindi copiamo solo gli elementi 1/2 N di destra. Inoltre, posso ipotizzare che l'input già ordinato faccia brillare il predittore dei rami del processore, ma indovinando quasi tutti i rami correttamente, evitando così le stalle della pipeline.

L'ordinamento rapido in media richiede ~ 1,38 N log N confronti. Non beneficia molto dell'array già ordinato in termini di confronti (tuttavia lo fa in termini di scambi e probabilmente in termini di previsioni di diramazione all'interno della CPU).

I miei benchmark su un processore abbastanza moderno mostrano quanto segue:

Quando la funzione di confronto è una funzione di callback (come nell'implementazione libc di qsort), quicksort è più lento rispetto alla fusione del 15% sull'input casuale e del 30% per l'array già ordinato per numeri interi a 64 bit.

D'altra parte, se il confronto non è un callback, la mia esperienza è che quicksort supera i fusioni fino al 25%.

Tuttavia, se il tuo array (di grandi dimensioni) ha pochissimi valori univoci, in ogni caso l'unione ordinamento inizia a guadagnare su quicksort.

Quindi forse la linea di fondo è: se il confronto è costoso (ad esempio la funzione di callback, il confronto di stringhe, il confronto di molte parti di una struttura per lo più arrivare a un secondo terzo "se" per fare la differenza) - le probabilità sono che sarai migliore con tipo di unione. Per compiti più semplici quicksort sarà più veloce.

Detto ciò, tutto ciò che è stato detto in precedenza è vero: - Quicksort può essere N ^ 2, ma Sedgewick afferma che una buona implementazione randomizzata ha più possibilità che un computer che esegue un ordinamento venga colpito da un fulmine piuttosto che andare N ^ 2 - Mergesort richiede spazio extra


Qsort batte la fusione anche per input ordinati se il confronto è economico?
Eonil

2

Quando ho sperimentato entrambi gli algoritmi di ordinamento, contando il numero di chiamate ricorsive, quicksort ha costantemente meno chiamate ricorsive rispetto a mergesort. È perché quicksort ha dei pivot e i pivot non sono inclusi nelle successive chiamate ricorsive. In questo modo quicksort può raggiungere il caso di base ricorsivo più rapidamente di mergesort.


I pivot non hanno nulla a che fare con il motivo per cui QS ha meno chiamate ricorsive ... è perché metà della ricorsione di QS è ricorsione di coda, che può essere eliminata.
Jim Balter,

2

Questa è una domanda comune posta nelle interviste che, nonostante le migliori prestazioni nel caso peggiore del tipo di unione, quicksort è considerato migliore di unire l'ordinamento, soprattutto per un input di grandi dimensioni. Ci sono alcuni motivi per cui è meglio quicksort:

1- Spazio ausiliario: l' ordinamento rapido è un algoritmo di ordinamento sul posto. L'ordinamento sul posto significa che non è necessario spazio di archiviazione aggiuntivo per eseguire l'ordinamento. Unire l'ordinamento invece richiede un array temporaneo per unire gli array ordinati e quindi non è sul posto.

2- Caso peggiore: il caso peggiore di quicksort O(n^2)può essere evitato usando quicksort randomizzato. Può essere facilmente evitato con alta probabilità scegliendo il perno giusto. Ottenere un comportamento nel caso medio scegliendo l'elemento pivot giusto rende improvvisa la prestazione e diventa efficiente come l'ordinamento Merge.

3- Località di riferimento: Quicksort in particolare mostra una buona posizione della cache e questo lo rende più veloce di unire l'ordinamento in molti casi come nell'ambiente di memoria virtuale.

4 - Ricorsione della coda : QuickSort è ricorsivo della coda, mentre Merge sort non lo è. Una funzione ricorsiva di coda è una funzione in cui la chiamata ricorsiva è l'ultima cosa eseguita dalla funzione. Le funzioni ricorsive della coda sono considerate migliori delle funzioni non ricorsive della coda in quanto la ricorsione della coda può essere ottimizzata dal compilatore.


1

Sebbene siano entrambi nella stessa classe di complessità, ciò non significa che entrambi abbiano lo stesso runtime. Quicksort di solito è più veloce di mergesort, solo perché è più facile codificare un'implementazione rigorosa e le operazioni che fa possono andare più veloci. È perché quel quicksort è generalmente più veloce che le persone lo usano invece di mergesort.

Però! Personalmente userò spesso mergesort o una variante quicksort che degrada a mergesort quando quicksort fa male. Ricorda. Quicksort è solo O (n log n) in media . Il caso peggiore è O (n ^ 2)! Mergesort è sempre O (n log n). Nei casi in cui le prestazioni in tempo reale o la reattività sono indispensabili e i dati di input potrebbero provenire da una fonte dannosa, non è necessario utilizzare quicksort.


1

A parità di condizioni, mi aspetto che la maggior parte delle persone utilizzi tutto ciò che è più convenientemente disponibile, e che tende ad essere qsort (3). A parte questo, quicksort è noto per essere molto veloce sugli array, proprio come il mergesort è la scelta comune per gli elenchi.

Quello che mi chiedo è perché è così raro vedere l' ordinamento di radix o bucket. Sono O (n), almeno negli elenchi collegati e tutto ciò che serve è un metodo per convertire la chiave in un numero ordinale. (stringhe e float funzionano bene.)

Sto pensando che la ragione abbia a che fare con il modo in cui viene insegnata l'informatica. Ho anche dovuto dimostrare al mio docente nell'analisi Algorithm che era davvero possibile ordinare più velocemente di O (n log (n)). (Aveva la prova che non puoi confrontare l' ordinamento più velocemente di O (n log (n)), il che è vero.)

In altre notizie, i float possono essere ordinati come numeri interi, ma in seguito è necessario invertire i numeri negativi.

Modifica: In realtà, ecco un modo ancora più vizioso per ordinare i float-come-numeri interi: http://www.stereopsis.com/radix.html . Tieni presente che il trucco del cambio di bit può essere utilizzato indipendentemente dall'algoritmo di ordinamento effettivamente utilizzato ...


1
Ho visto la mia parte di radix sorta. Ma è piuttosto difficile da usare perché se analizzato correttamente, il suo runtime non è O (n) in quanto dipende da più del numero di elementi di input. In generale, è molto difficile fare quel tipo di previsioni forti che l'ordinamento di Radix deve essere efficiente sull'input.
Konrad Rudolph,

Si è O (n), dove n è il totale dimensione dell'input, cioè, tra cui la dimensione degli elementi. È vero che puoi implementarlo, quindi devi fare il pad con molti zero, ma non ha senso usare una scarsa implementazione per il confronto. (Detto questo, l'implementazione può essere difficile, ymmv.)
Anders Eurenius,

Nota che se stai usando GNU libc, qsortè un ordinamento di tipo merge.
Jason Orendorff il

Ehm, per essere precisi, è un tipo di unione a meno che non sia possibile allocare la memoria temporanea necessaria. cvs.savannah.gnu.org/viewvc/libc/stdlib/…
Jason Orendorff il

1

Piccole aggiunte ai tipi rapidi vs unione.

Inoltre può dipendere dal tipo di articoli di ordinamento. Se l'accesso a elementi, scambio e confronti non sono operazioni semplici, come il confronto di numeri interi nella memoria del piano, è possibile preferire un algoritmo di fusione.

Ad esempio, ordiniamo gli articoli utilizzando il protocollo di rete sul server remoto.

Inoltre, in contenitori personalizzati come "elenco collegato", non ci sono vantaggi dell'ordinamento rapido.
1. Unisci l'ordinamento nell'elenco collegato, non è necessaria memoria aggiuntiva. 2. L'accesso agli elementi nell'ordinamento rapido non è sequenziale (in memoria)


0

L'ordinamento rapido è un algoritmo di ordinamento sul posto, quindi è più adatto per gli array. Unire l'ordinamento invece richiede una memoria aggiuntiva di O (N) ed è più adatto per gli elenchi collegati.

A differenza delle matrici, nella lista piaciuti possiamo inserire elementi nel mezzo con O (1) spazio e O (1) tempo, quindi l'operazione di unione in tipo di unione può essere implementata senza spazio aggiuntivo. Tuttavia, l'allocazione e la disallocazione di spazio aggiuntivo per le matrici hanno un effetto negativo sul tempo di esecuzione dell'ordinamento di tipo merge. Unisci ordinamento favorisce anche l'elenco collegato poiché si accede ai dati in sequenza, senza molto accesso casuale alla memoria.

L'ordinamento rapido, d'altra parte, richiede un sacco di accesso casuale alla memoria e con un array possiamo accedere direttamente alla memoria senza alcun passaggio, come richiesto dagli elenchi collegati. Anche l'ordinamento rapido quando utilizzato per gli array ha una buona località di riferimento poiché gli array vengono memorizzati contigui nella memoria.

Anche se la complessità media di entrambi gli algoritmi di ordinamento è O (NlogN), di solito le persone per attività ordinarie usano un array per l'archiviazione, e per questo motivo l'ordinamento rapido dovrebbe essere l'algoritmo di scelta.

EDIT: Ho appena scoperto che unire sort worst / best / avg case è sempre nlogn, ma l'ordinamento rapido può variare da n2 (nel caso peggiore quando gli elementi sono già ordinati) a nlogn (avg / best case quando pivot divide sempre l'array in due metà).


0

Considera entrambi la complessità del tempo e dello spazio. Per unire l'ordinamento: Complessità temporale: O (nlogn), Complessità spaziale: O (nlogn)

Per ordinamento rapido: complessità temporale: O (n ^ 2), complessità spaziale: O (n)

Ora, entrambi vincono in uno scenario ciascuno. Ma, usando un perno casuale, puoi quasi sempre ridurre la complessità temporale dell'ordinamento rapido a O (nlogn).

Pertanto, l'ordinamento rapido è preferito in molte applicazioni anziché nell'unione ordinamento.


-1

In c / c ++ land, quando non uso i contenitori stl, tendo a usare quicksort, perché è integrato nel tempo di esecuzione, mentre mergesort non lo è.

Quindi credo che in molti casi sia semplicemente il percorso di minor resistenza.

Inoltre, le prestazioni possono essere molto più elevate con l'ordinamento rapido, nei casi in cui l'intero set di dati non si adatta al set di lavoro.


3
In realtà, se si tratta della funzione di libreria qsort () di cui stai parlando, potrebbe essere implementata o meno come quicksort.
Thomas Padron-McCarthy,

3
Konrad, mi dispiace essere un po 'anale per questo, ma dove trovi quella garanzia? Non riesco a trovarlo nello standard ISO C o nello standard C ++.
Thomas Padron-McCarthy,

2
GNU libc qsortè un tipo di unione a meno che il numero di elementi sia davvero gigantesco o la memoria temporanea non possa essere allocata. cvs.savannah.gnu.org/viewvc/libc/stdlib/…
Jason Orendorff il

-3

Uno dei motivi è più filosofico. Quicksort è la filosofia Top-> Down. Con n elementi da ordinare, ci sono n! possibilità. Con 2 partizioni di m & nm che si escludono a vicenda, il numero di possibilità diminuisce in diversi ordini di grandezza. m! * (nm)! è più piccolo di diversi ordini di n! solo. immaginare 5! vs 3! * 2 !. 5! ha 10 volte più possibilità di 2 partizioni di 2 e 3 ciascuna. ed estrapolare a 1 milione fattoriale contro 900K! * 100K! vs. Quindi invece di preoccuparsi di stabilire un ordine all'interno di un intervallo o di una partizione, basta stabilire l'ordine a un livello più ampio nelle partizioni e ridurre le possibilità all'interno di una partizione. Qualsiasi ordine stabilito in precedenza all'interno di un intervallo verrà disturbato in seguito se le partizioni stesse non si escludono a vicenda.

Qualsiasi approccio dal basso verso l'alto come unisci ordinamento o ordinamento heap è come l'approccio di un lavoratore o di un lavoratore in cui si inizia a confrontare in anticipo a livello microscopico. Ma questo ordine è destinato a perdersi non appena un elemento tra loro viene trovato in seguito. Questi approcci sono molto stabili ed estremamente prevedibili, ma svolgono un certo lavoro extra.

L'ordinamento rapido è come l'approccio manageriale in cui inizialmente non ci si preoccupa di alcun ordine, ma solo di soddisfare un criterio ampio senza alcun riguardo per l'ordine. Quindi le partizioni vengono ristrette fino ad ottenere un set ordinato. La vera sfida in Quicksort è trovare una partizione o un criterio al buio quando non si sa nulla degli elementi da ordinare. Questo è il motivo per cui abbiamo bisogno di dedicare qualche sforzo per trovare un valore mediano o sceglierne uno a caso o un approccio "gestionale" arbitrario. Trovare una mediana perfetta può richiedere uno sforzo considerevole e condurre di nuovo a uno stupido approccio dal basso verso l'alto. Quindi Quicksort dice solo di scegliere un perno casuale e spero che sia da qualche parte nel mezzo o faccia qualche lavoro per trovare una mediana di 3, 5 o qualcosa di più per trovare una mediana migliore ma non hai intenzione di essere perfetto & don ' t perdere tempo nell'ordinare inizialmente. Ciò sembra fare bene se sei fortunato o a volte degrada a n ^ 2 quando non ottieni una mediana ma ne hai solo una. In ogni caso i dati sono casuali. destra. Quindi sono più d'accordo con l'approccio logico top -> down di quicksort e si scopre che la possibilità che ci si pone sulla selezione del pivot e sui confronti che salva in precedenza sembra funzionare meglio più volte di qualsiasi meticoloso e accurato approccio bottom stabile -> approccio up come unisci ordinamento. Ma i confronti che salva in precedenza sembrano funzionare meglio più volte di qualsiasi approccio meticoloso e accurato dal basso stabile verso l'alto come unire l'ordinamento. Ma i confronti che salva in precedenza sembrano funzionare meglio più volte di qualsiasi approccio meticoloso e accurato dal basso stabile verso l'alto come unire l'ordinamento. Ma


quicksort trae vantaggio dalla casualità della selezione pivot. Il perno casuale tende naturalmente verso la divisione 50:50 ed è improbabile che sia coerente verso uno degli estremi. Il fattore costante di nlogn è piuttosto basso fino a quando il partizionamento medio è di 60-40 o addirittura fino a 70-30.
Winter Melon

Questa è una totale assurdità. quicksort è usato per le sue prestazioni, non per "filosofia" ... e le affermazioni su "l'ordine è destinato a essere perso" è semplicemente falso.
Jim Balter,
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.