it-swarm.it

Perché quicksort è migliore di un mergesort?

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

339

Quicksort ha O (n2) runtime nel caso peggiore e O (nceppon) runtime caso medio. Tuttavia, è preferibile unire l'ordinamento in molti scenari perché molti fattori influenzano il runtime di un algoritmo e, quando li prende tutti insieme, Quicksort vince.

In particolare, il runtime spesso citato degli algoritmi di ordinamento fa riferimento 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, anche altre cose - come la località di riferimento (cioè leggiamo molti elementi che sono probabilmente nella cache?) - giocano anche un ruolo importante sull'hardware attuale. Quicksort in particolare richiede poco spazio aggiuntivo ed esibisce una buona localizzazione della cache, e in molti casi questo rende più veloce dell'ordinamento dell'unione.

Inoltre, è molto facile evitare il tempo di esecuzione nel caso peggiore di O di Quicksort (n2) quasi interamente utilizzando una scelta appropriata del perno - come ad esempio selezionarlo a caso (questa è una strategia eccellente).

In pratica, molte implementazioni moderne di quicksort (in particolare std::sort di libstdc ++) sono in realtà introsort , il cui caso peggiore teorico è O (nceppon), lo stesso di unire l'ordinamento. Raggiunge questo limite limitando la profondità di ricorsione e passando a un algoritmo diverso ( heapsort ) una volta che supera il registron.

253
Konrad Rudolph

Come molte persone hanno notato, la performance media dei casi per quicksort è più veloce di un mergesort. Ma questo è vero solo se stai assumendo un tempo costante per accedere a qualsiasi pezzo di memoria su richiesta.

In RAM questa ipotesi non è generalmente troppo male (non è sempre vera a causa delle cache, ma non è male). Tuttavia se la tua struttura dati è abbastanza grande da vivere su disco, quicksort ottiene ucciso dal fatto che il tuo disco medio fa qualcosa come 200 ricerche casuali al secondo. Ma lo stesso disco non ha problemi a leggere o scrivere megabyte al secondo di dati in sequenza. Che è esattamente ciò che fa il mergesort.

Pertanto, se i dati devono essere ordinati su disco, si vuole davvero utilizzare alcune varianti su mergesort. (In genere, i quicksort si sottolineranno, quindi inizieranno a unirli insieme sopra una soglia di dimensioni.)

Inoltre, se devi fare qualcosa con set di dati di quella dimensione, pensa seriamente a come evitare di cercare sul disco. Ad esempio, questo è il motivo per cui si consiglia di 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 innanzitutto ordinando le informazioni da trattare (utilizzando un mergesort, ovviamente!) E quindi caricandolo in una infrastruttura BTREE per l'indice. (I BTREE sono naturalmente tenuti in ordine, quindi è possibile caricarne uno da un set di dati ordinato con poche ricerche su disco.)

Ci sono state un certo numero di occasioni in cui capire come evitare la ricerca di dischi mi ha permesso di fare in modo che i lavori di elaborazione dati richiedessero ore anziché giorni o settimane.

271
user11318

In realtà, QuickSort è O (n2). Il suo caso medio tempo di esecuzione è O (nlog (n)), ma il suo worst-case è O (n2), che si verifica quando lo si esegue su un elenco che contiene pochi oggetti unici. La randomizzazione richiede O (n). Naturalmente, questo non cambia il suo caso peggiore, ma impedisce solo a un utente malintenzionato di fare in modo che il tuo tipo richieda molto tempo.

QuickSort è più popolare perché:

  1. È sul posto (MergeSort richiede memoria extra lineare per il numero di elementi da ordinare).
  2. Ha una piccola costante nascosta.
87
Dark Shikari

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

Una ragione psicologica che non è stata data è semplicemente che Quicksort è più abilmente chiamato. cioè buon marketing.

Sì, Quicksort con tripla partioning è probabilmente uno dei migliori algoritmi di ordinamento per scopi generici, ma non c'è modo di superare il fatto che l'ordinamento "Veloce" suona molto più potente dell'ordinamento "Unisci".

29
Ash

Come altri hanno notato, il caso peggiore di Quicksort è O (n ^ 2), mentre mergesort e heapsort rimangono su 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 migliore in media è che il ciclo interno implica il confronto di diversi valori con uno singolo, mentre negli altri due entrambi i termini sono diversi per ciascun confronto. In altre parole, Quicksort fa la metà delle letture degli altri due algoritmi. Nelle moderne CPU, le prestazioni sono pesantemente dominate dai tempi di accesso, quindi alla fine Quicksort finisce per essere una grande prima scelta.

15
Javier

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

Ma, a dire il vero, in situazioni pratiche molte persone hanno bisogno solo di buone prestazioni medie e quicksort è ... quick =)

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

8
Antti Rasinen

Da la 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. Il Mergesort è un ordinamento stabile, a differenza di quicksort e heapsort, e può essere facilmente adattato per operare su elenchi concatenati e elenchi molto grandi memorizzati su supporti ad accesso lento come lo storage su disco o l'archiviazione collegata alla rete. Sebbene Quicksort possa essere scritto per operare su liste collegate, spesso soffrirà di scarse opzioni di pivot senza accesso casuale. Lo svantaggio principale 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 di coda utilizza solo lo spazio Θ (logn). (Si noti che quando si opera in elenchi collegati, il mergesort richiede solo una piccola quantità costante di memoria ausiliaria).

7
gnobal

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

Mergesort merita di essere preso in considerazione se la velocità è essenziale, le prestazioni peggiori nel caso peggiore non possono essere tollerate e lo spazio extra è disponibile. 1

Hai affermato che "Sono entrambi O(nlogn) [...]". Questo è sbagliato. "Quicksort utilizza i confronti n ^ 2/2 nel caso peggiore." 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 i linguaggi di programmazione con il paradigma imperativo.

1 Sedgewick, Algoritmi

7
Roman Glass

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

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

6
Niyaz

La spiegazione di Wikipedia è:

In genere, quicksort è significativamente più veloce nella pratica rispetto ad altri algoritmi Θ (nlogn), perché il suo ciclo interno può essere implementato in modo efficiente su molte architetture e nella maggior parte dei dati del mondo reale è possibile fare scelte di progettazione che riducono al minimo 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 quicksort non hanno. Nel peggiore dei casi, hanno la stessa quantità di tempo algoritmico, ma il mergesort richiede più spazio di archiviazione.

5
Mat Mannion

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

Al di fuori dei problemi di accesso casuale, vi sono due fattori principali che possono influire sulle prestazioni di QuickSort e sono entrambi correlati al modo in cui il perno si 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 volta su un QuickSort di Vanilla 2 partizione perché tutti i valori tranne la posizione di pivot vengono posizionati su un lato ogni volta. Le moderne implementazioni affrontano questo problema con metodi come l'uso di un ordinamento a 3 partizioni. Questi metodi vengono eseguiti su un set di dati con lo stesso valore in O(n) volta. Pertanto, l'utilizzo di tale implementazione implica che un input con un numero ridotto di chiavi migliora effettivamente il tempo di esecuzione e non rappresenta più un problema.

2) La selezione pivot estremamente brutta può causare prestazioni nel caso peggiore. In un caso ideale, il pivot sarà sempre tale che il 50% dei dati è più piccolo e il 50% dei dati è più grande, così che l'input sarà spezzato a metà durante ogni iterazione. Questo ci dà n confronti e tempi di scambio log-2 (n) ricorsioni per O (n * logn) tempo.

In che misura la selezione del perno non ideale influisce sul tempo di esecuzione?

Consideriamo un caso in cui il pivot viene scelto in modo coerente in modo 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 in termini di 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 veloce fino a quando la scelta pivot diventa (costantemente) molto negativa:

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

Quando ci avviciniamo al 100% su un lato, la porzione di registro 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 primo elemento pivot) o un array ordinato in ordine inverso (per l'ultimo pivot dell'elemento) generano in modo affidabile il tempo di esecuzione O (n ^ 2) nel caso peggiore. Inoltre, le implementazioni con una selezione pivot prevedibile possono essere sottoposte all'attacco DoS da parte di dati progettati per produrre l'esecuzione nel caso peggiore. Le moderne implementazioni lo evitano con una varietà di metodi, come randomizzare i dati prima di ordinare, scegliere la mediana di 3 indici scelti a caso, 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.
  • Grande set di dati. Il caso peggiore è possibile in teoria ma non nella pratica.

Quanto è probabile che vedremo prestazioni terribili?

Le probabilità sono incredibilmente piccole . Consideriamo una sorta di 5.000 valori:

La nostra ipotetica implementazione sceglierà un pivot usando una mediana di 3 indici scelti a caso. Considereremo i perni compresi nell'intervallo tra il 25% e il 75% per essere "buoni" e i perni compresi nell'intervallo 0% -25% o 75% -100% per essere "cattivi". Se si guarda la distribuzione di probabilità usando la mediana di 3 indici casuali, ciascuna ricorsione ha una probabilità di 11/16 di finire con un buon pivot. Facciamo 2 ipotesi conservative (e false) per semplificare la matematica:

  1. I buoni perni sono sempre esattamente al 25%/75% divisi e funzionano al caso ideale 2.4 *. Non otteniamo mai una divisione ideale o uno split migliore di 25/75.

  2. I pivot sbagliati sono sempre i peggiori e non contribuiscono sostanzialmente alla soluzione.

La nostra implementazione di QuickSort si fermerà a n = 10 e passerà a un ordinamento di inserimento, quindi abbiamo bisogno di 22 partizioni pivot del 25%/75% per interrompere il valore di 5.000 input fino a quel punto. (10 * 1.333333 ^ 22> 5000) Oppure, abbiamo bisogno di 4990 pivot nel caso peggiore. Tieni presente che se accumuliamo 22 buoni pivot a qualsiasi punto , allora l'ordinamento verrà completato, quindi nel peggiore dei casi o in qualsiasi altra cosa ci sia bisogno estremamente sfortuna . Se ci sono volute 88 ricorsioni per ottenere effettivamente i 22 buoni pivot necessari per ordinare fino a n = 10, sarebbe 4 * 2.4 * caso ideale o circa 10 volte il tempo di esecuzione del caso ideale. Quanto è probabile che non dovremmo non raggiungere i 22 pivot necessari dopo 88 ricorsioni?

Le distribuzioni di probabilità binomiale possono rispondere a questo, e la risposta è circa 10 ^ -18. (n è 88, k è 21, p è 0,6875) Il tuo utente è circa un migliaio di volte più probabilità di essere colpito da un fulmine nel 1 secondo necessario per fare clic su [SORT] di quello che sono per vedere che 5.000 item sort run peggiore di 10 * caso ideale. Questa possibilità si riduce man mano che il set di dati diventa più grande. Ecco alcune dimensioni di array e le relative probabilità di funzionare più a lungo di 10 * ideale:

  • Matrice di 640 elementi: 10 ^ -13 (richiede 15 buoni punti di rotazione 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 il saldo della probabilità rimanente è più vicino all'ideale che non.

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

4
Lance Wisely

Quicksort NON è migliore di un mergesort. Con O (n ^ 2) (il caso peggiore che accade raramente), quicksort è potenzialmente molto più lento del O(nlogn) dell'ordinamento di fusione. Quicksort ha meno spese generali, quindi con computer piccoli e lenti, è meglio. Ma oggi i computer sono così veloci che il sovraccarico 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 elementi con chiavi identiche nel loro ordine originale, un utile attributo.

4
xpda

A differenza di Unisci Ordina Ordina veloce non utilizza uno spazio ausiliario. Mentre Merge Sort utilizza uno spazio ausiliario O (n). Ma Merge Sort ha la complessità del caso peggiore di O(nlogn) considerando che la complessità peggiore di Quick Sort è O (n ^ 2) che si verifica quando l'array è già ordinato.

3
Shantam Mittal

La risposta si inclina leggermente verso quicksort w.r.t alle modifiche apportate con DualPivotQuickSort per i valori primitivi. È usato 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.

Qui puoi trovare l'impiantazione Java7 - http://grepcode.com/file/repository.grepcode.com/Java/root/jdk/openjdk/7-b147/Java/util/Array .java

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

3
SSR

In merge-sort, l'algoritmo generale è:

  1. Ordina l'array secondario sinistro
  2. Ordina il sub-array giusto
  3. Unisci i 2 sotto-array ordinati

Al livello più alto, la fusione dei 2 sottosegmenti ordinati comporta la gestione di N elementi.

A un livello inferiore a quello, ogni iterazione del passaggio 3 implica il trattamento di elementi N/2, 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 implica l'unione dello stesso numero di elementi, attraverso tutte le chiamate per quella profondità.

Considera invece l'algoritmo di ordinamento rapido:

  1. Scegli un punto di svolta
  2. Posiziona il punto di rotazione nella posizione corretta nell'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 di destra

Al livello più alto, hai a che fare con un array di dimensioni N. Scegli quindi un punto di rotazione, 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è sottrarre il punto pivot precedente). Scegli un punto di articolazione per ogni sub-array, che arriva a 2 ulteriori punti di rotazione.

A un livello inferiore, hai a che fare con 4 sotto-array con dimensioni combinate N-3, per gli stessi motivi di cui sopra.

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

La profondità della tua pila ricorsiva rimane approssimativamente la stessa (logN). Con merge-sort, hai sempre a che fare con un'unione di elementi N, attraverso ogni livello dello stack ricorsivo. Con quick-sort, però, il numero di elementi con cui hai a che fare diminuisce man mano che vai in pila. Ad esempio, se si guarda la profondità a metà della pila ricorsiva, il numero di elementi con cui si ha a che fare è N - 2 ^ ((logN)/2)) == N - sqrt (N).

Dichiarazione di non responsabilità: su un merge-sort, poiché ogni volta si divide l'array in due blocchi esattamente uguali, la profondità ricorsiva è esattamente logN. In caso di ordinamento rapido, poiché è improbabile che il punto di pivot si trovi esattamente nel mezzo dell'array, la profondità dello stack ricorsivo potrebbe essere leggermente superiore a logN. Non ho fatto i calcoli per vedere quanto grande sia questo fattore e il fattore sopra descritto, in realtà giocano nella complessità dell'algoritmo.

3
RvPr

Quicksort ha una complessità del caso medio migliore ma in alcune applicazioni è la scelta sbagliata. Quicksort è vulnerabile agli attacchi denial of service. Se un utente malintenzionato può scegliere l'input da ordinare, può facilmente costruire un set che impiega la complessità temporale più grave di o (n ^ 2).

La complessità del caso medio di Mergesort e la complessità del caso peggiore sono le stesse e, come tale, non subiscono lo stesso problema. Questa proprietà di merge-sort lo rende anche la scelta migliore per i sistemi in tempo reale, proprio perché non ci sono casi patologici che causano un funzionamento molto, molto più lento.

Sono un fan più grande di Mergesort di me di Quicksort, per queste ragioni.

2
Simon Johnson

Mentre sono entrambi nella stessa classe di complessità, ciò non significa che entrambi abbiano lo stesso runtime. Quicksort è solitamente più veloce di un mergesort, solo perché è più facile codificare un'implementazione ristretta e le operazioni che esegue possono andare più velocemente. È perché quel quicksort è generalmente più veloce che le persone lo usano invece di un 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) on media . Il caso peggiore è O (n ^ 2)! Il Mergesort è sempre O (n log n). Nei casi in cui le prestazioni in tempo reale o la reattività sono un must ei tuoi dati di input potrebbero provenire da una fonte malevola, non dovresti usare quicksort semplice.

2
DJ Capelis

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

L'ordinamento unione richiede anche O(2n) memoria, mentre l'ordinamento rapido può essere eseguito in posizione (richiede solo O (n)). Questo è un altro motivo per cui l'ordinamento rapido è generalmente preferito rispetto all'unione di tipo merge.

Ulteriori informazioni:

Il caso peggiore di ordinamento rapido si verifica quando il pivot viene scelto in modo errato. Considera il seguente esempio:

[5, 4, 3, 2, 1]

Se il pivot viene scelto come il numero più piccolo o più grande del 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 della lista è 0,5. Ciò fornisce all'algoritmo una probabilità di 0,5 di essere un buon pivot. Se utilizziamo un tipico algoritmo di scelta del pivot (diciamo scegliendo un elemento casuale), abbiamo 0,5 possibilità di scegliere un buon pivot per ogni scelta di un pivot. Per le raccolte di grandi dimensioni, la probabilità di scegliere sempre un pivot povero è 0,5 * n. Sulla base di questa probabilità, l'ordinamento rapido è efficiente per il caso medio (e tipico).

2
Wade Anderson

Perché Quicksort è buono?

  • QuickSort prende N ^ 2 nel caso peggiore e nel caso medio NlogN. Il caso peggiore si verifica quando i dati vengono ordinati. Questo può essere mitigato da casuale shuffle prima di iniziare l'ordinamento.
  • QuickSort non prende la memoria aggiuntiva che viene acquisita dall'ordinamento di unione.
  • 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 oggetti identici è migliore del genere. Se tutti gli elementi sono identici, ordina in tempo lineare. [Questa è l'implementazione predefinita nella maggior parte delle librerie]

Quicksort è sempre migliore di Mergesort?

Non proprio.

  • Mergesort è stabile ma Quicksort non lo è. Quindi se hai bisogno di stabilità in uscita, useresti Mergesort. La stabilità è richiesta in molte applicazioni pratiche.
  • La memoria è a buon mercato al giorno d'oggi. Quindi, se la memoria extra utilizzata da Mergesort non è fondamentale per la tua applicazione, non c'è nulla di male nell'uso di Mergesort.

Nota: In Java, la funzione Arrays.sort () utilizza Quicksort per i tipi di dati primitivi e Mergesort per i tipi di dati dell'oggetto. Poiché gli oggetti consumano sovraccarico della memoria, quindi l'aggiunta di un piccolo overhead per Mergesort potrebbe non rappresentare un problema per il punto di vista delle prestazioni.

Riferimento : Guarda i video QuickSort di Settimana 3, Corso Princeton Algorithms a Coursera

2

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

L'ordinamento di unione ha bisogno in media di confronti N log N. Per gli array ordinati già (quasi) ordinati, questo si riduce a 1/2 N log N, poiché durante la fusione abbiamo (quasi) sempre selezionato la parte "sinistra" 1/2 N di volte e poi solo la copia destra di 1/2 elementi N. Inoltre, posso ipotizzare che l'input già ordinato renda lucidi i predittori del processore, ma indovinano quasi tutti i rami correttamente, prevenendo così le bancarelle della pipeline.

L'ordinamento rapido richiede in media ~ 1.38 confronti N log N. Non trae grandi benefici dall'array già ordinato in termini di confronti (ma lo fa in termini di swap e probabilmente in termini di previsioni di branch all'interno della CPU).

I miei benchmark su processori abbastanza moderni mostrano quanto segue:

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

D'altro canto, se il confronto non è una richiamata, la mia esperienza è che Quicksort sovraperforma il mergesort fino al 25%.

Tuttavia, se il tuo (grande) array ha pochissimi valori unici, l'unisci sort inizia a guadagnare su quicksort in ogni caso.

Quindi forse la linea di fondo è: se il confronto è costoso (es. Funzione di callback, confrontare stringhe, confrontare molte parti di una struttura per lo più arrivando a un secondo-terzo "se" per fare la differenza) - le probabilità sono che tu sia migliore con unire l'ordinamento. Per attività più semplici, quicksort sarà più veloce.

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

2
virco

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

2
Aldian Fazrihady

Piccole aggiunte al rapido e all'unione si uniscono.

Inoltre può dipendere dal tipo di elementi di ordinamento. Se l'accesso agli elementi, allo scambio e ai confronti non è un'operazione semplice, come confrontare interi nella memoria piana, l'unire l'ordinamento può essere un algoritmo preferibile.

Ad esempio, ordiniamo gli elementi usando il protocollo di rete sul server remoto.

Inoltre, in contenitori personalizzati come "elenco collegato", non ci sono vantaggi di ordinamento rapido.
1. Unisci ordina nell'elenco collegato, non è necessario ulteriore memoria. 2. L'accesso agli elementi nell'ordinamento veloce non è sequenziale (in memoria)

1
minorlogic

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

1
Peter

A parità di condizioni, mi aspetterei che la maggior parte delle persone utilizzi 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 più comune per le liste.

Quello che mi chiedo è perché sia ​​così raro vedere radix o bucket sort. Sono O (n), almeno nelle liste collegate e tutto quello che serve è un metodo per convertire la chiave in un numero ordinale. (gli archi e i galleggianti funzionano bene).

Penso che la ragione abbia a che fare con il modo in cui viene insegnata l'informatica. Ho persino dovuto dimostrare al mio professore di Algorithm Analysis che era effettivamente possibile ordinare più velocemente di O (n log (n)). (Aveva la prova che non è possibile comparare ordinare più velocemente di O (n log (n)), che è vero).

In altre notizie, i float possono essere ordinati come numeri interi, ma devi girare i numeri negativi in ​​seguito.

Modifica: in realtà, ecco un modo ancora più vizioso per ordinare i float-as-interi: http://www.stereopsis.com/radix.html . Nota che il trucco del bit-flipping può essere usato indipendentemente da quale algoritmo di ordinamento effettivamente usi ...

1
Anders Eurenius

Considerare la complessità di tempo e spazio entrambi. Per unisci ordina: complessità temporale: O(nlogn), complessità dello spazio: O (nlogn)

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

Ora, vincono entrambi in uno scenerio ciascuno. Ma, usando un pivot casuale, puoi quasi sempre ridurre la complessità temporale di Ordinamento rapido a O (nlogn).

Pertanto, l'ordinamento rapido è preferito in molte applicazioni anziché in Merge sort.

0
pankaj

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

A differenza degli array, nella lista dei preferiti possiamo inserire gli elementi nel mezzo con O(1) spazio e O(1) volta, quindi l'operazione di unione in sort di merge può essere implementata senza spazio extra. Tuttavia, l'allocazione e la disallocazione di spazio aggiuntivo per gli array hanno un effetto negativo sul tempo di esecuzione dell'ordinamento di tipo merge. Unisci ordina favorisce anche la lista collegata mentre i dati sono accessibili in sequenza, senza molti accessi casuali 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 alcuna traversata come richiesto dagli elenchi collegati. Anche l'ordinamento rapido quando utilizzato per gli array ha una buona localizzazione di riferimento poiché gli array vengono memorizzati in modo contiguo nella memoria.

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

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

0
Saad