5. Hash-taulut ja binääriset etsintäpuut

Samankaltaiset tiedostot
811312A Tietorakenteet ja algoritmit V Hash-taulukot ja binääriset etsintäpuut

811312A Tietorakenteet ja algoritmit, , Harjoitus 5, Ratkaisu

811312A Tietorakenteet ja algoritmit, , Harjoitus 5, Ratkaisu

4. Perustietorakenteet

811312A Tietorakenteet ja algoritmit IV Perustietorakenteet

Algoritmit 2. Luento 3 Ti Timo Männikkö

Algoritmit 2. Luento 3 Ti Timo Männikkö

Algoritmit 2. Luento 2 To Timo Männikkö

Algoritmit 2. Luento 4 To Timo Männikkö

Algoritmit 2. Luento 2 Ke Timo Männikkö

Algoritmit 2. Luento 4 Ke Timo Männikkö

Hakupuut. tässä luvussa tarkastelemme puita tiedon tallennusrakenteina

811312A Tietorakenteet ja algoritmit Kertausta jälkiosasta

811312A Tietorakenteet ja algoritmit, , Harjoitus 7, ratkaisu

811312A Tietorakenteet ja algoritmit Kertausta jälkiosasta

Algoritmit 1. Luento 7 Ti Timo Männikkö

(a) L on listan tunnussolmu, joten se ei voi olla null. Algoritmi lisäämiselle loppuun:

Algoritmit 2. Luento 7 Ti Timo Männikkö

58131 Tietorakenteet ja algoritmit (kevät 2016) Ensimmäinen välikoe, malliratkaisut

Algoritmit 2. Luento 6 Ke Timo Männikkö

811312A Tietorakenteet ja algoritmit II Perustietorakenteet

AVL-puut. eräs tapa tasapainottaa binäärihakupuu siten, että korkeus on O(log n) kun puussa on n avainta

Algoritmit 1. Luento 6 Ke Timo Männikkö

v 1 v 2 v 3 v 4 d lapsisolmua d 1 avainta lapsen v i alipuun avaimet k i 1 ja k i k 0 =, k d = Sisäsolmuissa vähint. yksi avain vähint.

Algoritmit 1. Luento 12 Ti Timo Männikkö

Algoritmit 1. Luento 12 Ke Timo Männikkö

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

Algoritmit 2. Luento 6 To Timo Männikkö

8. Puna-mustat puut ja tietorakenteiden täydentäminen

Algoritmit 2. Luento 5 Ti Timo Männikkö

Tietorakenteet, laskuharjoitus 7, ratkaisuja

58131 Tietorakenteet ja algoritmit (kevät 2013) Kurssikoe 1, , vastauksia

Algoritmit 2. Luento 5 Ti Timo Männikkö

Algoritmit 2. Demot Timo Männikkö

2. Perustietorakenteet

ALGORITMIT 1 DEMOVASTAUKSET KEVÄT 2012

58131 Tietorakenteet (kevät 2009) Harjoitus 6, ratkaisuja (Antti Laaksonen)

4 Tehokkuus ja algoritmien suunnittelu

Algoritmit 1. Luento 8 Ke Timo Männikkö

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

TKT20001 Tietorakenteet ja algoritmit Erilliskoe , malliratkaisut (Jyrki Kivinen)

Ohjelmoinnin perusteet Y Python

58131 Tietorakenteet (kevät 2008) 1. kurssikoe, ratkaisuja

Algoritmit 1. Luento 10 Ke Timo Männikkö

Datatähti 2019 loppu

Koe ma 1.3 klo salissa A111, koeaika kuten tavallista 2h 30min

Ohjelmoinnin perusteet Y Python

4. Joukkojen käsittely

811312A Tietorakenteet ja algoritmit , Harjoitus 2 ratkaisu

Algoritmit 1. Luento 5 Ti Timo Männikkö

lähtokohta: kahden O(h) korkuisen keon yhdistäminen uudella juurella vie O(h) operaatiota vrt. RemoveMinElem() keossa

Miten käydä läpi puun alkiot (traversal)?

811312A Tietorakenteet ja algoritmit III Lajittelualgoritmeista

811312A Tietorakenteet ja algoritmit, , Harjoitus 3, Ratkaisu

Algoritmit 1. Demot Timo Männikkö

A TIETORAKENTEET JA ALGORITMIT

Ohjelmoinnin perusteet Y Python

Algoritmit 1. Luento 4 Ke Timo Männikkö

Ohjelmoinnin peruskurssi Y1

Pinot, jonot, yleisemmin sekvenssit: kokoelma peräkkäisiä alkioita (lineaarinen järjestys) Yleisempi tilanne: alkioiden hierarkia

Luku 8. Aluekyselyt. 8.1 Summataulukko

811312A Tietorakenteet ja algoritmit V Verkkojen algoritmeja Osa 2 : Kruskalin ja Dijkstran algoritmit

3. Hakupuut. B-puu on hakupuun laji, joka sopii mm. tietokantasovelluksiin, joissa rakenne on talletettu kiintolevylle eikä keskusmuistiin.

1.1 Tavallinen binäärihakupuu

1. (a) Seuraava algoritmi tutkii, onko jokin luku taulukossa monta kertaa:

2. Seuraavassa kuvassa on verkon solmujen topologinen järjestys: x t v q z u s y w r. Kuva 1: Tehtävän 2 solmut järjestettynä topologisesti.

Ohjelmoinnin perusteet Y Python

CS-A1140 Tietorakenteet ja algoritmit

B + -puut. Kerttu Pollari-Malmi

Kierros 4: Binäärihakupuut

Algoritmit 1. Luento 1 Ti Timo Männikkö

Algoritmit 1. Demot Timo Männikkö

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

Tietorakenteet ja algoritmit - syksy

9.3 Algoritmin valinta

Ohjelmoinnin perusteet Y Python

Tietorakenteet, laskuharjoitus 3, ratkaisuja

58131 Tietorakenteet ja algoritmit (kevät 2014) Uusinta- ja erilliskoe, , vastauksia

Tieto- ja tallennusrakenteet

T Syksy 2004 Logiikka tietotekniikassa: perusteet Laskuharjoitus 7 (opetusmoniste, kappaleet )

TKHJ:ssä on yleensä komento create index, jolla taululle voidaan luoda hakemisto

Tehtävän V.1 ratkaisuehdotus Tietorakenteet, syksy 2003

Algoritmit 1. Luento 10 Ke Timo Männikkö

A TIETORAKENTEET JA ALGORITMIT

7. Tasapainoitetut hakupuut

ITKP102 Ohjelmointi 1 (6 op)

Algoritmit 2. Demot Timo Männikkö

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

A ja B pelaavat sarjan pelejä. Sarjan voittaja on se, joka ensin voittaa n peliä.

58131 Tietorakenteet (kevät 2009) Harjoitus 9, ratkaisuja (Antti Laaksonen)

5. Hajautus. Tarkastellaan edelleen sivulla 161 esitellyn joukkotietotyypin toteuttamista

A TIETORAKENTEET JA ALGORITMIT

7.4 Sormenjälkitekniikka

Binäärihaun vertailujärjestys

Kysymyksiä koko kurssista?

Algoritmit 2. Luento 9 Ti Timo Männikkö

18. Abstraktit tietotyypit 18.1

Tarkennamme geneeristä painamiskorotusalgoritmia

Hajautus. operaatiot insert ja search pyritään tekemään erittäin nopeiksi

A TIETORAKENTEET JA ALGORITMIT KORVAAVAT HARJOITUSTEHTÄVÄT 3, DEADLINE KLO 12:00

Transkriptio:

5. Hash-taulut ja binääriset etsintäpuut Tässä osassa käsitellään tietorakenteista hash-taulukot ja binääriset etsintäpuut ja niiden perusalgoritmit. Teoksessa [Cor] käsitellään tässä esitettäviä asioita seuraavasti: hash-taulukot luku 11; binääriset etsintäpuut luku 12. 5.1. Hash-taulukot Monissa sovelluksissa tarvitaan jonkin tyyppistä joukkoa, johon voidaan kohdistaa lisäys- poistoja hakuoperaatioita. Esimerkiksi ohjelmointikielen kääntäjissä käytetään symbolitaulua, jonka avulla ohjelmassa esiintyvät muuttujat liitetään käytettävän kielen tunnisteisiin. Hash-taulukoiden avulla voidaan toteuttaa tehokkaasti tällainen ratkaisu; hash-taulukoinnissa käytetään ns. tiivisteeli hashfunktioita. Hashfunktioilla on muitakin sovelluksia kuten tarkistussumman laskeminen. Oletetaan, että ollaan tallentamassa tietoalkioita, joissa on jokin yksilöllinen avainkenttä, esimerkiksi kokonaisluku väliltä 1,...,N. Tällöin yksi tapa tallentaa alkoita on varata N- paikkainen taulukko T[1,..,N] ja tallentaa tietoalkio (tai osoitin ko. alkioon) paikkaan T[k], mikäli tietoalkion avainkenttä on k. Tällaista menetelmää sanotaan suoraksi osoittamiseksi (directaddressing). Aluksi taulukko alustetaan tyhjillä arvoilla (NIL). Tällöin hakeminen avainkentän perusteella samoin kuin uusien alkioiden poistaminen sekä entisten lisääminen on tehokasta (operaatiot ovat vakioaikaisia). Haettaessa alkiota tarkistetaan, onko taulukkoon avainkenttään tallennettu NIL vai jokin arvo. Poistettaessa ainoastaan kirjoitetaan arvo NIL taulukkoon sopivaan kohtaan. Mutta mikäli mahdollisia avaimia on paljon, tuhlataan tällä tavalla suuri määrä muistia Jos avaimet voivat olla mitä tahansa kokonaislukuja, ei tarpeeksi suurta taulukkoa voida edes varata. Edellämanittu ongelma voidaan kiertää tiivistefunktioita (hashfunktioita, hash functions) ja hash-taulukoita (hash tables) käyttämällä. Tällöin käytetään taulukkoa T[1,..,m], missä m on paljon pienempi kuin mahdollisten tietoalkioiden lukumäärä, lisäksi määritellään funktio h, joka kuvaa avaimet joukkoon {1,...,m}. Nyt alkio, jonka avainkenttä on k tallennetaan taulukon paikkaan T[h(k)]. Koska avaimia on enemmän kuin funktion arvoja, jotkin avaimet kuvautuvat väistämättä samalle arvolle; tätä sanotaan törmäykseksi (collision). Tiivistefunktiot pyritään suunnittelemaan niin, että törmäykset ovat mahdollisimman epätodennäköisiä, mutta törmäysongelmaa ei voi kuitenkaan kokonaan välttää. Törmäyksistä voidaan selvitä ketjutuksella, eli tallentamalla taulukkoon osoitin lineaariseen (linkitettyyn) listaan ja lisätä uudet tietoalkiot aina listan häntään.

T Kaikki avaimet Käytetyt avaimet k 8 k 4 k 8 k 1 k 2 k 7 k 1 k 2 k 3 k 4 k 5 k 7 k 6 k 3 k 5 k 6 Kuva 5.1. Törmäysongelman välttäminen ketjutuksella. Yllä h(k1) = h(k2) = h(k7), h(k4) = h(k8) ja h(k3) = h(k5). Tällöin alkioiden käsittelyssä käytetään listojen käsittelyoperaatioita, jotka ovat tuttuja tietorakenteiden kurssista. Nyt operaatiot eivät enää ole vakioaikaisia, koska vakioajassa löydetään ainoastaan sen listan pää, jossa tietoalkio on tai johon se pitää lisätä tai josta se pitää poistaa. Listan käsittelyoperaatiot ovat tunnetusti lineaarisia listan pituuden suhteen. Oletetaan, että tiivistefunktion arvot jakaantuvat tasaisesti, ts. jos avaimia on N kappaletta ja funktion arvoja m, niin jokaiselle arvolle kuvautuu yhtä monta avainta, eli Nm kappaletta. Jos taulukkoon tallennetaan n alkiota, yhdelle arvolle kuvautuu keskimäärin nm alkiota, eli yhden listan keskimääräinen pituus on nm. Suhdetta nm sanotaan taulukon täyttöasteeksi (load factor). Näin ollen hash-taulukon käsittelyoperaatioiden (haku, lisäys, poisto) keskimääräinen aikakompleksisuus on luokkaa O( 1 n m), koska tiivistefunktion arvon laskeminen on vakioaikainen operaatio, samoin kuin listan pään hakeminen. Listassa on keskimäärin nm alkiota, joten listaoperaatio tehdään keskimäärin ajassa joka on luokkaa O ( n m). Näin saadaan keskimääräinen aikakompleksisuus. Huomaa, että jos tiivistefunktio on huono ja kuvaa kaikki avaimet samalle arvolle, tilanne on sama kuin jos käytettäisiin yhtä linkitettyä listaa tallentamaan kaikki tietoalkiot. Tällöin saataisiin operaatioiden suoritusajan luokaksi O (n). Pohditaan seuraavaksi, minkälainen on hyvä tiivistefunktio. Tämä riippuu voimakkaasti käyttötarkoituksesta. Mikäli tarkoituksena on tehostaa tietoalkioiden tallennusta ja hakua, eikä avainkentän arvojen jakaumaa tunneta, on järkevää valita funktio, joka kuvaa avaimet keskimäärin tasaisesti arvojoukolle. Tietoturvasovelluksissa tällainen järjestely ei yleensä ole tarkoituksenmukainen, vaan on ensisijaisen tärkeää minimoida törmäykset ja estää avainkentän arvaaminen, kun tiiviste on tiedossa. Tällainen tapaus voisi olla esimerkiksi pääsynvalvonta: jotta käyttäjien salasanoja ei tarvitsisi tallentaa, tallennetaan ainoastaan salasanaa vastaava tiiviste ja

verrataan tätä salasanasta laskettuun tiivisteeseen. Tällöin tietenkin pitää tiivistettä vastaavan salasanan olla erittäin vaikeasti keksittävissä, mikäli tiiviste joutuu hyökkääjän ulottuville. Tarkastellaan nyt kuitenkin tietoalkioiden tehokasta käsittelyä. Voimme olettaa, että avaimet ovat luonnollisia lukuja, ts. lukuja 0,1,2,3,.... Näin ei aseteta rajoitusta, sillä avaimet voidaan aina yksikäsitteisesti koodata luonnollisiksi luvuiksi. Yksinkertainen tiivistefunktio saadaan jakomenetelmällä: Valitaan jokin positiivinen kokonaisluku m ja määritellään tiivistefunktio h( k) k(mod m), ts. funktion arvo on kentän arvon jakojäännös jaettaessa se luvulla m. On vain valittava luku m sopivasti. Ohjelmoinnissa voisi olla houkuttelevaa valita jokin luvun 2 potenssi 2 r, mutta se ei ole yleensä hyvä valinta, koska tällöin tiivistefunktion arvo riippuu vain luvun k alimmista r:stä bitistä. Useimmiten halutaan kuitenkin tiivistefunktion riippuvan luvun kaikista biteistä, ellei olla varmoja, että alimmat r bitin kaikki kuviot ovat yhtä todennäköisiä. Tämän vuoksi usein valitaan luvuksi m jokin alkuluku, joka ei ole kovin lähellä mitään luvun 2 potenssia. Oletetaan, että tallennettavia avaimia on 3000 kappaletta ja voidaan sallia keskimäärin viiden avaimen tallentua samaan listaan: tällöin valitaan jokin alkuluku läheltä lukua 30005 = 600. Nyt 601 olisi sopiva luvun m arvo ja tiivistefunktioksi saataisiin h( k) k (mod 601). Tiivistefunktio voidaan konstruoida myös tulomenetelmällä. Käytetään seuraavaa merkintää: kun x on jokin reaaliluku, x(mod 1) tarkoittaa luvun x desimaaliosaa, esimerkiksi 2.234 (mod 1) 0.234 ja 5.9993 (mod 1) 0. 9993. Tällöin valitaan taas positiivinen kokonaisluku m ja jokin reaalilukuvakio 0 < A < 1 sekä määritellään tiivistefunktio h( k) m ( ka(mod1)). Tämän menetelmän etu on, ettei luvun m arvo vaikuta kriittisesti tiivistystulokseen. Usein p valitaan luku m joksikin luvun 2 potenssiksi; olkoon m 2, missä p on jokin positiivinen kokonaisluku. Tällöin tiivistefunktio voidaan implementoida helposti seuraavalla tavalla, kun tietokoneen sananpituus on w: Valitaan luku s väliltä (0,2 w w ) ja A s 2. Silloin w k s ka 2 r r, w 1 2 0 missä sekä r1 että r0 ovat väliltä (0,2 w ) ja mahtuvat siten yhteen sanaan. Silloin ka 2 w r1 r0, joten ka(mod1) r0 2 w. Siten saadaan p w m ( ka(mod1)) 2 r h k) 2. ( 0

Tiivistefunktion arvo muodostuu täten luvun r0 p:stä eniten merkitsevästä bitistä, kun arvo laajennetaan w-bittiseksi lisäämällä sen eteen riittävä määrä nollia, ks. kuva 4.2. w bittiä k s=a 2 w k s: r 1 r 0 p bittiä = h(k) Kuva 5.2. Tiivistefunktion laskeminen tulomenetelmällä, kun tietokoneen sananpituus on w bittiä. Ellei haluta käyttää ketjutusta, voidaan tallentaa avaimet suoraan ja hallita törmäykset niin, että törmäystilanteessa valitaan uusi, vapaa paikka taulukosta. Tällaisia ratkaisuja kutsutaan avoimen osoittamisen menetelmiksi (open addressing). Yksi tällainen menetelmä on antaa tiivistefunktion riippua avaimen lisäksi toisestakin parametrista, jonka arvot voivat olla 0,...,m-1, kun taulukon koko on m. Tällöin avain k yritetään tallentaa ensiksi paikkaan h(k,0) ja jos tämä on varattu, paikkaan h(k,1), h(k,2) jne. kunnes löytyy vapaa kohta. Menetelmää kutsutaan luotaamiseksi (probing). Luotaamisen hyöty verrattuna tavalliseen taulukkoon tallentamiseen on siinä, että tallentamista ei aloiteta aina taulukon alusta, vaan avaimesta riippuvasta arvosta; lisäksi luotausjono riippuu myös avaimesta k. Näin törmäysten mahdollisuus vähenee. Usein tiivistefunktiolta vaaditaan lisäksi, että luotausjonossa h(k,0),..., h(k,m-1) esiintyvät kaikki taulukon paikat jossain järjestyksessä kaikilla avaimilla k, joten avain saadaan aina lopulta lisättyä taulukkoon ellei taulukko ole täynnä. Kuvatussa järjestelmässä avain haetaan samalla lailla kuin lisätäänkin: luodataan avaimen määräämää jonoa, kunnes avain löytyy tai kohdataan tyhjä arvo (NIL), josta tiedetään, että avain ei esiinny taulukossa. Seuraavat algoritmit toteuttavat nämä operaatiot, oletuksena tiivistefunktio h, jonka luotausjonossa h(k,0),..., h(k,m-1) esiintyvät kaikki taulukon paikat jossain järjestyksessä kaikilla avaimilla k.

Syöte: Taulukko T[1,..,m], m >= 1, lisättävä avain k Tulostus: Taulukon indeksi, johon avain lisätään tai arvo OVERFLOW jos taulukko oli täynnä. HASH_TALLENNA(T,k) 1. i = 0 2. while i<m 3. j = h(k,i) 4. if T[j]==NIL 5. T[j]=k 6. return j 7. i = i+1 8. return OVERFLOW Syöte: Taulukko T[1,..,m], m >= 1, etsittävä avain k Tulostus: Taulukon indeksi, josta avain löydetään tai arvo NIL jos avain ei ole taulukossa. HASH_ETSI(T,k) 1. i = 0 2. j = h(k,i) 3. while (i<m && T[j]!=NIL) 4. if T[j]==k 5. return j 6. i = i+1 7. if i<m 8. j = h(k,i) 9. return NIL Avaimen poisto on hieman monimutkaisempi operaatio, koska poistaminen (ts. arvon NIL tallentaminen taulukkoon) keskeltä luotausjonoa poistaisi myös jonon loppuosan. Ongelma voidaan poistaa esimerkiksi käyttämällä toista erikoisarvoa DEL, joka ei voi esiintyä avaimena. Poistettaessa olemassaoleva alkio, merkitään se arvolla DEL. Näin ollen hakualgoritmi muuttuu niin, että DEL-arvot ohitetaan ja haetaan, kunnes avain löytyy tai kohdataan arvo NIL. Lisäys tehdään puolestaan ensimmäiseen DEL- tai NIL-kohtaan, joka tulee luotausjonossa vastaan. Haittapuolena hakuajat kasvavat, koska myös poistetut kohdat on tarkistettava; siten hakuaika ei enää ole suorassa suhteessa taulukon täyttöasteeseen. Mikäli poistoja tehdään yleisesti, on järkevämpää toteuttaa hash-taulu ketjuttamalla. Luotauksen ongelma on myös rajoitettu tallennettavien avainten määrä. Toisaalta luotauksen etuja ovat helpompi osoittaminen ja vähäisempi muistin käyttö. Esitetään seuraavaksi lyhyesti kolme luotausmenetelmää. Lineaarisessa luotauksessa (linear probing) oletetaan tunnetuksi jokin tiivistefunktio h, joka kuvaa avaimet joukolle {0,..., m-1}. Funnktio h voi olla esimerkiksi jako- tai tulomenetelmällä konstruoitu funktio. Tällöin voidaan määritellä uusi tiivistefunktio h, joka riippuu kahdesta parametrista: h( k, i) ( h'( k) i)(mod m). Parametri i voi saada arvot 0,..., m-1. On helppo havaita, että jokaisella avaimella luotausjono h(k,0),..., h(k,m-1) sisältää kaikki arvot 0,...,m-1. Luotausjono alkaa nimittäin aina arvosta h (k)

ja etenee askel kerrallaan kohti listan päätä, pyörähtää listan päässä alkuun ja jatkuu aina arvoon h (k)-1 saakka (ellei h (k) ole nolla, jolloin taulukko käydään läpi alusta loppuun). Lineaarinen luotaus on helppo implementoida, mutta se kärsii kasautumisongelmasta: Tiivistefunktion arvo sattuu todennäköisemmin pitkälle varatulle osuudelle kuin lyhyelle ja pitentää pitkää osuutta entisestään. Näin haut hidastuvat. Neliöllinen luotaus (quadratic probing) käyttää myös tunnettua tiivistefunktiota h avuksi. Nyt tiivistefunktio määritellään kuitenkin kaavalla: 2 h( k, i) ( h'( k) c i c i )(mod ), 1 2 m missä c1 ja c2 ovat nollasta poikkeavia vakioita. Nyt paikkoja ei testata peräkkäin, vaan siirryttävien askeleiden määrä riippuu siitä, kuinka monta paikkaa on jo testattu. Näin ollen kasautumisongelma poistuu. Jotta menetelmä toimisi mahdollisimman hyvin, on kuitenkin vakiot c1 ja c2 valittava aina jokaiselle taulukon koolle m sopivasti. Kaksoishashausta (double hashing) pidetään yhtenä parhaista avoimen osoittamisen muodoista. Nyt tarvitaan kaksi aputiivistefunktiota h1 ja h2. Tällöin tiivistefunktio h määritellään seuraavasti: h( k, i) ( h1 ( k) i h2 ( k))(mod m) Luotaus aloitetaan nyt avaimesta h1(k), ja askeleen pituus on h2(k), joten askeleen pituus riippuu avaimesta k. Erilaisia luotausjonoja saadaan näin yksi kutakin paria (h1(k), h2(k)) kohti, eli niitä on aputiivistefunktioista riippuen noin m 2 kappaletta. Ei ole kuitenkaan varmaa, että luotausjono kävisi kullakin avaimella koko taulukon läpi. Mikäli m on alkuluku, näin käy aina olettaen, että h2 saa arvot 0,..., m-1, mutta muuten funktio h2 on valittava sopivasti. Asia voidaan varmistaa myös esimerkiksi valitsemalla luku m luvun 2 potenssiksi ja h2 niin, että sen arvo on aina pariton. Tässä ei analysoida kaksoishashausta tarkemmin, todettakoon kuitenkin, että voidaan osoittaa kaksoishashauksen olevan suorituskyvyltään hyvin lähellä ideaalitapausta. Mainittakoon vielä lopuksi tiivistefunktioiden muita sovelluksia. Tiivistefunktioita käytetään yleisesti tarkistussummissa: välitettävään viestiin k liitetään tiivistefunktion arvo h(k) ja lähetetään pari (k,h(k)). Vastaanottaja voi tarkistaa tiedon eheyden seuraavasti: hän saa parin (k,h ) ja laskee arvon h(k ). Mikäli h on erisuuri kuin h(k ), tiedetään, että viesti on muuttunut matkalla. Mikäli h =h(k ), on todennäköistä, että saatiin alkuperäinen viesti muuttumattomana. Tällaisessa käytössä tiivistefunktion arvojen törmäämisen on oltava hyvin epätodennäköistä. Tiivistefunktion avulla voidaan varmistaa esimerkiksi Internetissä jaeltavien tiedostojen eheys: julkaistaan tiedoston tarkistussumma, jolloin peilisivustolta ladattavan tiedoston sisältöön voidaan luottaa vertaamalla sille laskettua tarkistussummaa alkuperäiseen. Jos halutaan estää viestin tarkoituksellinen muuttaminen, tiivisteen täytyy lisäksi riippua avaimesta, joka on ainoastaan lähettäjän ja vastaanottajan tiedossa. Tällöin puhutaan myös MACfunktiosta (Message Authentication Code). Kummassakin sovelluksessa edellytetään, että tiivistefunktio on erittäin vaikea väärentää, ts. löytää toinen viesti, jolla on sama tiiviste kuin alkuperäisellä viestillä. Itse asiassa salaussovelluksissa käytettävän hyvän kryptografisen tiivistefunktion suunnitteleminen on hyvin vaikea tehtävä ja ainoastaan verrattain harvoja

käyttökelpoisia funktioita tunnetaan. Asiasta kiinnostunut lukija voi perehtyä aiheeseen kirjan [Sta] luvusta 12. 5.2. Binääriset etsintäpuut Binääripuut esiteltiin aiemmin kekolajittelun yhteydessä. Muistettakoon, että binääripuu on tyhjä tai se on puu, jonka jokaisella solmulla on korkeintaan kaksi lapsisolmua. Puun korkeudeksi sanotaan sen tasojen lukumäärää. Puun solmuun voidaan liittää jokin avainkenttä. Kun x on puun solmu, käytetään seuraavia merkintöjä: x.key solmun avainkentän arvo x.left osoitin solmun vasempaan alipuuhun x.right osoitin solmun oikeaan alipuuhun. x.p osoitin solmun vanhempaan Kun T on puu, sen juuren osoitin on T.root. Binäärisessä etsintäpuussa (Binary Search Tree, BST) eli binäärisessä hakupuussa avainkenttien arvoilla on järjestys. Puun kunkin solmun avainkenttä on suurempi kuin mikään sen vasemman alipuun avainkenttä ja pienempi kuin mikään sen oikean alipuun avainkenttä. Näin puun läpikäyminen sisäjärjestyksessä tuottaa avainkenttien järjestyksen pienimmästä suurimpaan. Seuraavassa on esitetty mainittu puun läpikäymistapa Syöte: Binäärisen etsintäpuun solmu x Tulostus: Tulostaa solmusta x lähtevän alipuun avaimet suuruusjärjestyksessä BST_SISÄJÄRJESTYS(x) 1. if x!= NIL 2. BST_SISÄJÄRJESTYS(x.left) 3. tulosta x.key 4. BST_SISÄJÄRJESTYS(x.right) Kun halutaan tulostaa kaikki puun T avaimet, rutiinia kutsutaan antamalla puun juuri syötteeksi, ts. BST_SISÄJÄRJESTYS(T.root). Seuraavassa oletetaan, että puussa ei sama avain esiinny useaan kertaan; avaimien useampikertainen tallentaminen on mahdollista toteuttaa pienillä muutoksilla. 10 27 Kuva 5.3. Binäärinen etsintäpuu

Kun binäärinen etsintäpuu on muodostettu, siitä on varsin helppo etsiä tietoa. Seuraava algoritmi etsii avaimen puusta tai havaitsee, että avain ei esiinny puussa. Algoritmia kutsutaan seuraavasti: BST_ETSI(T.root,k), kun k on puusta T etsittävä avain. Syöte: Binäärisen etsintäpuun juuri x, etsittävä avain k Tulostus: Palauttaa osoittimen solmuun joka sisältää avaimen tai arvon NIL jos avain ei ole puussa. BST_ETSI(x,k) 1. y = x 2. while y!=nil && y.key!=k 3. if y.key < k k oikeassa alipuussa 4. y = y.right 5. else k vasemmassa alipuussa 6. y = y.left 7. return y Koska jokaisella silmukan kierroksella mennään puussa syvemmälle, kierroksia kertyy korkeintaan puun korkeuden verran. Näin ollen algoritmin kompleksisuus on luokkaa (h), missä h on puun korkeus. Myös puun avainkenttien minimi- ja maksimiarvot on helppo löytää, koska minimiarvo löytyy edettäessä jatkuvasti vasenta haaraa pitkin ja maksimiarvo oikeaa haaraa pitkin. Syöte: Binäärisen etsintäpuun juuri x Tulostus: Palauttaa osoittimen solmuun joka sisältää puun pienimmän avaimen tai arvon NIL, jos puu on tyhjä. BST_MINIMI(x) 1. if x==nil 2. return NIL 3. y = x 4. while y.lefty!=nil 5. y = y.left 6. return y Syöte: Binäärisen etsintäpuun juuri x Tulostus: Palauttaa osoittimen solmuun joka sisältää puun suurimman avaimen tai arvon NIL, jos puu on tyhjä. BST_MAKSIMI(x) 1. if x==nil 2. return NIL 3. y = x 4. while y.right!=nil 5. y = y.right 6. return y Näidenkin algoritmien kompleksisuus on luokkaa (h), kun h on puun korkeus. Vielä tarvitaan algoritmit puun muodostamiseen, ts. avaimen lisäämiseen puuhun ja poistamiseen puusta. Avain voidaan lisätä varsin suoraviivaisesti: Etsitään puusta sopiva lehti, jonka lapseksi lisätään

avaimen sisältävä solmu. Seuraava algoritmi lisää vain uusia avaimia; mikäli avain esiintyy puussa, ei tehdä mitään. Syöte: Binäärinen etsintäpuu T ja lisättävä avain k Tulostus: Lisää avaimen sisältävän solmun puun lehtisolmuksi. BST_LISÄÄ(T,k) 1. Muodosta uusi solmu t 2. t.key = k 3. t.left = NIL 4. t.right = NIL 5. x = T.root 6. while x!= NIL Etsitään sopiva lehti 7. y=x 8. if k < x.key 9. x=x.left 10. else if k > x.key 11. x=x.right 12. else Avain oli jo puussa, ei tehdä mitään. return. t.p = y Avain menee y:n lapseksi 15. if y==nil Puu tyhjä, uusi solmu juureksi 16. T.root = t 17. else if k < y.key. y.left = t 19. else. y.right = t 21. return Tässäkin algoritmissa esiintyvän silmukan jokaisella kierroksella mennään puussa syvemmälle, joten kierroksia kertyy korkeintaan puun korkeuden verran. Silmukan lisäksi tehdään vakiomäärä vakioaikaisia operaatioita. Näin ollen algoritmin kompleksisuus on jälleen luokkaa (h), missä h on puun korkeus. Avaimen poistaminen puusta on mutkikkain toimenpide, koska poistettava avain voi olla puun keskellä. Poistossa voidaan kohdata kolme olennaisesti erilaista tapausta. Ensimmäiseksi, jos avain on puun lehdessä, se voidaan yksinkertaisesti poistaa seuraavan esimerkin mukaisesti.

10 25 10 25 12 12 Kuva 5.4. Binäärisestä etsintäpuusta poistetaan avain, joka on puun lehdessä. Toinen tapaus syntyy, kun puun keskeltä poistetaan solmu, jolla on ainoastaan yksi alipuu. Tällöin solmu voidaan yksinkertaisesti ohittaa siirtämällä mainittu alipuu poistettavan solmun vanhemman alipuuksi, kuten seuraava kuva osoittaa. 10 25 10 12 12 Kuva 5.5. Binäärisestä etsintäpuusta poistetaan avain 25, jolla on vain yksi alipuu. Kolmas tapaus on mutkikkain: tällöin on poistettava solmu, jolla on kaksi alipuuta. Tällaista solmua ei voida poistaa suoraan, joten ensin solmun oikeasta alipuusta haetaan pienin avain k. Tämän on oltava solmussa, jolla ei ole vasenta alipuuta (koska vasen alipuu sisältää ainoastaan juuriavainta pienempiä avaimia). Tämä solmu voidaan poistaa yllä esitetyllä menetelmällä. Solmun avain voidaan kopioida poistettavan solmun uudeksi avainkentäksi, koska oikeassa

alipuussa esiintyy ainoastaan suurempia avaimia. Näin on avain saatu poistettua puusta: seuraava kuva havainnollistaa tapausta. 10 25 12 10 25 12 12 25 Kuva 5.6. Binäärisestä etsintäpuusta poistettava avain (10) on solmussa, jolla on kaksi alipuuta. Solmun oikean alipuun pienin avain on 12, joka poistetaan alipuusta ja kopioidaan avaimen 10 tilalle. Poistettaessa puusta solmuja, voidaan joutua siirtelemään alipuita, joten konstruoidaan ensin apualgoritmi tätä varten. Syöte: Binäärinen etsintäpuu T ja sen solmut u ja v (v voi olla NIL) Tulostus: Binäärinen etsintäpuu, jossa solmusta u lähtevän alipuun tilalle on siirretty solmusta v lähtevä alipuu BST_TRANSPLANT(T,u,v) 1. if u.p == NIL u oli juuri 2. T.root = v 3. else if u == u.p.left u vanhempansa vas. alipuussa 4. u.p.left = v 5. else 6. u.p.right = v u vanhempansa oik. alipuussa 7. if v!= NIL 8. v.p = u.p

Algoritmi on luonnollisesti vakioaikainen. Nyt voidaan luoda algoritmi käsittelemään solmujen poistotapaukset: Syöte: Binäärinen etsintäpuu T ja poistettava avain k Tulostus: Poistaa avaimen puusta, jos se esiintyy. Palauttaa arvon TRUE, jos avain oli puussa ja arvon FALSE muuten. BST_POISTA(T,k) 1. x = BST_ETSI(T,k) 2. if x == NIL 3. return FALSE Avain ei ollut puussa 4. if x.left == NIL Ei vasenta alipuuta 5. BST_TRANSPLANT(T,x,x.right) 6. else if x.right == NIL Ei oikeata alipuuta 7. BST_TRANSPLANT(T,x,x.left) 8. else Kaksi alipuuta 9. y = BST_MINIMI(x.right) 10. if y.p!= x 11. BST_TRANSPLANT(T,y,y.right) 12. y.right = x.right. y.right.p = y. BST_TRANSPLANT(T,x,y) 15. y.left = x.left 16. y.left.p = y 17. return TRUE Yllä oleva algoritmi tekee vakioaikaisia operaatioita lukuun ottamatta algoritmien BST_ETSI ja BST_MINIMI kutsuja. Mainitut algoritmit ovat kumpikin huonoimmassa tapauksessa luokkaa (h), kuten aiemmin on todettu. Näin ollen avaimen poistamisen kompleksisuus on (h). Lähteet: [Cor] Cormen, T.H., Leiserson, C.E., Rivest, R.L., Stein, C. Introduction to Algorithms, 2 nd edition, The MIT Press 01. [Sta] Stallings, W. Cryptography and Network Security, Fourth Edition, Prentice Hall 06.