Tietojenkäsittelytieteessä **hajautustaulu** (engl. *hash table*) on hakurakenne eli avaimia arvoihin yhdistävä tietorakenne. Kun hajautustaululle annetaan avain (esimerkiksi henkilön nimi), se kertoo arvon (puhelinnumero). Sisäisesti se käyttää avaimesta hajautusfunktion avulla muodostettua tiivistelukua taulukon indeksinä. Siten haku toimii hyvin tehokkaasti, mutta alkioiden järjestykseen liittyvät operaatiot kuten suurimman arvon etsiminen eivät. Tyypillisesti hajautustaulua käytetään, kun halutaan indeksoida taulukkoa merkkijonoilla kokonaislukujen sijasta.

Puhelinluettelo hajautustauluna, jossa ei ole törmäyksiä.

Suoritusaika
------------

Tehokkuudeltaan hajautustaulu muistuttaa järjestämätöntä taulukkoa. Sen tärkein toiminto on haku, jonka asymptoottinen aikavaativuus on keskimäärin {\displaystyle O(1)}; lisäys ja poisto ovat samaa luokkaa. Harvinainen pahin tapaus voi olla {\displaystyle O(n)}, mutta tämä voidaan estää, jos tallennettava data tunnetaan ennalta. Tehokkuus riippuu täysin törmäysten määrästä; se puolestaan riippuu taulukon täyttöasteesta, hajautusfunktion tuottamasta jakaumasta ja törmäysten käsittelykeinosta.

Koska haku hajautustaulusta ei yleensä vaadi kuin yhden tai kaksi yhtäsuuruusvertailuoperaatiota, se on huomattavasti hakupuita nopeampi, jos avaimien vertailu vie paljon aikaa.

Hajautustaulun yleisten operaatioiden keskimääräisiä aikavaativuuksia:

|  |  |
| --- | --- |
| Haku | {\displaystyle O(1)} |
| Lisäys | {\displaystyle O(1)} |
| Poisto | {\displaystyle O(1)} |
| Suurin tai pienin arvo | {\displaystyle O(n)} |
| Arvojen käsittely järjestyksessä | {\displaystyle O(n\log n)} |
|

Hajautustaulun käyttökohteet
----------------------------

Usein hajautustaulu sopii hakurakenteeksi, jos

* on helppoa keksiä funktio, joka muuttaa avaimen lukuarvoksi
* järjestykseen liittyviä operaatioita ei tarvita
* aikavaativuudelle ei ole täysin ehdotonta ylärajaa
* voidaan hyväksyä ylimääräinen tilan tarve
* avainten vertailu vie aikaa

Jokin hakupuu kuten punamusta puu voi olla hyvä vaihtoehto toteuttaa hakurakenne.

Toteutus
--------

Oletetaan, että avaimina käytetään merkkijonoja ja arvoina kokonaislukuja, kuten puhelinluettelossa. Tavoitteena on, että hajautustaulua voisi käyttää kuten assosiaatiotaulua:

```
HashTable T := **new** HashTable(50)
set(T, "Pekka Virtanen", 091231231)
int v := lookup(T, "Pekka Virtanen")
```

### Tietosisältö

Tietorakenteen ytimenä on taulukko, johon avain-arvo -parit säilötään.

```
**record** Pair
    string key
    int value
**end record**

**record** HashTable
    int size
    Pair[] array
**end record**
```

### Hajautusfunktio

Hajautusfunktio tiivistää merkkijonoja taulukon indekseiksi eli muodostaa suurista arvoista pieniä satunnaislukuja. Ensin merkkijono täytyy muuttaa kokonaisluvuksi; yksinkertainen mutta melko huono tapa on laskea merkkikoodit yhteen. Tuloksena on luku, joka vielä täytyisi saada pysymään taulukon koon rajoissa. Tämä onnistuu esimerkiksi jakojäännöksen avulla.

```
**function** hashf(HashTable T, string key)
    **return** sum(key) **mod** T.size
**end function**
```

### Törmäykset

Eri avaimilla hajautusfunktio voi antaa saman indeksin. Funktiolla *hashf* avaimet "venes" ja "seven" tuottavat saman indeksin, koska anagrammien merkkikoodien summa on aina sama. Tätä kutsutaan törmäykseksi, ja sen käsittelyyn on useita keinoja, joista kaikki käyttävät jonkinlaista peräkkäishakua. Siten törmäykset hidastavat hajautustaulua, ja hajautusfunktion tulisikin kuvata avaimia indekseiksi mahdollisimman tasaisesti.

Taulukolle on arvioitava sopiva koko, sillä ylimääräinen tila vähentää törmäyksiä mutta vie muistia. Alkioiden ja taulukon koon välistä suhdetta kutsutaan *täyttösuhteeksi*, ja se yritetään pitää sopivana.

### Ylivuotolista

Törmäys, joka on käsitelty ylivuotolistan avulla. Sandra Dee sijoitetaan listan jatkoksi.
Ilmeinen tapa hallita törmäyksiä on törmäävien arvojen tallentaminen samaan indeksiin linkitetyn listan avulla. Ratkaisua kutsutaan ylivuotolistaksi tai -ketjuksi. Ylivuotolista on suotuisa tapa käsitellä törmäyksiä. Tietorakenteen koko voi kasvaa taulukkoa suuremmaksi ilman merkittävää hidastumista. Muisti ja hidastuminen ovat kapasiteetin ainoat rajat. Kuitenkaan käytännössä välimuisti ei kiihdytä linkitystä niin hyvin kuin puhdasta taulukkototeutusta, avointa hajautusta.

#### Esimerkkikoodia

```
**record** HashTable
    int size
    PairList[] array
**end record**

**procedure** set(HashTable T, string key, int value)
    int hash := hashf(T, key)
    **if exists** Pair p **in** T.array[hash] **satisfying** p.key = key
       p.value := value
    **else**
       listInsert(T.array[hash], **new** Pair(key,value))
    **end if**
**end procedure**

**function** lookup(HashTable T, string key)
    int hash := hashf(T, key)
    **if exists** Pair p **in** T.array[hash] **satisfying** p.key = key
       **return** p.value
    **else**
       **return** **null**
    **end if**
**end function**
```

### Avoin hajautus

Törmäys, joka on käsitelty lineaarisen kokeilun avulla. Sandra Dee sijoitetaan seuraavaan vapaaseen indeksiin.
Avoimessa hajautuksessa vuodetaan törmääviä alkioita suoraan johonkin taulukon vapaaseen paikkaan. Intuitiivisin tapa toteuttaa avoin hajautus lienee **lineaarinen kokeilu**, jossa kokeillaan peräkkäin vapaita taulukkopaikkoja, kunnes vapaa paikka löytyy tai taulu on kierretty ympäri.

Lineaarinen kokeilu ei yleensä ole hyvä vaihtoehto, sillä se kasaa alkioita peräkkäin, jolloin kokeileminen hidastuu. Vaikka peräkkäisyys käyttää tehokkaasti välimuistia, parempia algoritmeja ovat **neliöinen kokeilu** ja **kaksoishajautus**, jotka hyppivät epätasaisia loikkia taulukkoa ympäri.

On selvää, että taulukon koolla on suuri merkitys avoimessa hajautuksessa: Kapasiteetilla on konkreettinen yläraja, ja operaatioiden ajantarve kasvaa räjähdysmäisesti, kun taulukko alkaa tulla täyteen.

#### Esimerkkikoodia

Apufunktio *findIndex* etsii avaimelle seuraavan tyhjän paikan käyttäen lineaarista kokeilua.

```
**function** findIndex(HashTable T, string key)
    int hash := hashf(T, key)
    int i := hash
    **while** T.array[i] ≠ **null** **and** T.array[i].key ≠ key
        i := (i + 1) **mod** T.size
        **if** i = hash **then** **error** "taulukko on täynnä"
    **repeat**
    **return** i
**end function**

**procedure** set(HashTable T, string key, int value)
    int i := findIndex(T, key)
    **if** T.array[i] = **null**
        T.array[i] := **new** Pair(key,value)
    **else**
        T.array[i].value := value
**end procedure**

**function** lookup(HashTable T, string key)
    int i := findIndex(T, key)
    **if** T.array[i] = **null**
        **return** not found
    **else**
        **return** T.array[i].value
**end function**
```

### Taulukon koon muuttaminen

Taulukon pidentäminen vaatii uuden hajautusfunktion, mikä käytännössä tarkoittaa uuden hajautustaulun luomista. Tämä on raskas operaatio, joten sitä tulee välttää. Yksi keino saada pidennysväli riittävän laajaksi on kaksinkertaistaa pituus aina, kun taulukkoa tarvitsee pidentää. Tällöin hidas hajautustaulun kopiointioperaatio ei keskimäärin juuri vaikuta tehokkuuteen.

Ylivuotolistoilla toteutettu hajautustaulu ei välttämättä tarvitse pidennysoperaatiota. Se vain hidastuu tasaisesti, kun alkioita tulee lisää.

Lähteet
-------

* Matti Luukkainen ja Matti Nykänen: 58131: Tietorakenteet  8. tammikuuta 2007. Helsingin yliopisto.  Arkistoitu 13.6.2007. Viitattu 30. maaliskuuta 2007.
* Thomas Cormen, Charles Leiserson, Ronald Rivest ja Clifford Stein: *Introduction to Algorithms*, s. 221–252. MIT Press ja McGraw-Hill, 2001. ISBN 0-262-03293-7.