Shellsort - Shellsort
![]() Shellsort con spazi 23, 10, 4, 1 in azione
| |
| Classe | Algoritmo di ordinamento |
|---|---|
| Struttura dati | Vettore |
| Prestazioni nel caso peggiore | O( n 2 ) (sequenza di gap peggiore nota nel caso peggiore) O( n log 2 n ) (sequenza di gap peggiore nota nel caso peggiore) |
| Best-caso le prestazioni | O( n log n ) (la maggior parte delle sequenze gap) O( n log 2 n ) (sequenza gap più nota nel caso peggiore) |
| Prestazioni medie | dipende dalla sequenza di spazi |
| Complessità dello spazio nel caso peggiore | О( n ) totale, O(1) ausiliario |
Shellsort , noto anche come Shell sort o Shell's method , è un ordinamento di confronto sul posto . Può essere visto come una generalizzazione dell'ordinamento per scambio ( bubble sort ) o dell'ordinamento per inserimento ( inserimento ordinamento ). Il metodo inizia ordinando coppie di elementi distanti tra loro, quindi riducendo progressivamente il divario tra gli elementi da confrontare. Partendo da elementi distanti, può spostare alcuni elementi fuori posto in posizione più velocemente di un semplice scambio di vicini più vicini. Donald Shell ha pubblicato la prima versione di questo tipo nel 1959. Il tempo di esecuzione di Shellsort dipende fortemente dalla sequenza di spazi che utilizza. Per molte varianti pratiche, la determinazione della loro complessità temporale rimane un problema aperto .
Descrizione
Shellsort è un'ottimizzazione dell'ordinamento per inserzione che consente lo scambio di elementi distanti tra loro. L'idea è di organizzare l'elenco degli elementi in modo che, partendo da qualsiasi punto, prendendo ogni h- esimo elemento si produca un elenco ordinato. Si dice che tale elenco è h -sorted. Può anche essere pensato come h elenchi interlacciati, ciascuno ordinato individualmente. Iniziare con grandi valori di h consente agli elementi di spostarsi a lunghe distanze nell'elenco originale, riducendo rapidamente grandi quantità di disordine e lasciando meno lavoro da eseguire per passaggi h -sort più piccoli . Se l'elenco è poi k-ordinato per qualche intero più piccolo k , allora l'elenco rimane h- ordinato. Seguendo questa idea per una sequenza decrescente di valori h che terminano con 1 è garantito che alla fine lascerà un elenco ordinato.
In termini semplicistici, questo significa che se abbiamo un array di 1024 numeri, il nostro primo intervallo ( h ) potrebbe essere 512. Quindi esaminiamo l'elenco confrontando ogni elemento nella prima metà con l'elemento nella seconda metà. Il nostro secondo gap ( k ) è 256, che suddivide l'array in quattro sezioni (a partire da 0,256,512,768) e ci assicuriamo che i primi elementi in ogni sezione siano ordinati l'uno rispetto all'altro, quindi il secondo elemento in ogni sezione e così via . In pratica la sequenza dei gap potrebbe essere qualsiasi cosa, ma l'ultimo gap è sempre 1 per terminare l'ordinamento (in effetti terminando con un normale ordinamento per inserimento).
Di seguito è mostrato un esempio di esecuzione di Shellsort con i gap 5, 3 e 1.
| un 1 | un 2 | un 3 | un 4 | un 5 | un 6 | un 7 | un 8 | un 9 | un 10 | un 11 | un 12 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Dati in ingresso | 62 | 83 | 18 | 53 | 07 | 17 | 95 | 86 | 47 | 69 | 25 | 28 |
| Dopo 5 smistamenti | 17 | 28 | 18 | 47 | 07 | 25 | 83 | 86 | 53 | 69 | 62 | 95 |
| Dopo 3 smistamenti | 17 | 07 | 18 | 47 | 28 | 25 | 69 | 62 | 53 | 83 | 86 | 95 |
| Dopo 1 ordinamento | 07 | 17 | 18 | 25 | 28 | 47 | 53 | 62 | 69 | 83 | 86 | 95 |
Il primo passaggio, 5-ordinamento, esegue l'ordinamento per inserimento su cinque sottoarray separati ( a 1 , a 6 , a 11 ), ( a 2 , a 7 , a 12 ), ( a 3 , a 8 ), ( a 4 , a 9 ), ( a 5 , a 10 ). Ad esempio, cambia il sottoarray ( a 1 , a 6 , a 11 ) da (62, 17, 25) a (17, 25, 62). Il passaggio successivo, 3-ordinamento, esegue l'ordinamento per inserzione sui tre sottoarray ( a 1 , a 4 , a 7 , a 10 ), ( a 2 , a 5 , a 8 , a 11 ), ( a 3 , a 6 , un 9 , un 12 ). L'ultimo passaggio, 1-sorting, è un normale ordinamento di inserimento dell'intero array ( a 1 ,..., a 12 ).
Come illustra l'esempio, i sottoarray su cui opera Shellsort sono inizialmente brevi; poi sono più lunghe ma quasi ordinate. In entrambi i casi l'ordinamento per inserimento funziona in modo efficiente.
Shellsort non è stabile : può cambiare l'ordine relativo degli elementi con valori uguali. È un algoritmo di ordinamento adattivo in quanto viene eseguito più velocemente quando l'input è parzialmente ordinato.
Pseudocodice
Usando la sequenza di spazi vuoti di Marcin Ciura, con un ordinamento di inserimento interno.
# Sort an array a[0...n-1].
gaps = [701, 301, 132, 57, 23, 10, 4, 1] // Ciura gap sequence
# Start with the largest gap and work down to a gap of 1
foreach (gap in gaps)
{
# Do a gapped insertion sort for this gap size.
# The first gap elements a[0..gap-1] are already in gapped order
# keep adding one more element until the entire array is gap sorted
for (i = gap; i < n; i += 1)
{
# add a[i] to the elements that have been gap sorted
# save a[i] in temp and make a hole at position i
temp = a[i]
# shift earlier gap-sorted elements up until the correct location for a[i] is found
for (j = i; j >= gap and a[j - gap] > temp; j -= gap)
{
a[j] = a[j - gap]
}
# put temp (the original a[i]) in its correct location
a[j] = temp
}
}
sequenze gap
La questione di decidere quale sequenza di spazi utilizzare è difficile. Ogni sequenza di spazi che contiene 1 produce un ordinamento corretto (dato che questo rende il passaggio finale un normale ordinamento per inserzione); tuttavia, le proprietà delle versioni di Shellsort così ottenute possono essere molto diverse. Troppi spazi vuoti rallentano i passaggi e troppi spazi generano un sovraccarico.
La tabella seguente mette a confronto la maggior parte delle sequenze di gap proposte pubblicate finora. Alcuni di essi hanno elementi decrescenti che dipendono dalla dimensione dell'array ordinato ( N ). Altri stanno aumentando le sequenze infinite, i cui elementi minori di N dovrebbero essere usati in ordine inverso.
| OEIS | Termine generale ( k ≥ 1) | Lacune concrete | Complessità temporale nel caso peggiore |
Autore e anno di pubblicazione |
|---|---|---|---|---|
| [es. quando N = 2 p ] | Conchiglia , 1959 | |||
| Frank & Lazzaro, 1960 | ||||
| A000225 | Hibbard , 1963 | |||
| A083318 | , preceduto da 1 | Papernov & Stasevich, 1965 | ||
| A003586 | Numeri successivi della forma ( 3 numeri lisci ) | Pratt , 1971 | ||
| A003462 | , non maggiore di | Knuth , 1973, basato su Pratt , 1971 | ||
| A036569 | Incerpi & Sedgewick , 1985, Knuth | |||
| A036562 | , preceduto da 1 | Sedgewick, 1982 | ||
| A033622 | Sedgewick, 1986 | |||
| Sconosciuto | Gonnet & Baeza-Yates , 1991 | |||
| A108870 | Sconosciuto | Tokuda, 1992 | ||
| A102549 | Sconosciuto (derivato sperimentalmente) | Sconosciuto | Ciura, 2001 |
Quando la rappresentazione binaria di N contiene molti zeri consecutivi, Shellsort utilizzando la sequenza gap originale di Shell effettua confronti Θ( N 2 ) nel caso peggiore. Ad esempio, questo caso si verifica per N pari a una potenza di due quando elementi maggiori e minori della mediana occupano rispettivamente posizione pari e dispari, poiché vengono confrontati solo nell'ultimo passaggio.
Sebbene abbia una complessità maggiore rispetto a O ( N log N ) che è ottimale per gli ordinamenti di confronto, la versione di Pratt si presta a reti di ordinamento e ha la stessa complessità di porta asintotica del sorter bitonico di Batcher .
Gonnet e Baeza-Yates hanno osservato che Shellsort effettua in media il minor numero di confronti quando i rapporti dei gap successivi sono approssimativamente uguali a 2,2. Questo è il motivo per cui la loro sequenza con rapporto 2,2 e la sequenza di Tokuda con rapporto 2,25 si dimostrano efficienti. Tuttavia, non si sa perché sia così. Sedgewick consiglia di utilizzare spazi che hanno divisori comuni massimi bassi o sono coprimi a due a due .
Rispetto al numero medio di confronti, la sequenza di Ciura ha le prestazioni più note; le lacune da 701 non sono state determinate ma la sequenza può essere ulteriormente estesa secondo la formula ricorsiva .
La sequenza di Tokuda, definita dalla semplice formula , dove , , può essere consigliata per applicazioni pratiche.
Se la dimensione massima dell'input è piccola, come può accadere se Shellsort viene utilizzato su piccoli sottoarray da un altro algoritmo di ordinamento ricorsivo come quicksort o merge sort , allora è possibile tabulare una sequenza ottimale per ogni dimensione di input.
Complessità computazionale
Vale la seguente proprietà: dopo h 2 -sorting di qualsiasi array h 1 -sorted, l'array rimane h 1 -sorted. Ogni array h 1 ordinato e h 2 ordinato è anche ( a 1 h 1 + a 2 h 2 ) ordinato, per tutti gli interi non negativi a 1 e a 2 . La complessità del caso peggiore di Shellsort è quindi connessa con il problema di Frobenius : dati interi h 1 ,..., h n con mcd = 1, il numero di Frobenius g ( h 1 ,..., h n ) è il massimo intero che non può essere rappresentato come a 1 h 1 + ... + a n h n con intero non negativo a 1 ,..., a n . Utilizzando formule note per i numeri di Frobenius, possiamo determinare la complessità del caso peggiore di Shellsort per diverse classi di sequenze gap. I risultati provati sono mostrati nella tabella sopra.
Rispetto al numero medio di operazioni, nessuno dei risultati provati riguarda una pratica gap sequence. Per gap che sono potenze di due, Espelid ha calcolato questa media come . Knuth ha determinato la complessità media dell'ordinamento di un array di N elementi con due spazi ( h , 1) come . Ne consegue che uno Shellsort a due passaggi con h = ( N 1/3 ) effettua in media O ( N 5/3 ) confronti/inversioni/tempo di esecuzione. Yao ha trovato la complessità media di uno Shellsort a tre passaggi. Il suo risultato è stato affinato da Janson e Knuth: il numero medio di confronti/inversioni/tempo di esecuzione effettuati durante uno Shellsort con tre gap ( ch , cg , 1), dove h e g sono coprimi, è nel primo passaggio, nel secondo passaggio e nel terzo passaggio. ψ ( h , g ) nell'ultima formula è una funzione complicata asintoticamente uguale a . In particolare, quando h = Θ( N 7/15 ) eg = Θ( N 1/5 ), il tempo medio di smistamento è O ( N 23/15 ).
Sulla base di esperimenti, si ipotizza che Shellsort con la sequenza gap di Hibbard funzioni in un tempo medio O ( N 5/4 ) e che la sequenza di Gonnet e Baeza-Yates richieda in media 0,41 N ln N (ln ln N + 1/6 ) elemento si sposta. Le approssimazioni del numero medio di operazioni precedentemente proposte per altre sequenze falliscono quando gli array ordinati contengono milioni di elementi.
Il grafico sottostante mostra il numero medio di confronti di elementi in varie varianti di Shellsort, diviso per il limite inferiore teorico, ovvero log 2 N !, dove la sequenza 1, 4, 10, 23, 57, 132, 301, 701 è stata estesa secondo la formula .
Applicando la teoria della complessità di Kolmogorov , Jiang, Li e Vitányi hanno dimostrato il seguente limite inferiore per l'ordine del numero medio di operazioni/tempo di esecuzione in uno Shellsort p- pass: Ω( pN 1+1/ p ) quando p log 2 N e Ω( pN ) quando p > log 2 N . Pertanto, Shellsort ha prospettive di esecuzione in un tempo medio che cresce asintoticamente come N log N solo quando si utilizzano sequenze di gap il cui numero di gap cresce in proporzione al logaritmo della dimensione dell'array. Tuttavia, non è noto se Shellsort possa raggiungere questo ordine asintotico di complessità del caso medio, che è ottimale per gli ordinamenti di confronto. Il limite inferiore è stato migliorato da Vitányi per ogni numero di passaggi fino a dove . Questo risultato implica, ad esempio, il limite inferiore di Jiang-Li-Vitányi per tutte le sequenze di incremento -pass e migliora tale limite inferiore per particolari sequenze di incremento. Infatti tutti i limiti (inferiore e superiore) attualmente noti per il caso medio sono esattamente abbinati a questo limite inferiore. Ad esempio, questo dà il nuovo risultato che il limite superiore Janson-Knuth è abbinato al limite inferiore risultante per la sequenza di incremento utilizzata, mostrando che Shellsort a tre passaggi per questa sequenza di incremento utilizza confronti/inversioni/tempo di esecuzione. La formula ci permette di cercare sequenze di incremento che producono limiti inferiori sconosciuti; ad esempio una sequenza di incremento per quattro passaggi che ha un limite inferiore maggiore rispetto alla sequenza di incremento . Il limite inferiore diventa
La complessità nel peggiore dei casi di qualsiasi versione di Shellsort è di ordine superiore: Plaxton, Poonen e Suel hanno dimostrato che cresce almeno alla stessa velocità di .
Applicazioni
Shellsort esegue più operazioni e ha un tasso di errore della cache più elevato rispetto a Quicksort . Tuttavia, poiché può essere implementato utilizzando poco codice e non utilizza lo stack di chiamate , alcune implementazioni della funzione qsort nella libreria standard C destinata ai sistemi embedded la utilizzano invece di Quicksort. Shellsort è, ad esempio, utilizzato nella libreria uClibc . Per ragioni simili, in passato, nel kernel Linux veniva utilizzato Shellsort .
Shellsort può anche fungere da sotto-algoritmo di ordinamento introspettivo , per ordinare brevi sottoarray e per prevenire un rallentamento quando la profondità di ricorsione supera un determinato limite. Questo principio è impiegato, ad esempio, nel compressore bzip2 .
Guarda anche
Riferimenti
Bibliografia
- Knuth, Donald E. (1997). "Metodo Shell". L'arte della programmazione informatica. Volume 3: Ordinamento e ricerca (2a ed.). Reading, Massachusetts: Addison-Wesley. pp. 83-95. ISBN 978-0-201-89685-5.
- Analisi di Shellsort e algoritmi correlati , Robert Sedgewick, Quarto Simposio europeo sugli algoritmi, Barcellona, settembre 1996.
link esterno
- Algoritmi di ordinamento animati: Shell Sort alla Wayback Machine (archiviato il 10 marzo 2015) - dimostrazione grafica
- Shellsort con spazi 5, 3, 1 come una danza popolare ungherese
