Ho implementato merge sort e quick sort usando C (GCC 4.4.3 su Ubuntu 10.04 su un laptop da 4 GB con CPU Intel DUO a 2GHz) e volevo confrontare le prestazioni dei due algoritmi.
I prototipi delle funzioni di ordinamento sono:
void merge_sort(const char **lines, int start, int end);
void quick_sort(const char **lines, int start, int end);
vale a dire. entrambi prendono una serie di puntatori alle stringhe e ordinano gli elementi con l'indice i: start < = i < = end.
Ho prodotto alcuni file contenenti stringhe casuali con una lunghezza media di 4,5 caratteri. I file di test vanno da 100 righe a 10000000 righe.
Sono rimasto un po 'sorpreso dai risultati perché, anche se so che l'unire sort ha complessità O (n log (n)) mentre l'ordinamento rapido è O (n ^ 2), ho letto spesso che in media sort veloce dovrebbe essere veloce come unire l'ordinamento. Tuttavia, i miei risultati sono i seguenti.
- Fino a 10000 stringhe, entrambi gli algoritmi funzionano altrettanto bene. Per 10000 stringhe, entrambe richiedono circa 0,007 secondi.
- Per 100000 stringhe, l'unione di ordinamento è leggermente più veloce con 0,095 s contro 0,121 s.
- Per 1000000 stringhe, l'ordinamento accetta 1.287 s contro 5.233 s di ordinamento rapido.
- Per 5000000 stringhe, l'ordinamento richiede 7.582 s contro 118.240 s di ordinamento rapido.
- Per 10000000 stringhe, l'ordinamento prende 16.305 s contro 1202.918 s di ordinamento rapido.
Quindi la mia domanda è: i miei risultati sono come previsto , il che significa che l'ordinamento rapido è comparabile in velocità per unire l'ordinamento per i piccoli input ma, con l'aumentare delle dimensioni dei dati di input, il fatto che la complessità è quadratica diventerà evidente?
Ecco uno schizzo di ciò che ho fatto. Nell'implementazione di tipo merge, il partizionamento consiste nel chiamare ricorsivamente l'ordinamento di merge, cioè
merge_sort(lines, start, (start + end) / 2);
merge_sort(lines, 1 + (start + end) / 2, end);
L'unione dei due sub-array ordinati viene eseguita leggendo i dati dall'array lines
e scrivendoli su una matrice temporanea globale di puntatori (questo array globale viene assegnato una sola volta). Dopo ogni unione i puntatori vengono copiati nell'array originale. Quindi le stringhe vengono memorizzate una volta, ma ho bisogno del doppio della memoria per i puntatori.
Per l'ordinamento rapido, la funzione di partizione sceglie l'ultimo elemento dell'array da ordinare come pivot e analizza gli elementi precedenti in un ciclo. Dopo aver prodotto una partizione del tipo
start ... {elements <= pivot} ... pivotIndex ... {elements > pivot} ... end
si chiama in modo ricorsivo:
quick_sort(lines, start, pivotIndex - 1);
quick_sort(lines, pivotIndex + 1, end);
Si noti che questa implementazione rapida dell'ordinamento ordina l'array sul posto e non richiede memoria aggiuntiva, pertanto è più efficiente in termini di memoria rispetto all'implementazione di merge sort.
Quindi la mia domanda è: c'è un modo migliore per implementare un ordinamento rapido che vale la pena provare? Se migliorerò l'implementazione rapida dell'ordinamento e eseguire più test su set di dati diversi (calcolando la media dei tempi di esecuzione su set di dati diversi) mi aspetto una prestazione migliore di ordinamento rapido per l'unione di ordinamento?
Modifica
Grazie per le tue risposte.
La mia implementazione è a posto e si basa sullo pseudo-codice che ho trovato su wikipedia nella Sezione Versione sul posto :
function partition(array, 'left', 'right', 'pivotIndex')
in cui scelgo l'ultimo elemento dell'intervallo da ordinare come pivot, ovvero pivotIndex: = right. Ho controllato il codice più e più volte e mi sembra corretto. Per escludere il caso che sto usando l'implementazione sbagliata Ho caricato il codice sorgente su github (nel caso desiderassi per dargli un'occhiata)
Le tue risposte sembrano suggerire che sto usando i dati di test sbagliati. Lo esaminerò e proveremo diversi set di dati di test. Riferirò non appena avrò dei risultati.