Tietorakenteet ja algoritmit

syksy 2024

Lisäysjärjestäminen

Lisäysjärjestäminen (insertion sort) on yksinkertainen järjestämisalgoritmi, jonka aikavaativuus on \(O(n^2)\).

Algoritmi käy läpi listan kohdat vasemmalta oikealle. Jokaisessa listan kohdassa algoritmi siirtää kyseisessä kohdassa olevaa alkiota sopivan määrän vasemmalle niin, että listan alkuosa kyseiseen kohtaan asti on järjestyksessä. Kun algoritmi on käynyt läpi kaikki listan kohdat, koko lista on järjestyksessä.

Esimerkki

Seuraava kuva näyttää, miten lisäysjärjestäminen järjestää listan \([5,2,4,2,6,1]\).

Toteutus

Lisäysjärjestäminen voidaan toteuttaa seuraavasti Pythonilla:

def swap(items, a, b):
    temp = items[a]
    items[a] = items[b]
    items[b] = temp

def insertion_sort(items):
    for i in range(1, len(items)):
        for j in range(i - 1, -1, -1):
            if items[j] > items[j + 1]:
                swap(items, j, j + 1)
            else:
                break
        
numbers = [5, 2, 4, 2, 6, 1]
insertion_sort(numbers)
print(numbers) # [1, 2, 2, 4, 5, 6]

Funktio insertion_sort järjestää sille annetun listan lisäysjärjestämisen avulla. Funktiossa on kaksi sisäkkäistä silmukkaa, joista ensimmäinen käy läpi listan kohdat vasemmalta oikealle ja toinen siirtää käsiteltävän alkion oikealle paikalle listan alkuosassa. Käytössä on apufunktio swap, joka vaihtaa keskenään kaksi listassa olevaa alkiota niiden kohtien perusteella.

Huomaa, että funktion insertion_sort tarkoituksena on havainnollistaa, miten lisäysjärjestäminen toimii, eikä antaa esimerkkiä siitä, miten järjestäminen kannattaa toteuttaa Python-ohjelmoinnissa. Funktion insertion_sort sijasta järjestämiseen kannattaa käyttää Pythonin valmiita menetelmiä (sort tai sorted).

Tehokkuus

Lisäysjärjestäminen vie aikaa \(O(n^2)\), koska se muodostuu kahdesta sisäkkäisestä silmukasta. Pahin tapaus algoritmille on käänteisessä järjestyksessä oleva lista, jossa jokainen alkio täytyy siirtää listan alkuun askel kerrallaan.

Lisäysjärjestämisen tehokkuutta kuvaa tarkemmin listan inversioiden määrä. Lukupari \((a,b)\) on inversio, jos \(a<b\) ja listan alkiot kohdissa \(a\) ja \(b\) ovat väärässä järjestyksessä. Esimerkiksi listassa \([5,2,4,2,6,1]\) inversiot ovat \((0,1)\), \((0,2)\), \((0,3)\), \((0,5)\), \((1,5)\), \((2,3)\), \((2,5)\), \((3,5)\) ja \((4,5)\).

Jokainen lisäysjärjestämisen tekemä kahden alkion vaihto (koodissa funktion swap kutsuminen) poistaa listasta yhden inversion. Niinpä lisäysjärjestämisen vaihtojen määrä on yhtä suuri kuin listan inversioiden määrä. Kun lista on käänteisessä järjestyksessä, inversioiden määrä on \(n(n-1)/2\) eli luokkaa \(n^2\).

Jos järjestämisalgoritmi vaihtaa aina kahden vierekkäin olevan alkion järjestyksen, algoritmin aikavaativuus ei voi olla parempi kuin \(O(n^2)\). Syynä tähän on, että inversioiden määrä voi olla luokkaa \(n^2\) ja jokainen vaihto poistaa vain yhden inversion. Tehokkaat järjestämisalgoritmit pystyvät siirtämään alkioita tehokkaammin kuin vaihtamalla keskenään vierekkäisiä alkioita.