Shelli sortimine

**Shelli sortimine** (ka **väheneva sammu meetod**) on sortimisalgoritm, mis töötab kohapeal (s.t. ei vaja abiandmestruktuure) ja põhineb elementide võrdlusel. Shelli sortimisalgoritmi vaadeldakse tavaliselt pistemeetodi variatsioonina, kus võrreldakse ja vahetatakse ka üksteisest kaugel asuvaid elemente.

Algoritmi käigus sorditakse esmalt elemendid, mis asuvad üksteisest suure fikseeritud sammu kaugusel, seejärel vähendatakse järk-järgult sorditavate elementide vahelist sammu. Suure sammuga alustamine võimaldab mõned elemendid viia oma õigele positsioonile kiiremini kui ainult kõrvuti asuvate elementide vahetamine.

Algoritmi esimese versiooni avaldas Donald Shell 1959. aastal.

## Kirjeldus
Shelli sortimise idee seisneb loendi elementide ümberpaigutamises nii, et võttes mistahes positsioonist alustades iga h-nda elemendi, saadakse sorditud alamloend. Sel juhul öeldakse, et loend on h-sorditud. Suurte h väärtustega alustamine võimaldab liigutada elementide kaugetele positsioonidele, vähendades kiiresti valejärjestusi. Kui loend on k-sorditud mingi täisarvu k 1, *a*6, *a*11), (*a*2, *a*7, *a*12), (*a*3, *a*8), (*a*4, *a*9), (*a*5, *a*10). Näiteks on alamloend (*a*1, *a*6, *a*11) ehk (62, 17, 25) pärast sortimist (17, 25, 62). Järgmisel läbimisel sorditakse alamloendid (*a*1, *a*4, *a*7, *a*10), (*a*2, *a*5, *a*8, *a*11), (*a*3, *a*6, *a*9, *a*12). Viimasel läbimisel sorditakse kogu loend (*a*1,..., *a*12) tavalise pistemeetodiga.

Näide illustreerib, kuidas Shelli sortimisel on alamloendid algul lühikesed ja hiljem pikad, kuid peaaegu sorditud. Mõlemal juhul töötab tavaline pistemeetod efektiivselt.

Shelli sortimine ei ole stabiilne: võrdsete elementide korral võib nende omavaheline järjestus tulla erinev. Shelli sortimisalgoritm on adaptiivne ehk selle tööaeg on lühem loenditel, mis on juba osaliselt sorditud.

## Pseudokood
Koodis on kasutatakse Marcin Ciura sammude jada ja sisemise sortimismeetodina pistemeetodit.

```c
# Olgu sorditavaks loendiks a[0...n-1].
gaps = [701, 301, 132, 57, 23, 10, 4, 1]

# Alustame suurima sammuga ja lõpetame sammuga 1.
foreach (gap in gaps)
{
    # Sordime üksteisest käesoleva sammu kaugusel asuvad elemendid.
    # Esimese sammu elemendid a[0..gap-1] on juba sammu järgi sorditud.
    # Lisame ühe elemendi kaupa, kuni kogu loend on sammu järgi sorditud.
    for (i = gap; i < n; i += 1)
    {
        # Lisame a[i] sammuga sorditud elementide hulka.
        # Salvestame a[i] abimuutujasse temp, et vabastada positsioon i.
        temp = a[i]
        # Nihutame varasemaid sammuga sorditud elemente, kuni leiame a[i] õige positsiooni.
        for (j = i; j >= gap and a[j - gap] > temp; j -= gap)
        {
            a[j] = a[j - gap]
        }
        # Paneme abimuutuja (originaalse a[i]) õigele positsioonile.
        a[j] = temp
    }
}
```


## Sammujadad
Shelli sortimisalgoritmi ajaline keerukus sõltub valitud sammupikkuste jadast. Tõestatult parimat võimalikku sammujada ei ole leitud. Iga sammujada, mis lõpeb sammuga 1, annab Shelli sortimise tulemuseks korrektselt sorditud loendi.

Alljärgnev tabel võrdleb levinumaid seni avaldatud sammujadasid. Mõned neist on kasvavad lõpmatud jadad, mille elemendid, mis on väiksemad kui *N*, tuleb võtta vastupidises (kahanevas) järjekorras.

: 

Kui arvu *N* kahendesituses esineb palju järjestikuseid nulle, tehakse sortimisel Shelli originaalse sammujadaga halvimal juhul Θ(*N*2) võrdlust. Selline juht ilmneb näiteks siis, kui *N* on võrdne arvu 2 mingi astmega ja mediaanist suuremad elemendid on paaritutel positsioonidel ning väiksemad paaris positsioonidel, kuna siis võrreldakse neid alles viimasel läbimisel.

Keskmise võrdluste arvu poolest on teadaolevalt parima jõudlusega Ciura jada, kus sammupikkused üle 701 saab välja arvutada üldvalemiga h_k = \lfloor 2.25 h_{k-1} \rfloor.

## Rakendused

Shelli sortimine teeb rohkem võrdlusi ja sellel on suurem vahemälu möödalask kui kiirsortimisel, kuid selle implementatsioon on lühike ja see ei kasuta kutsepinu. Seetõttu on Shelli sortimine kasutusel mõnes qsort funktsiooni implementatsioonis C standardteegis, mis on suunatud manussüsteemidele. Sarnastel põhjustel oli Shelli sortimine varem kasutusel Linuxi kernelis. Shelli sortimist võib kasutada introsort algoritmis, et sortida lühikesi alamloendeid ja vältida algoritmi aeglustumist, kui rekursioon saavutab teatud sügavuse. Sel põhimõttel töötab näiteks bzip2 kompressor.

## Viited