Shellsort - Shellsort

Shellsort
Visualizzazione passo passo di 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
I passi di Shellsort.
Scambiare coppie di elementi in passaggi successivi di Shellsort con spazi 5, 3, 1

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 .

Shell ordina il numero medio di confronti (inglese).svg

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

link esterno