CS-A1140 Tietorakenteet ja algoritmit

Samankaltaiset tiedostot
Kierros 5: Hajautus. Tommi Junttila. Aalto University School of Science Department of Computer Science

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

Algoritmit 2. Luento 4 To Timo Männikkö

Algoritmit 2. Luento 3 Ti Timo Männikkö

Algoritmit 2. Luento 4 Ke Timo Männikkö

Algoritmit 2. Luento 3 Ti Timo Männikkö

5. Hajautus. Tarkastellaan edelleen sivulla 161 esitellyn joukkotietotyypin toteuttamista

A TIETORAKENTEET JA ALGORITMIT

Tietorakenteet ja algoritmit. Hajautus. Ari Korhonen Tietorakenteet ja algoritmit - syksy

4. Hajautus. Hajautus (hashing) on vaihtoehto tasapainoisille puille dynaamisen joukon toteuttamisessa:

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

Algoritmit 2. Demot Timo Männikkö

Algoritmit 2. Luento 7 Ti Timo Männikkö

Ohjelmoinnin jatkokurssi, kurssikoe

Ohjelmoinnin peruskurssien laaja oppimäärä

Kierros 4: Binäärihakupuut

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op. Tietorakenneluokkia 2: HashMap, TreeMap

Kääreluokat (oppikirjan luku 9.4) (Wrapper-classes)

CS-A1140 Tietorakenteet ja algoritmit

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

Algoritmit 2. Luento 2 To Timo Männikkö

Tietorakenteet, laskuharjoitus 8, malliratkaisut

A TIETORAKENTEET JA ALGORITMIT

811312A Tietorakenteet ja algoritmit II Perustietorakenteet

Hajautusrakenteet. R&G Chapter Tietokannan hallinta, kevät 2006, Jan 1

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

811312A Tietorakenteet ja algoritmit, , Harjoitus 5, Ratkaisu

List-luokan soveltamista. Listaan lisääminen Listan läpikäynti Listasta etsiminen Listan sisällön muuttaminen Listasta poistaminen Listan kopioiminen

811312A Tietorakenteet ja algoritmit, , Harjoitus 5, Ratkaisu

Hajautusrakenteet. Hajautukseen perustuvat tiedostorakenteet. Hajautukseen perustuvat tiedostorakenteet. Hajautukseen perustuvat tiedostorakenteet

A TIETORAKENTEET JA ALGORITMIT

Tietorakenteet, laskuharjoitus 3, ratkaisuja

Ohjelmoinnin perusteet Y Python

Informaatioteknologian laitos Olio-ohjelmoinnin perusteet / Salo

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin peruskurssi Y1

private TreeMap<String, Opiskelija> nimella; private TreeMap<String, Opiskelija> numerolla;

4 Tehokkuus ja algoritmien suunnittelu

Tekijä Pitkä Matematiikka 11 ratkaisut luku 2

811312A Tietorakenteet ja algoritmit , Harjoitus 2 ratkaisu

Ohjelmointi 1 Taulukot ja merkkijonot

Ohjelmoinnin perusteet Y Python

Olio-ohjelmointi Syntaksikokoelma

(p j b (i, j) + p i b (j, i)) (p j b (i, j) + p i (1 b (i, j)) p i. tähän. Palaamme sanakirjaongelmaan vielä tasoitetun analyysin yhteydessä.

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

Listarakenne (ArrayList-luokka)

Algoritmit 1. Luento 5 Ti Timo Männikkö

Luku 6. Dynaaminen ohjelmointi. 6.1 Funktion muisti

Harjoitustyö: virtuaalikone

Osoitin ja viittaus C++:ssa

Algoritmit 1. Luento 12 Ke Timo Männikkö

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

Algoritmit 1. Luento 12 Ti Timo Männikkö

Tietorakenteet ja algoritmit syksy Laskuharjoitus 1

Tietorakenteet ja algoritmit

Ohjelmoinnin peruskurssien laaja oppimäärä

Java-kielen perusteet

Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan.

Algoritmit 2. Luento 2 Ke Timo Männikkö

STL:n uudistukset. Seppo Koivisto TTY Ohjelmistotekniikka

Sisällys. 18. Abstraktit tietotyypit. Johdanto. Johdanto

6. Sanakirjat. 6. luku 298

7.4 Sormenjälkitekniikka

18. Abstraktit tietotyypit 18.1

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

TIETORAKENTEET JA ALGORITMIT

Luento 2: Tiedostot ja tiedon varastointi

Tietorakenteet ja algoritmit

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Tietorakenteet ja algoritmit

Taulukot. Jukka Harju, Jukka Juslin

Ohjelmoinnin perusteet Y Python

58131 Tietorakenteet ja algoritmit (syksy 2015)

Algoritmit 2. Luento 6 Ke Timo Männikkö

Tietorakenteet, laskuharjoitus 7, ratkaisuja

Ohjelmoinnin peruskurssien laaja oppimäärä

811120P Diskreetit rakenteet

Muuttujien roolit Kiintoarvo cin >> r;

Algoritmit 2. Luento 6 To Timo Männikkö

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

Rekursiolause. Laskennan teorian opintopiiri. Sebastian Björkqvist. 23. helmikuuta Tiivistelmä

Algoritmit 1. Demot Timo Männikkö

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

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

Demo 6 vastauksia. 1. tehtävä. #ifndef #define D6T1 H D6T1 H. #include <iostream> using std::ostream; using std::cout; using std::endl;

Sekvenssi: kokoelma peräkkäisiä alkioita (lineaarinen

Tässä tehtävässä käsittelet metodeja, listoja sekä alkulukuja (englanniksi prime ).

Tietorakenteet. JAVA-OHJELMOINTI Osa 5: Tietorakenteita. Sisällys. Merkkijonot (String) Luokka String. Metodeja (public)

Ohjelmoinnin peruskurssien laaja oppimäärä

Algoritmit 2. Luento 14 Ke Timo Männikkö

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

9.3 Algoritmin valinta

Taulukot. Taulukon määrittely ja käyttö. Taulukko metodin parametrina. Taulukon sisällön kopiointi toiseen taulukkoon. Taulukon lajittelu

Tieto- ja tallennusrakenteet

13 Operaattoreiden ylimäärittelyjä

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen

811312A Tietorakenteet ja algoritmit, , Harjoitus 7, ratkaisu

Hakupuut. tässä luvussa tarkastelemme puita tiedon tallennusrakenteina

Kirjoita oma versio funktioista strcpy ja strcat, jotka saavat parametrinaan kaksi merkkiosoitinta.

Transkriptio:

CS-A1140 Tietorakenteet ja algoritmit Kierros 5: Hajautus Tommi Junttila Aalto-yliopisto Perustieteiden korkeakoulu Tietotekniikan laitos Syksy 016

Materiaali kirjassa Introduction to Algorithms, 3rd ed. (online via Aalto lib): Kappaleet 11.1 11.4 Vastaavaa materiaalia muualla: Linkkejä: kappale 3.4 kirjassa Algorithms, 4th ed. ja nämä kalvot (neliöinen kokeilu ei kirjassa) Kappale hashing OpenDSA-kirjassa MIT 6.006 OCW video on hashing with chaining MIT 6.006 OCW video on open addressing and cryptographic hashing /49

Monessa sovelluksessa joukoille ja kuvauksille riittää rajapinnan INSERT, SEARCH ja DELETE tehokas toteuttaminen Hajautuksen avulla yllä mainitut saadaan toimimaan keskimäärin vakioajassa pahimman tapauksen aikavaatimus on Θ(n) mutta hyvällä suunnittelulla erittäin epätodennäköistä pienimmän ja suurimman avaimen löytäminen jne ovat kuitenkin lineaariaikaisia operaatioita Toteutuksia: C++11-standardikirjastossa unordered set ja unordered map Javassa HashSet ja HashMap Scalassa HashSet ja HashMap... 3/49

Johdattelu: pienet avainjoukot ja suorahakutaulukot Oletetaan, että mahdollisten avainten joukko on pieni Esimerkiksi ISO 3166-1 alpha--standardissa maakoodit muodostetaan kahdesta aakkoston {A, B,..., Z} 6 kirjaimesta ja mahdollisia koodeja on siis 6 6 = 676, joista 49, kuten FI ja UK, ovat käytössä Voidaan helposti toteuttaa kuvaus koodeilta olioille (esim. pääkaupunkien nimille) tekemällä suorahakutaulukko, jossa on 676 alkiota Koodiin c 1 c assosioitu arvo v talletetaan taulukon alkioon kohdassa index(c 1 c ) = f (c 1 ) 6 + f (c ), missä f (A) = 0,f (B) = 1,...,f (Z) = 5 INSERT, SEARCH ja DELETE on nyt helppoa toteuttaa toimimaan vakioajassa 4/49

Esimerkki: Maakoodien indeksointi suorahakutaulukkossa arr: arr 0 AA 1 AB AC DE FI UK US ZZ 8 138 530 538 675..... 5/49

Maakoodikuvausten toteuttaminen Scala-kielellä: import scala. r e f l e c t. ClassTag class CountryMap [ B >: N u l l ] ( ) ( i m p l i c i t tag : ClassTag [ B ] ) { private val a r r = new Array [ B] ( 6 7 6 ) private def f ( c : Char ) = c. t o I n t A. t o I n t private def isvalidcode ( code : S t r i n g ) = code. length == && 0 <= f ( code ( 0 ) ) && f ( code ( 0 ) ) < 6 && 0 <= f ( code ( 1 ) ) && f ( code ( 1 ) ) < 6 def index ( code : S t r i n g ) : I n t = { require ( isvalidcode ( code ) ) f ( code ( 0 ) ) * 6 + f ( code ( 1 ) ) } def apply ( code : S t r i n g ) : Option [ B ] = { val v = a r r ( index ( code ) ) i f ( v == null ) None else Some( v ) } def update ( code : String, value : B) = { r e q u i r e ( value!= null ) a r r ( index ( code ) ) = value } def delete ( code : S t r i n g ) = { a r r ( index ( code ) ) = null } } 6/49

Huom: null-arvon käyttöä tulee yleisesti ottaen välttää Scala-kielessä (käytetään Option-luokkaa) mutta anteeksiannettavissa tietorakenteiden sisäisissä toteutuksissa Luokan laajentaminen vakioajassa toimivalla size-metodilla on helppoa Esimerkki: Pääkaupunki nimien assosiointi maakoodeihin: val c a p i t a l = new CountryMap [ S t r i n g ] ( ) c a p i t a l ( "DE" ) = " B e r l i n " c a p i t a l ( " FI " ) = " H e l s i n k i " c a p i t a l ( "UK" ) = " London " c a p i t a l ( "US" ) = " Washington " p r i n t l n ( c a p i t a l ( " FI " ) ) tuottaa Some( H e l s i n k i ) AA AB AC DE FI UK 0 1 8 138 530 arr... Berlin Helsinki London US 538. Washington ZZ 675. 7/49

Hajautus ja hajautustaulukot Laajennetaan suorahakutaulukoiden ideaa suurille (tai jopa äärettömille) avainjoukoille U Kunakin ajanhetkenä vain osajoukko K U mahdollisista avaimista on käytössä Perusideana on varata m-alkioinen hajautustaulukko ja käyttää hajautusfunktiota h : U {0,1,...,m 1} kuvaamaan avaimet taulukon indekseille Ideaalitapauksessa jokainen käytössä oleva avain kuvautuisi eri indeksille... mutta yleisesti ottaen tämä on vaikeaa toteuttaa tehokkaasti ja täten tapahtuu yhteentörmäyksiä (engl. collision) kun kaksi avainta kuvautuu samalle indeksille Palataan hajautusfunktioiden suunnitteluun myöhemmin 8/49

Esimerkki: Oletetaan, että käytössä on hajautustaulukko, jossa on tilaa m = 13 alkiolle ja hajautusfunktio merkkijonoille, joka on toteutettu Scalalla (.11.8) seuraavasti: def h(s: String) = math.abs(s.hashcode) % 13 Monet merkkojonot kuvautuvat eri indekseille ja perusidea toimii ongelmitta Germany Finland United States Denmark United Kingdom Sweden 0 1 3 4 5 6 7 8 9 10 11 1 arr Berlin Helsinki Washington Copenhagen London Stockholm Mutta jotkin merkkijonot kuvautuvat samoille indekseille ja tuloksena on yhteentörmäyksiä Germany Finland United States Denmark Austria United Kingdom Sweden 0 1 3 4 5 6 7 8 9 10 11 1 arr Berlin Helsinki Washington London Stockholm 9/49

Kuinka todennäköisiä yhteentörmäykset ovat? Oletetaan hajautusfunktio h, jossa satunnaisella avaimella on sama todennäköisyys kuvautua mille tahansa indeksille {0, 1,..., m 1} toisistaan riippumatta (engl. simple uniform hashing assumption) Tällöin pitää lisätä mln ( 1 ) 1 p avainta, jotta tapahtuu todennäköisyydellä p vähintään yksi yhteentörmäys Esimerkiksi hajautustauluun, jonka koko on m = 1000000, tarvitsee lisätä vain 1178 satunnaista avainta jotta saadaan todennäköisyydellä 0.5 vähintään yksi yhteentörmäys Eli yhteentörmäykset ovat yleisiä Yllä olevan erikoistapaus on niin sanottu syntymäpäiväongelma (engl. birthday paradox): vuodessa on 365 päivää mutta jos samaan tilaan kokoontuu 3 satunnaisesti valittua henkilöä, niin todennäköisyys sille, että vähintään kahdella näistä on sama syntymäpäivä on vähintään 0.5 (olettaen, että syntymäpäivät ovat tasaisesti jakautuneet vuoden jokaiselle päivälle) 10/49

Yhteentörmäysten käsittely 11/49

Vääjäämättä tapahtuvien yhteentörmäysten käsittelyyn tarvitaan jokin strategia Seuraavassa tarkastellaan ketjutusta ja avointa osoittamista Tarvitaan yksi lisämäärittely: jos hajautustaulukkoon, jonka koko on m, on talletettu n avainta, niin taukon täyttösuhde (engl. load factor) on α = n/m 1/49

Ketjutus Myös avoin hajautus ; (engl. chaining, open hashing) Perusidea: hajautustaulukon jokaisesta indeksistä alkaa linkitetty lista, johon siihen indeksiin kuvautuvat avaimet (avain/arvo-parit) tallennetaan Indeksin h(k) laskemisen jälkeen jatko on kuten muuttuvatilaisilla linkitetyillä listoilla: SEARCH: etsitään avainta k listasta alkio kerrallaan INSERT: etsitään avainta listasta ja jos sitä ei löydy, lisätään se listan alkuun/loppuun lisäämäällä uusi lista-alkio DELETE: käydään listaa läpi ja poistetaan listan alkio, mikäli se sisältää poistettavan avaimen k 13/49

Esimerkki: Yhteentörmäysten käsittely ketjutuksella Listojen alkiot ovat muotoa key value next entry, missä key on avain (tai viittaus siihen) value on avaimeen liitetty arvo (tai viittaus siihen) ja next entry on viittaus listan seuraavaan alkioon (null jos viimeinen) Germany Finland 0 1 arr Germany Berlin Finland Helsinki United States 3 4 United States Washington 5 6 7 Denmark Austria 8 9 Denmark Copenhagen Austria Vienna United Kingdom 10 United Kingdom London Sweden 11 1 Sweden Stockholm 14/49

Esimerkki: Joukkojen toteutus hajautuksella ja ketjutuksella Jos ei olla kiinnostuttu kuvauksista vaan joukoista, jätetään vain kenttä value pois ja listan alkiot ovat muotoa key next entry. Lisätään luvut 131, 9833, 344, 6, 17, 434, 653 ja -13 alunperin tyhjään hajautustaulukkoon käyttäen hajautusfunktiota h(k) = k mod m: 0 1 arr 6-13 3 4 5 6 344 653 17 9833 7 8 9 10 131 434 11 1 15/49

unordered sets C++11-standardikirjastossa GNU ISO C++ -kirjasto toteuttaa järjestämättömät joukot ketjutuksella Hajautusfunktiot ja avainten yhtäsuuruus toteutettu valmiiksi perustyypeille # include <iostream > # include <unordered_set > i n t main ( ) { / / A set of small prime numbers std : : unordered_set < i n t > myset = {3,5,7,11,13,17,19,3,9}; myset. erase ( 1 3 ) ; / / erasing by key myset. erase ( myset. begin ( ) ) ; / / erasing by i t e r a t o r std : : cout << " myset contains : " ; f o r ( const i n t & x : myset ) std : : cout << " " << x ; std : : cout << std : : endl ; r e t u r n 0; } Eräs mahdollinen lopputulos (huomaa järjestämättömyys): myset contains : 19 17 11 9 7 5 3 Vastaavasti unordered multisets, maps ja multimaps 16/49

Analyysiä Olkoon lisätty n satunaista avainta hajautustaulukkoon, jossa on m alkiota Oletetaan kuten aiemminkin, että hajautusfunktio jakaa avaimet tasaisesti ja toisistaan riippumattomasti eri indekseille Hajautustaulukon indeksistä i alkavassa listassa on nyt keskimäärin n m avainta Oletetaan, että arvon h(k) laskenta voidaan tehdä vakioajassa Avaimen etsimiseen listasta kuluu aikaa keskimäärin O(1 + n m ), eli O(1 + α), sekä silloin kun avain löytyy että kun sitä ei löydy Jos avainten määrä n on suoraan verrannollinen taulukon kokoon m, eli n cm jollekin vakiolle c, niin α c ja avaimen etsintä vie keskimäärin ajan O(1 + cm m ) = O(1) Jos/kun avainten lisääminen ja poistaminen etsivät ensin avainta listasta, myös niiden aikavaatimus on sama 17/49

Pahimman tapauksen aikavaatimus toteutuu kun kaikki avaimet (tai suuri osa) kuvautuvat samalle indeksille: hajautustaulu muuntuu käytännössä linkitetyksi listaksi ja avainten etsiminen jne vaatii lineaarisen ajan hyvien hajautusfunktioiden suunnittelu on tärkeää 18/49

Uuudelleenhajautus Kuinka suuri taulukko pitäisi varata alussa kun ei välttämättä tiedetä, kuinka monta avainta siihen tullaan tallentamaan myöhemmin? Tai mitä tehdään, kun taulukon täyttösuhde kasvaa liian suureksi? Vastaus on uudelleenhajautus: aloitetaan pienehköllä taulukolla ja kasvatetaan sen kokoa aina kun täyttösuhde kasvaa jotain tiettyä raja-arvoa suuremmaksi Kun taulukon kokoa kasvatetaan, kaikki vanhan taulukon avaimet (tai avain/arvo-parit) täytyy lisätä uuteen taulokkoon koska niiden indeksit suuremmassa taulukossa ovat luultavasti erilaiset kuin vanhassa Mikä on hyvä raja-arvo uudelleenhajautuksen käynnistämiseksi? Ei ole yhtä parasta vastausta; GNU ISO C++ -kirjaston versio 4.6 tekee oletuksena uudelleenhajautuksen kun täyttösuhde kasvaa yli arvon 1,0 ja kasvattaa tällöin taulukon koon kaksinkertaiseksi 19/49

Avoin osoittaminen Myös suljettu hajauttaminen (engl. open addressing, closed hashing) Toinen törmäyksien käsittelymenetelmä Idea: taulukko pidetään niin suurena, että kaikki lisätyt avaimet mahtuvat siihen Jokainen taulukon alkio sisältää yhden avaimen (tai avain/arvo-parin) tai on null tms jos se on vapaa Eli täyttösuhde on korkeintaan 1,0 avoimessa osoittamisessa Kun avainta lisättäessä tapahtuu törmäys, kokeillaan (engl. probe) jotain toista taulukon indeksiä kunnes vapaa paikka löytyy Samoin avainta etsittäessä kokeillaan indeksejä kunnes avain löytyy tai löytyy tyhjä alkio 0/49

Kokeilemista varten täytty määrittää, mitä indeksiä kokeillaan seuraavaksi Tätä varten määritellään hajautusfunktio muotoon h : U {0,1,...,m 1} {0,1,...m 1} missä toinen argumentti kertoo kokeilun järjestysnumeron Jokaiselle avaimelle k saadaan siten kokeilujono h(k,0),h(k,1),...,h(k,m 1) Jotta kokeiltaisiin jokaista indeksiä vuorollaan, tulee kokeilujonon olla joukon {0, 1,..., m 1} permutaatio jokaiselle avaimelle k 1/49

Lineaarinen kokeilu Yksinkertaisin tapa muodostaa kokeilujonoja Olkoon apuhajautusfunktio h : U {0,1,...,m 1} h on yleensä avaimen luokan tai käyttäjän määrittelemä alkuperäinen hajautusfunktio (esim. hashcode Scala-kielessä) Määritellään nyt hajautusfunktio Selvästikin kokeilujono h(k,i) = (h (k) + i) mod m h(k,0),h(k,1),...,h(k,m 1) on nyt joukon {0,1,...,m 1} permutaatio /49

Esimerkki: Törmäysten käsittely lineaarisella kokeilulla Taulukon alkiot ovat muotoa key value, missä jälleen key on avain (tai viittaus siihen) ja value on avaimeen liitetty arvo (tai viittaus siihen). Lisätään kuvaukset Finland Helsinki United States Washington United Kingdom London Denmark Copenhagen Austria Vienna Sweden Stockholm tässä järjestyksessä taulukkoon, jonka koko on 13, käyttämällä aiemmin esitettyä hajautusfunktiota ja lineaarista kokeilua Tulos on oikealla Lisättäessä kuvaus Austria Vienna huomataan indeksin 8 olevan jo varattu avaimelle Denmark. Kokeillaan seuraavaa, ja koska se oli vapaa, lisätään avain/arvo-pari siihen. 0 1 3 4 5 6 7 8 9 10 11 1 arr Germany Berlin Finland Helsinki United States Washington Denmark Copenhagen Austria Vienna United Kingdom London Sweden Stockholm 3/49

Esimerkki: Joukot hajautuksella ja lineaarisella kokeilulla Jos toteutetaan joukkoja, taulukon alkiot ovat vain avaimia (tai viittauksia niihin). Lisätään kokonaislukuavaimet 131, 9833, 344, 6, 17, 434, 653 ja -13 käyttämällä apuhajautusfunktiota h (k) = k: 1. 131 lisätään indeksiin h(131,0) = (131 + 0) mod 13 = 9. 9833 lisätään kohtaan h(9833,0) = (9833 + 0) mod 13 = 5 3. 344 lisätään kohtaan h(344,0) = (344 + 0) mod 13 = 3 4. 6 lisätään kohtaan h(6,0) = (6 + 0) mod 13 = 0 5. 17 lisätään kohtaan h(17,0) = (17 + 0) mod 13 = 4 6. koska indeksi h(434,0) = (434 + 0) mod 13 = 9 on varattu, kokeillaan indeksiä h(434,1) = (434 + 1) mod 13 = 10 ja lisätään 434 siihen 7. koska indeksi h(653,0) = (653 + 0) mod 13 = 3 kuten myös kaksi seuraavaa h(653, 1) = 4 ja h(653, ) = 5 ovat varattuja, lisätään 653 kohtaan h(653,3) = 6 8. koska h( 13, 0) = 0 on varattu, avain 13 lisätään kohtaan h( 13,1) = 1 0 1 3 344 4 5 6 7 8 9 10 434 11 1 arr 6-13 17 9833 653 131 4/49

Seuraavissa esimerkeissä tarkastellaan esityksen yksinkertaisuuden vuoksi vain kokonaislukujoukkoja yleistys muille avaintyypeille ja kuvauksille on suoraviivaista Lineaarinen kokeilu kärsii ilmiöstä nimeltä primäärinen kasautuminen (engl. primary clustering): vapaa indeksi, jonka edeltäjistä i on varattuja, tulee täytetyksi todennäköisyydellä (i + 1)/m eikä 1/m, ja täten varatut indeksit alkavat kasautumaan Esimerkki: Olkoon h (k) = k kokonaislukuavaimille ja m = 17. Lisätään avaimet 1, 50,, 0, 38 ja 35 tässä järjestyksessä lineaarisella kokeilulla ja saadaan 0 1 1 50 3 4 0 5 38 6 7 8 9 10 11 1 13 14 15 16 35 missä nuolet visualisoivat avaimen 35 kokeilujonon. Huomaa, että vain avaimet 1, 50 ja 35 kuvautuvat samalle indeksille. 5/49

Neliöinen kokeilu Päästään eroon primäärisestä kasautumisesta käyttämällä monimutkaisempaa kokeilujonoa Määritellään h(k,i) = (h (k) + c 1 i + c i ) mod m missä c 1 ja c ovat positiivisia vakioita Jotta kokeilujonot olisivat joukon {0, 1,..., m 1} permutaatioita, tulee arvojen c 1, c ja m olla hyvin valittuja Esimerkki: Olkoon m = 11 (eli alkuluku) ja h(k,i) = (h (k) + i + i ) mod m Jos h (k) = 0, niin kokeilujono 0,,6,1,9,8,9,1,6,,0 ei ole joukon {0, 1,..., 10} permutaatio vaan kokeilee vain 6 indeksiä 11 mahdollisesta. Jos taulukon täyttösuhde on suurehko, tämä kokeilujono ei välttämättä löydä vapaita indeksejä. 6/49

Esimerkki: Olkoon m jokin kahden potenssi. Tässä tapauksessa voidaan todistaa, että hajautusfunktio h(k,i) = (h (k) + i(i + 1) ) mod m = (h (k) + 0.5i + 0.5i ) mod m tuottaa kokeilujonoja, jotka ovat joukon {0, 1,..., m} permutaatioita. Esimerkiksi jos m = 3 = 8 ja h (k) = 0, niin kokeilujono on 0,1,3,6,,7,5,4. Esimerkki: Olkoon h (k) = k kokonaislukuavaimille ja m = 16. Lisätään avaimet 1, 50,, 0, 38 ja 35 tässä järjestyksessä käyttämällä neliöistä kokeilua ja hajautusfunktiota h(k) = (h (k) + i(i+1) ) mod m. Nuolet kuvaavat jälleen kokeilujonoja. 0 1 3 4 5 6 7 8 9 10 11 1 13 14 15 7/49

Esimerkki: Olkoon m jokin kahden potenssi. Tässä tapauksessa voidaan todistaa, että hajautusfunktio h(k,i) = (h (k) + i(i + 1) ) mod m = (h (k) + 0.5i + 0.5i ) mod m tuottaa kokeilujonoja, jotka ovat joukon {0, 1,..., m} permutaatioita. Esimerkiksi jos m = 3 = 8 ja h (k) = 0, niin kokeilujono on 0,1,3,6,,7,5,4. Esimerkki: Olkoon h (k) = k kokonaislukuavaimille ja m = 16. Lisätään avaimet 1, 50,, 0, 38 ja 35 tässä järjestyksessä käyttämällä neliöistä kokeilua ja hajautusfunktiota h(k) = (h (k) + i(i+1) ) mod m. Nuolet kuvaavat jälleen kokeilujonoja. 0 1 3 4 5 6 7 8 9 10 11 1 13 14 15 1 8/49

Esimerkki: Olkoon m jokin kahden potenssi. Tässä tapauksessa voidaan todistaa, että hajautusfunktio h(k,i) = (h (k) + i(i + 1) ) mod m = (h (k) + 0.5i + 0.5i ) mod m tuottaa kokeilujonoja, jotka ovat joukon {0, 1,..., m} permutaatioita. Esimerkiksi jos m = 3 = 8 ja h (k) = 0, niin kokeilujono on 0,1,3,6,,7,5,4. Esimerkki: Olkoon h (k) = k kokonaislukuavaimille ja m = 16. Lisätään avaimet 1, 50,, 0, 38 ja 35 tässä järjestyksessä käyttämällä neliöistä kokeilua ja hajautusfunktiota h(k) = (h (k) + i(i+1) ) mod m. Nuolet kuvaavat jälleen kokeilujonoja. 0 1 1 3 4 5 6 7 8 9 10 11 1 13 14 15 50 9/49

Esimerkki: Olkoon m jokin kahden potenssi. Tässä tapauksessa voidaan todistaa, että hajautusfunktio h(k,i) = (h (k) + i(i + 1) ) mod m = (h (k) + 0.5i + 0.5i ) mod m tuottaa kokeilujonoja, jotka ovat joukon {0, 1,..., m} permutaatioita. Esimerkiksi jos m = 3 = 8 ja h (k) = 0, niin kokeilujono on 0,1,3,6,,7,5,4. Esimerkki: Olkoon h (k) = k kokonaislukuavaimille ja m = 16. Lisätään avaimet 1, 50,, 0, 38 ja 35 tässä järjestyksessä käyttämällä neliöistä kokeilua ja hajautusfunktiota h(k) = (h (k) + i(i+1) ) mod m. Nuolet kuvaavat jälleen kokeilujonoja. 0 1 1 50 3 4 5 6 7 8 9 10 11 1 13 14 15 30/49

Esimerkki: Olkoon m jokin kahden potenssi. Tässä tapauksessa voidaan todistaa, että hajautusfunktio h(k,i) = (h (k) + i(i + 1) ) mod m = (h (k) + 0.5i + 0.5i ) mod m tuottaa kokeilujonoja, jotka ovat joukon {0, 1,..., m} permutaatioita. Esimerkiksi jos m = 3 = 8 ja h (k) = 0, niin kokeilujono on 0,1,3,6,,7,5,4. Esimerkki: Olkoon h (k) = k kokonaislukuavaimille ja m = 16. Lisätään avaimet 1, 50,, 0, 38 ja 35 tässä järjestyksessä käyttämällä neliöistä kokeilua ja hajautusfunktiota h(k) = (h (k) + i(i+1) ) mod m. Nuolet kuvaavat jälleen kokeilujonoja. 0 1 1 50 3 4 5 6 7 8 9 10 11 1 13 14 15 0 31/49

Esimerkki: Olkoon m jokin kahden potenssi. Tässä tapauksessa voidaan todistaa, että hajautusfunktio h(k,i) = (h (k) + i(i + 1) ) mod m = (h (k) + 0.5i + 0.5i ) mod m tuottaa kokeilujonoja, jotka ovat joukon {0, 1,..., m} permutaatioita. Esimerkiksi jos m = 3 = 8 ja h (k) = 0, niin kokeilujono on 0,1,3,6,,7,5,4. Esimerkki: Olkoon h (k) = k kokonaislukuavaimille ja m = 16. Lisätään avaimet 1, 50,, 0, 38 ja 35 tässä järjestyksessä käyttämällä neliöistä kokeilua ja hajautusfunktiota h(k) = (h (k) + i(i+1) ) mod m. Nuolet kuvaavat jälleen kokeilujonoja. 0 1 1 50 3 4 5 0 6 7 8 9 10 11 1 13 14 15 38 3/49

Esimerkki: Olkoon m jokin kahden potenssi. Tässä tapauksessa voidaan todistaa, että hajautusfunktio h(k,i) = (h (k) + i(i + 1) ) mod m = (h (k) + 0.5i + 0.5i ) mod m tuottaa kokeilujonoja, jotka ovat joukon {0, 1,..., m} permutaatioita. Esimerkiksi jos m = 3 = 8 ja h (k) = 0, niin kokeilujono on 0,1,3,6,,7,5,4. Esimerkki: Olkoon h (k) = k kokonaislukuavaimille ja m = 16. Lisätään avaimet 1, 50,, 0, 38 ja 35 tässä järjestyksessä käyttämällä neliöistä kokeilua ja hajautusfunktiota h(k) = (h (k) + i(i+1) ) mod m. Nuolet kuvaavat jälleen kokeilujonoja. 0 1 1 50 3 4 5 0 6 7 8 38 9 10 11 1 13 14 15 35 33/49

Neliöllinen kokeilu ei kärsi primäärisestä kasautumisesta Mutta edelleen kahdella avaimella k 1 ja k, joilla on sama hajautusarvo h (k 1 ) = h (k ), on myös sama kokeilujono Tämä (vähemmän haitallinen) ongelma on nimeltään sekundäärinen kasautuminen Yleisemminkin lineaarinen ja neliöinen kokeilu ovat epäoptimaalisia koska m-alkioisille hajautustaulukoille on olemassa m! erilaista kokeilujonoa mutta lineaarinen ja neliöinen kokeilu tuottavat vain m kokeilujonoa (kun apuhajautusfunktio h ja vakiot ovat kiinnitettyjä) 34/49

Kaksoishajautus Tuottaa enemmän kokeilujonoja käyttämällä kahta apuhajautusfunktiota Yleinen muoto on h(k,i) = (h 1 (k) + i h (k)) mod m Kaksoishajauttaminen voi tuottaa m erilaista kokeilujonoa Jotta kokeilujonot olisivat permutaatioita, täytyy funktiolla h olla tiettyjä ominaisuuksia Vaaditaan, että arvoilla h (k) ei saa olla yhteisiä tekijöitä taulukon koon m kanssa (engl. relatively prime) 35/49

Esimerkki: Eräs helppo tapa pakottaa kokeilujonot h(k,0),h(k,1),...,h(k,m 1) olemaan joukon {0, 1,..., m 1} permutaatioita on vaatia, että m on kahden potenssi ja h (k) on pariton luku. Esimerkki: Toinen tapa saada kokeilujonot h(k,0),h(k,1),...,h(k,m 1) joukon {0, 1,..., m 1} permutaatioiksi on asettaa m alkuluvuksi ja h (k) positiivikseksi luvuksi, joka on pienempi kuin m. Esimerkiksi kokonaislukuvaimille voidaan asettaa h 1 (k) = k mod m ja h (k) = 1 + (k mod m ) jollekin arvolle m, joka on jonkin verran pienempi kuin m. 36/49

Avainten poistaminen Avainten poistaminen ketjutuksessa oli helppoa Avoimessa osoittamisessa täytyy pitää huoli, ettei avainten poistaminen jätä aukkoja taulukkoon ja muiden avainten kokeilujonoihin Esimerkki: Olkoon h (k) = k kokonaislukuavaimille, m = 16 ja käytetään neliöistä kokeilua hajautusfunktiolla h(k) = (h (k) + i(i+1) ) mod m. Lisätään avaimet 3, 0 ja 35 taulukkoon. Tulos on 0 1 3 3 4 5 0 6 7 8 9 10 11 1 13 14 15 35 Jos nyt poistettaisiin avain 0 vain tuhoamalla se, tulos on 0 1 3 4 5 3 6 7 8 9 10 11 1 13 14 15 35 Mutta nyt avainten hakeminen ei enää löydä avainta 35 koska se kokeilee vain indeksejä 3 sekä 4 ja tämän jälkeen lopettaa havaittuaan vapaan indeksin 4. 37/49

Eräs ratkaisu on korvata poistettu avain erikoissymbolilla del Haku ja poistaminen kohtelevät näitä oikeina avaimina mutta avainten lisääminen voi kirjoittaa niiden päälle Jos taulukossa on monta tällaista arvoa, operaatiot hidastuvat ja taulukon avaimet kannattanee uudelleenhajauttaa samaan taulukkoon Esimerkki: Tarkastellaan edellisen esimerkin taulukkoa 0 1 3 4 5 6 7 8 9 10 11 1 13 14 15 3 0 35 Poistetaan avain 0 edellä kuvatulla tavalla: 0 1 3 4 5 6 7 8 9 10 11 1 13 14 15 3 del 35 Nyt haku toimii kuten aiemminkin ja löytää avaimen 35 kokeiltuaan indeksejä 3, 4 ja 6. Jos lisätään avain 19, kokeillaan indeksejä 3 sekä 4 ja lisätään avain indeksiin 4. 38/49

Analyysiä Analyysiä varten oletetaan, että jokaisen avaimen kokeilujono on yhtä todennäköisesti mikä tahansa joukon {0, 1,..., m 1} m! permutaatiosta Esitetyistä kokeilumenetelmistä kaksoishajautus on lähimpänä vaatimusta Teoreema 11.6 kirjasta Introduction to Algorithms, 3rd ed. (online via Aalto lib) Edellisellä oletuksella avaimen hakeminen tekee keskimäärin korkeintaan 1/(1 α) kokeilua silloin kun avainta ei löydy. Täyttösuhde α on pienempi kuin 1 ei-täysillä hajautustaulukoilla avointa osoittamista käytettäessä ja täten 1/(1 α) = 1 + α + α + α 3 +... Epämuodollinen selitys: 1 tulee ensimmäisestä aina tehtävästä kokeilusta Ensimmäinen kokeiltu indeksi oli varattu todennäköisyydellä α ja täten toinen kokeilu tehdään todennäköisyydellä α Ensimmäinen ja toinen kokeiltu indeksi olivat varattuja todennäköisyydellä α ja täten kolmas kokeilu tehdään todennäköisyydellä α... 39/49

Eli jos täyttösuhde pidetään pienempänä kuin jokin vakioraja-arvo, niin avainten lisääminen, etsiminen ja poistaminen vievät keskimäärin vakioajan Esimerkiksi jos täyttösuhde pidetään alle arvon 0.5, niin kokeilujen lukumäärä on keskimäärin korkeintaan Huom: poistetut arvot ( del -symboli) lesketaan mukaan täyttöasteeseen tässä yhteydessä. Jos avainten poistot ovat erittäin yleisiä, ketjutus voi olla parempi törmäystenkäsittelyratkaisu. Jälleen jos täyttösuhde nousee liian suureksi, tehdään uudelleenhajautus: kasvatetaan taulukon kokoa ja lisätään kaikki avaimet uuteen taulukkoon Kuten dynaamisten taulukoiden tapauksessa, taulukon kokoa kasvatetaan tyypillisesti kaksinkertaiseksi Mikä on hyvä raja-arvo täyttösuhteelle? Jälleen ei ole yksiselitteisesti parasta arvoa mutta tyypillisesti avoimessa osoittamisessa käytetään arvoja 0.50 tai 0.75 40/49

Hajautusfunktioiden suunnittelusta 41/49

Aiemmin oletettiin hajautusfunktio h, jossa satunnaisella avaimella on sama todennäköisyys kuvautua mille tahansa indeksille {0, 1,..., m 1} toisistaan riippumatta Mutta ei välttämättä tiedetä etukäteen lisättävien avainten jakaumaa eikä niitä välttämättä valita toisistaan riippumattomasti Pyritään laskemaan hajautusarvot h(k) niin, että ne odotusarvoisesti ovat riippumattomia avaimissa mahdollisesti esiintyvistä säännönmukaisuuksista Esimerkiksi kääntäjän symbolitaulukossa ei tahdota yleisesti esiintyvien merkkijonojen i ja j hajautusarvojen olevan lähellä toisiaan jos käytetään suljettua hajautusta lineaarisella kokeilulla Karkea ohje voisi olla: mitä satunnaisemmalta hajautusfunktion arvot näyttävät, sen parempi Avaimen k hajautusarvon tulisi myös olla tehokkaasti laskettavissa 4/49

Kokonaislukujen hajautus Tarkastellaan ensin hajautusfunktioita kokonaisluvuille Ollaan jo nähty yleinen jakolaskumenetelmä, missä hajautusarvo h(k) on jakojäännös kun jaetaan avaimen arvo taulukon koolla m: h(k) = k mod m Tämä on tehokkaasti laskettavissa, tarvitaan vain yksi jakolasku Mutta tämä tapa ei välttämättä ole paras jos m on kahden potenssi eli m = p : vain p vähiten merkitsevää bittiä vaikuttaa hajautusfunktion arvoon Jos mahdollista, m pitäisi mieluummin olla alkuluku, joka ei ole liian lähellä kahden potenssia 43/49

Kertolaskumenetelmä tuottaa hajautusarvon w-bittiselle kokonaislukuavaimelle k kertomalla sen hyvin valitulla w-bittisellä vakioluvulla A, jolloin tulokseksi saadaan w-bittinen kokonaisluku r = r w 1 r w...r 0 = ka ja ottamalla tämän tuloksen vähemmän merkitsevän osuuden r w 1...r 0 eniten merkitsevistä biteistä tarvittavan määrän bittejä tulokseksi Nyt m on yleensä kahden potenssi, m = p, joten voidaan cain ottaa p eniten merkitsevää bittiä osuudesta r w 1...r 0 käyttämällä shift- ja and-operaatioita Tarkastellaan esimerkiksi Scala-funktiota def h(x: Int): Int = (x * 654435769L).toInt Nyt h(1).tohexstring = 9e3779b9 h().tohexstring = 3c6ef37 h(3).tohexstring = daa66db jne; arvot vaikuttavat kohtuullisen satunnaisilta 44/49

Merkkijonojen hajautus Tulkitaan merkkijono s = c 0...c n 1 kokonaisluvuksi prosessoimalla sen merkit c i yksi kerrallaan Esim. nykyisissä Javan toteutuksissa käytetään hajautusfunktiota n 1 h(s) = i=0 c i 31 n 1 i Toteutuksesta openjdk Java 8 (huomaa ohjelmallinen välimuisti hajautusarvolle eli sitä ei aina lasketa uudelleen): p u b l i c f i n a l class S t r i n g implements java. i o. S e r i a l i z a b l e, Comparable<String >, CharSequence { / * * The value i s used f o r character storage. * / p r i v a t e f i n a l char value [ ] ; / * * Cache the hash code f o r the s t r i n g * / private i n t hash ; / / Default to 0... p u b l i c i n t hashcode ( ) { i n t h = hash ; i f ( h == 0 && value. length > 0) { char va l [ ] = value ; f o r ( i n t i = 0; i < value. length ; i ++) { h = 31 * h + v a l [ i ] ; } hash = h ; } r e t u r n h ; } } 45/49

Rakenteellisten olioiden hajautus Taulukoille ja rakenteellisille olioille, joilla on useita kenttiä jne, voidaan laskea hajautusarvo samalla lailla yhdistämällä komponenttien hajautusarvot yhdeksi Esimerkiksi hajautusarvon laskenta taulukoille toteutuksessa openjdk Java 6 p u b l i c s t a t i c i n t hashcode ( i n t a [ ] ) { i f ( a == n u l l ) r e t u r n 0; i n t r e s u l t = 1; f o r ( i n t element : a ) r e s u l t = 31 * r e s u l t + element ; return r e s u l t ; } 46/49

Tällä hetkellä Scala käyttää hieman monimutkaisempaa hajautusfunktiota, joka pohjautuu MurMurHash 3-funktioon Se pyrkii tuottamaan hyviä hajautusarvoja sekoittamalla avaintaulukon alkioiden arvojen bittejä tehokkaasti ja olemaan myös nopea Scalan lähdekoodista: f i n a l def arrayhash [@s p e c i a l i z e d T ] ( a : Array [ T ], seed : I n t ) : I n t = { var h = seed var i = 0 while ( i < a. length ) { h = mix ( h, a ( i ). ## ) / / ## i s hashcode i += 1 } f i n a l i z e H a s h ( h, a. length ) } f i n a l def mix ( hash : I n t, data : I n t ) : I n t = { var h = mixlast ( hash, data ) h = r o t l ( h, 13) / / r o t l i s I n t e r g e r. r o t a t e L e f t h * 5 + 0xe6546b64 } 47/49

f i n a l def mixlast ( hash : I n t, data : I n t ) : I n t = { var k = data k * = 0xcc9ed51 k = r o t l ( k, 15) k * = 0x1b873593 hash ^ k } / * * F i n a l i z e a hash to i n c o r p o r a t e the length and make sure a l l b i t s avalanche. * / f i n a l def f i n a l i z e H a s h ( hash : I n t, le ngth : I n t ) : I n t = avalanche ( hash ^ length ) / * * Force a l l b i t s of the hash to avalanche. Used f o r f i n a l i z i n g the hash. * / private f i n a l def avalanche ( hash : I n t ) : I n t = { var h = hash h ^= h >>> 16 h * = 0x85ebca6b h ^= h >>> 13 h * = 0xcbae35 h ^= h >>> 16 h } 48/49

Lisää Hyvien hajautusfunktioiden tärkeydestä johtuen useita erilaisia hajautusfunktioita on kehitetty eri tarkoituksiin vuosien aikana Katso esim. seuraavia sivustoja: https://en.wikipedia.org/wiki/hash_function http://www.partow.net/programming/hashfunctions/ http://www.burtleburtle.net/bob/hash/doobs.html https://en.wikipedia.org/wiki/murmurhash https://gcc.gnu.org/viewcvs/gcc/trunk/libstdc%b% B-v3/libsupc%B%B/hash_bytes.cc?view=markup#l74 49/49