Tietorakenteet, laskuharjoitus 8,

Samankaltaiset tiedostot
Tietorakenteet, laskuharjoitus 8, malliratkaisut

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

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

Algoritmit 2. Luento 3 Ti Timo Männikkö

Algoritmit 2. Luento 3 Ti Timo Männikkö

Java-kielen perusteet

Algoritmit 2. Luento 4 To Timo Männikkö

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

Listarakenne (ArrayList-luokka)

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

20. Javan omat luokat 20.1

Sisällys. 20. Javan omat luokat. Java API. Pakkaukset. java\lang

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

Java-kielen perusteet

17. Javan omat luokat 17.1

Tietorakenteet, laskuharjoitus 7, ratkaisuja

5. Hajautus. Tarkastellaan edelleen sivulla 161 esitellyn joukkotietotyypin toteuttamista

Tietorakenteet, laskuharjoitus 3, ratkaisuja

Algoritmit 2. Luento 7 Ti Timo Männikkö

Ohjelmoinnin perusteet Y Python

Algoritmit 2. Luento 4 Ke Timo Männikkö

Algoritmit 1. Demot Timo Männikkö

Algoritmit 1. Demot Timo Männikkö

Ohjelmoinnin jatkokurssi, kurssikoe

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

811312A Tietorakenteet ja algoritmit, , Harjoitus 5, Ratkaisu

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.

Luokka Murtoluku uudelleen. Kirjoitetaan luokka Murtoluku uudelleen niin, että murtolukujen sieventäminen on mahdollista.

Informaatioteknologian laitos Olio-ohjelmoinnin perusteet / Salo

Tietorakenteet, laskuharjoitus 6,

Muuttujat ja kontrolli. Ville Sundberg

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:

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

ITKP102 Ohjelmointi 1 (6 op)

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

811312A Tietorakenteet ja algoritmit, , Harjoitus 5, Ratkaisu

Tietorakenteet (syksy 2013)

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

Metodien tekeminen Javalla

Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista

Tietorakenteet ja algoritmit

17. Javan omat luokat 17.1

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++?

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

Java-kielen perusteet

811120P Diskreetit rakenteet

Javan perusteet. Ohjelman tehtävät: tietojen syöttö, lukeminen prosessointi, halutun informaation tulostaminen tulostus tiedon varastointi

15. Ohjelmoinnin tekniikkaa 15.1

1. Omat operaatiot 1.1

Ohjelmistojen mallintaminen viikon 4 laskareiden mallivastauksia

15. Ohjelmoinnin tekniikkaa 15.1

Taulukot. Jukka Harju, Jukka Juslin

Ohjelmointi 2 / 2010 Välikoe / 26.3

Tietorakenteet ja algoritmit syksy Laskuharjoitus 1

5. Laskutoimitukset eri lukujärjestelmissä

Algoritmit 2. Luento 14 Ke Timo Männikkö

Ohjelmoinnin perusteet Y Python

16. Javan omat luokat 16.1

Algoritmit 2. Luento 2 To Timo Männikkö

ITKP102 Ohjelmointi 1 (6 op)

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

Ohjelmointi 2 / 2008 Välikoe / Pöytätestaa seuraava ohjelma.

Olio-ohjelmointi Javalla

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

VIII. Osa. Liitteet. Liitteet Suoritusjärjestys Varatut sanat Binääri- ja heksamuoto

Luokan sisällä on lista

Vertailulauseet. Ehtolausekkeet. Vertailulauseet. Vertailulauseet. if-lauseke. if-lauseke. Javan perusteet 2004

Tietotyypit ja operaattorit

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Muuttujien roolit Kiintoarvo cin >> r;

Algoritmit 1. Luento 12 Ti Timo Männikkö

Ohjelmoinnin perusteet Y Python

A TIETORAKENTEET JA ALGORITMIT

7. Näytölle tulostaminen 7.1

Algoritmit 2. Luento 2 Ke Timo Männikkö

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

MS-A0402 Diskreetin matematiikan perusteet

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

11. Javan valintarakenteet 11.1

Sisältö. 2. Taulukot. Yleistä. Yleistä

Yleistä. Nyt käsitellään vain taulukko (array), joka on saman tyyppisten muuttujien eli alkioiden (element) kokoelma.

ITKP102 Ohjelmointi 1 (6 op), arvosteluraportti

A TIETORAKENTEET JA ALGORITMIT

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

1. Kun käyttäjä antaa nollan, niin ei tulosteta enää tuloa 2. Hyväksy käyttäjältä luku vain joltain tietyltä väliltä (esim tai )

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

Ohjelmoinnin perusteet Y Python

Ohjelmointi 1 / 2009 syksy Tentti / 18.12

Merkitse kertolasku potenssin avulla ja laske sen arvo.

Java-kielen perusteita

ITKP102 Ohjelmointi 1 (6 op), arvosteluraportti

Tietorakenteet, laskuharjoitus 7,

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

Java-kielen perusteita

Ohjelmoinnin perusteet Y Python

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

Ohjelmoinnin perusteet Y Python

Tieto- ja tallennusrakenteet

Luku 6. Dynaaminen ohjelmointi. 6.1 Funktion muisti

Transkriptio:

Tietorakenteet, laskuharjoitus 8, 23.-26.3 1. Ohessa on esitetty algoritmit toteuttamaan muutokset ASCII-merkkijonosta kokonaisluvuksi ja kokonaisluvusta ASCII-merkkijonoksi. Esityksessä oletetaan, että Ascii palauttaa kirjaimen ASCII-koodin ja Char palauttaa annettua ASCII-koodia vastaavan kirjaimen. StringToInt(sana) 1 luku = 0 2 for i = 0 to sana.length 1 3 luku = luku + 128 i Ascii(sana[i]) 4 return luku IntToString(luku) 1 sana = tyhjä merkkijono 2 while luku > 0 3 jakoj = luku mod 128 4 lisää sanan viimeiseksi kirjaimeksi Char(jakoj) 5 luku = luku jakoj 6 luku = luku/128 7 return sana Algoritmit nojaavat ajattelutapaan, jossa ASCII-merkkijonoa vastaavaa kokonaislukua ajatellaan 128-kantaisen lukujärjestelmän lukuna. Silloin ylläolevat algoritmit voidaan nähdä muunnoksina 128-kantaisen ja 10-kantaisen lukujärjestelmien välillä. Pienin muutoksin jälkimmäisestä algoritmista saataisiin muunnos kymmenkantaisista luvuista mielivaltaisen kantaluvun lukujärjestelmiin. Simuloimalla algoritmien toimintaa käsin tai toteuttamalla ne jollain ohjelmointikielellä saadaan taulussa 1 esitetyt tulokset. merkkijono vastaava luku JONO 166963146 PINO 166962384 LISTA 17625834700 TIETORAKENTEET 209319107584126256052811424980 Taulukko 1: merkkijonoja ja niitä vastaavia kokonaislukuja 2. Tämän tehtävän vastauksiksi kelpaisi periaatteessa edellisessä tehtävässä sovellettu merkkijonon muunnos kokonaisluvuksi lisättynä jakojäännöksen ottamisella. Tässä lähestymistavassa on kuitenkin muutamia ongelmia. Ensinnäkään ASCII-merkistö ei sisällä ääkkösiä. Toiseksi kaikki ASCII-merkistön 128 merkkiä eivät ole kirjaimia vaan komentoja näyttöpäätteelle, jotka eivät esiinny tavallisessa tekstissä. Edellisen seurauksena luvuista tulee tarpeettoman isoja. Käytetään edellä esiteltyä menetelmää 1

kuitenkin hiukan muunnettuna kuvataksemme merkkijonon kokonaisluvuksi, jonka sitten hajautamme. Ohessa on Java-koodi, jolla kuvaamme merkkijonon kokonaisluvuksi joukkoon {0,..., 2 31 2. Suurimmaksi mahdolliseksi arvoksi on valittu 2 31 2 koska se yksinkertaistaa universaalin hajautusfunktion toteuttamista (2 31 1 on sopiva alkuluku universaaliin hajautukseen). Annamme kokonaisluvun vuotaa yli ja huolehdimme vasta lopussa, että palautettava luku ei ole negatiivinen. public static int stringtoint(string mjono) { int koodi = 0; int kerroin = 1; for (int i = 0; i < mjono.length(); i++) { int merkki = (int) mjono.charat(i); koodi += ((merkki > 180)? merkki - 133 : merkki - 33)*kerroin; kerroin *= 95; if (koodi == Integer.MAX_VALUE koodi <= Integer.MIN_VALUE + 1) return Integer.MAX_VALUE - 1; else if (koodi > 0) return koodi; else return (-1)*(koodi); Jokaisesta merkistä vähennetään ensin 33, koska merkit, joita vastaava kokonaisluku on tätä pienempi, ovat erilaisia komentoja (rivinvaihto ym.) näyttölaitteelle. Tämän vuoksi myös käytettävä kantaluku on 128:n sijaan 95. Lisäksi merkeistä joita vastaava kokonaisluku on isompi kuin 180 vähennetään 100. Tämä johtuu siitä, että Javassa skandinaavisia merkkejä vastaavat luvut ovat suurempia kuin 180. Käyttämällä edellä kuvattua funktiota muuntamaan merkkijono kokonaisluvuksi voimme keskittyä hajautusfunktioissa kuvaamaan kokonaisluvun kokonaisluvuksi. Koska useammassa hajautusfunktiossa tarvitaan alkulukuja, niin ohessa on Java-funktio, joka palauttaa annettua ylärajaa pienemmät alkuluvut ArrayListissä. public static ArrayList<Integer> alkuluvut(int max) { ArrayList<Integer> aluvut = new ArrayList<Integer>(); aluvut.add(2); for (int i = 3; i < max; i += 2) { boolean kelpo = true; for (int alkuluku : aluvut) { if (i % alkuluku == 0) { kelpo = false; break; 2

if (kelpo) aluvut.add(i); return aluvut; (a) Tähän kelpaa esimerkiksi universaalihajautus. Edellä esitelty stringtoint kuvaa merkkijonon luvuille {0,..., 2 31 2. 2 31 1 on alkuluku, joten se kelpaa mainiosti universaalihajautuksessa tarvittavaksi suureksi alkuluvuksi. Hajautusfunktiossa käytettävät a ja b ovat tietenkin valittu asiaan kuuluvalla tavalla. protected int universaali_haj(int num) { long arvo = (((this.a * num) + this.b ) % BIGPRIME) % this.koko; if (arvo < 0) arvo = (arvo + Integer.MAX_VALUE) % this.koko; return (int) arvo; Myös c-kohdan kertolalaskumenetelmä sopisi tähän oikein hyvin. (b) Kun halutaan hajauttaa kokonaisluku joukkoon {0,..., p 1, missä p on alkuluku, niin hajautusfunktioksi kelpaa hyvin jakojäännöksen ottaminen p:n suhteen annetusta kokonaisluvusta. Luentokalvoilla oli todettu, että alkuluvun olisi hyvä olla mahdollisimman kaukana kakkosen potensseista. Ohessa on Java-funktio, joka palauttaa alkuluvun, joka on mahdollisimman lähellä annettua lukua. Jos tätä kutsutaan luvulla (2 k + 2 k+1 )/2, niin saadaan jakojäännösmenetelmään toivotunlainen alkuluku. public static int alukulahelta(int luku) { ArrayList<Integer> aluvut = alkuluvut(2*luku); int etaisyys = Math.abs(luku - aluvut.get(aluvut.size() - 1)); int i; for (i = aluvut.size() - 2; i > 0; i--) { int temp = Math.abs(aluvut.get(i) - luku); if (etaisyys > temp) etaisyys = temp; else break; return aluvut.get(i+1); (c) Tähän kelpaa kertolaskumenetelmä, koska se ei aseta mitään rajoituksia hajautustaulun koolle. Tämän vuoksi sitä voidaan käyttää olipa ensimmäinen hajautusfunktio mikä tahansa. Ohessa on Java-toteutus. public static final double MAGIC = 0.6180339887498948; public static double murto_osa(double num) { return num - Math.floor(num); 3

protected int kerto_haj(int num) { return (int) Math.floor(this.koko*murto_osa(MAGIC*num)); Tarkalleen ottaen jokainen edellä toteutettu hajautusfunktio kuvaa avaimet joukkoon {0,..., m 1. Jos halutaan hajautusfunktioiden arvot tehtävänannon mukaisille joukoille riittää tietenkin lisätä funktioiden palautusarvoon 1. 3. Ylivuotoketjuihin ja avoimeen hajautukseen perustuvat hajautustaulut toteutettiin yhteisen yliluokan avulla, joka helpotti yhteisen toiminnallisuuden jakamista. Luokalla on mm. edellisessä tehtävässä esitellyt metodit. import java.util.arraylist; import java.math.biginteger; // Yliluokka avoimelle ja ylivuotoketjuja käyttävälle hajautukselle abstract class HajautusTaulu { /* Eri hajautusfunktioiden tyypit */ public enum hajautus {UNIV, KERTO, JAKOJ public static final double MAGIC = 0.6180339887498948; public static final BigInteger BIGPRIME = new BigInteger("2147483647"); protected int koko; protected hajautus haj; //ensisijainen hajautusfunktio //universaalihajautuksen parametrit protected int a; protected int b; public int annakoko() { return this.koko; protected int kerto_haj(int num) { return (int) Math.floor(this.koko*murto_osa(MAGIC*num)); protected int jakoj_haj(int num) { return num % this.koko; protected int universaali_haj(int num) { //esitetty edellä 4

protected int hash(int avain, hajautus haja) { int arvo = 0; switch(haja) { case JAKOJ: arvo = jakoj_haj(avain); break; case KERTO: arvo = kerto_haj(avain); break; case UNIV: arvo = universaali_haj(avain); break; return arvo; public static double murto_osa(double num) { return num - Math.floor(num); public static ArrayList<Integer> alkuluvut(int max) { //esitetty edellä public static int alukulahelta(int luku) { //esitetty edellä public static int stringtoint(string mjono) { //esitetty edellä Koska Java ei tue etumerkittömiä kokonaislukuja pitää avoimessa hajautuksessa olla hyvin tarkkana törmäysten sattuessa. Jos luku vuotaa yli, niin se pyörähtää ja saa hyvin pienen negatiivisen arvon. Javassa %-operaattori (jakojäännös) voi myös palauttaa negatiivisen arvon, joten lukujen oikeellisuus pitää tarkistaa käsin. Ylivuotoketjuja käyttävän hajautustaulun toteutus on Javassa melko suoraviivaista, koska apuna voidaan käyttää kirjastosta löytyviä valmiita toteutuksia linkitetyille listoille. Hajautustaulujen toteutukset ovat tiedostoissa HajautusTauluA (avoin hajautus) sekä HajautusTauluK (ylivuotoketjuja käyttävä hajautus). 5

4. Kuhunkin erityyppiseen hajautustauluun lisättiin kaikki Aleksis Kiven Seitsemän veljestä-romaanissa Lisäyksiin esiintyneet kulunut sanat. aika Kuvassa 1 on esitetty lisäyksiin kulutettu aika. 135.5 144.7 111.6 112.9 107.5 105.5 111.2 110.7 110.3 111.7 103.9 101.3 121.5 423.5 118.6 117 116.1 112.2 109.4 159.4 144.9 140.2 123.9 117.9 114.3 115.6 110.7 106.6 153.6 106.7 Avoin: universaali, lineaarinen (40000) Avoin: universaali, lineaarinen (50000) Avoin: kertolasku, lineaarinen (40000) Avoin: kertolasku, lineaarinen (50000) Avoin: jakojäännös, lineaarinen (40009) Avoin: jakojäännös, lineaarinen (49999) Avoin: universaali, neliöllinen (40000) Avoin: universaali, neliöllinen (50000) Avoin: kertolasku, neliöllinen (40000) Avoin: kertolasku, neliöllinen (50000) Avoin: jakojäännös, neliöllinen (40009) Avoin: jakojäännös, neliöllinen (49999) Avoin: universaali, kertolasku (40000) Avoin: universaali, kertolasku (50000) Avoin: universaali, kertolasku (50001) Avoin: kertolasku, universaali (40000) Avoin: kertolasku, universaali (50000) Avoin: jakojäännös, kertolasku (40009) Avoin: jakojäännös, kertolasku (49999) Ylivuotokeju: universaali (5000) Ylivuotokeju: universaali (10000) Ylivuotokeju: universaali (15000) Ylivuotokeju: kertolasku (5000) Ylivuotokeju: kertolasku (10000) Ylivuotokeju: kertolasku (15000) Ylivuotokeju: jakojäännös (4999) Ylivuotokeju: jakojäännös (10007) Ylivuotokeju: jakojäännös (15013) TreeSet HashSet 0 100 200 300 400 ms Kuva 1: Lisäyksiin kulunut aika Tarkastelemalla tuloksia havaitaan, että eräs kaksoishajautuksella toteutettu avoin hajautustaulu sattui olemaan erittäin hidas verrattuna muihin. Tämä selittyy luultavasti sillä, että törmäysten yhteensattuessa kaksoishajautuksella saatu kokeilujono on huono. Tämänlainen tilanne syntyy esimerkiksi silloin, jos n:n mittaisessa taulukossa kaksoishajautusfunktio palauttaa arvon n/2. Testeissä avointa hajautusta kokeiltiin myös 30000:n kokoisella taulukolla, mutta ne eivät mahtuneet kuvaan koska suoritus kesti niillä 10-30 kertaa enemmän kuin kuvassa esitetyillä tauluilla. Tämä johtuu siitä, että tällöin kokeilujonot ovat hyvin pitkiä. Kuvassa 2 on esitetty keskimääräinen ylivuotoketjun pituus kun käytetään ylivuotoketjuja. Empiiriset tulokset vahvistavat, että ylivuotoketjut kasvavat taulukon koon pienetessä. Aineistossa oli noin 30000 eri sanaa, joten hajautusfunktiot ovat onnistuneet jakamaan avaimet keskimäärin melko hyvin. 6

Ylivuotoketjujen keskimääräiset pituudet 6.2113 3.255 2.3734 6.2128 3.2512 2.3734 Ylivuotokeju: universaali (5000) Ylivuotokeju: universaali (10000) Ylivuotokeju: universaali (15000) Ylivuotokeju: kertolasku (5000) Ylivuotokeju: kertolasku (10000) Ylivuotokeju: kertolasku (15000) Ylivuotokeju: jakojäännös (4999) Ylivuotokeju: jakojäännös (10007) Ylivuotokeju: jakojäännös (15013) 6.2079 3.2539 2.3705 0 1 2 3 4 5 6 ylivuotoketjun keskimääräinen pituus Kuva 2: Ketjujen keskimääräiset pituudet käytettäessä ylivuotoketjuja 7

5. Edellisessä tehtävässä oli jo vertailtu Javan valmista kalustoa (HashSet, TreeSet) lisäysten tapauksessa. Kuvassa 3 on esitetty hakuihin kulunut aika, kun Seitsemän veljesten sanojen joukosta koitettiin hakea jokaista Dostojevskin Rikos ja Rangaistusromaanin englanninkielisessä versiossa esiintyvää sanaa. Ensimmäiseksi havaitaan, että hakuun kului huomattavasti vähemmän aikaa kuin lisäykseen. Tämä johtuu siitä, että haun yhteydessä ei tarvi jatkuvasti kopioida merkkijonoja talteen. Tuloksissa ei näy silmiinpistäviä eroja. Ehkä kiinnostavin tulos on, että Javan HashSet tuntuisi olevan ainakin lisäyksissä sekä hauissa nopampi kuin TreeSet kun talletettavana on merkkijonoja. Hakuihin kulunut aika 21.3 18 15.5 16 18.7 14.9 16 14.7 16 14.4 15 13.6 22.6 179.4 17.1 17.6 15.6 15.4 14.3 18.4 15.9 16.1 18.4 16.4 15.9 18.5 15.8 15.5 16.6 13.4 Avoin: universaali, lineaarinen (40000) Avoin: universaali, lineaarinen (50000) Avoin: kertolasku, lineaarinen (40000) Avoin: kertolasku, lineaarinen (50000) Avoin: jakojäännös, lineaarinen (40009) Avoin: jakojäännös, lineaarinen (49999) Avoin: universaali, neliöllinen (40000) Avoin: universaali, neliöllinen (50000) Avoin: kertolasku, neliöllinen (40000) Avoin: kertolasku, neliöllinen (50000) Avoin: jakojäännös, neliöllinen (40009) Avoin: jakojäännös, neliöllinen (49999) Avoin: universaali, kertolasku (40000) Avoin: universaali, kertolasku (50000) Avoin: universaali, kertolasku (50001) Avoin: kertolasku, universaali (40000) Avoin: kertolasku, universaali (50000) Avoin: jakojäännös, kertolasku (40009) Avoin: jakojäännös, kertolasku (49999) Ylivuotokeju: universaali (5000) Ylivuotokeju: universaali (10000) Ylivuotokeju: universaali (15000) Ylivuotokeju: kertolasku (5000) Ylivuotokeju: kertolasku (10000) Ylivuotokeju: kertolasku (15000) Ylivuotokeju: jakojäännös (4999) Ylivuotokeju: jakojäännös (10007) Ylivuotokeju: jakojäännös (15013) TreeSet HashSet 0 50 100 150 ms Kuva 3: Hakuihin kulunut aika Iteraattoreita voidaan käyttää Javassa kahdella eri tavalla. Ensimmäisessä tavassa niitä käytetään suoraan Iterator-olion välityksellä. Seuraava koodinpätkä käy läpi TreeSet<String>-tyyppiä olevan puu-muuttujan avaimet kasvavassa järjestyksessä. import java.util.iterator Iterator<String> iter = puu.iterator(); while(iter.hasnext()) 8

System.out.println(iter.next()); Jos Javan olio toteuttaa Iterable-rajapinnan niin iteraattoria voidaan käyttää myös niin kutsutun for-each-silmukan avulla. Seuraava koodi tekee tismalleen saman kuin edellinenkin. //ei tarvitse importata Iterator-luokkaa for(string mjono : puu) System.out.println(mjono); Koska Javan TreeSet on järjestetty joukko niin iteraattori käy avaimet läpi kasvavassa järjestyksessä. Jos sama tehdään HashSet-oliolle niin avaimet tulostuvat mielivaltaisessa järjestyksessä. Kirjojen yhteisten sanojen tulostus ainoastaan kerran voitaisiin suorittaa käyttämällä kahta hajautustaulua. Toiseen talletettaisiin Seitsemässä veljeksessä esiintyvät sanat ja toiseen talletettaisiin jo kertaalleen tulostetut sanat. 6. Voimme vähäisellä vaivalla luoda injektion kokonaislukuja sisältävien binääripuiden joukosta luonnollisille luvuille (0, 1,...). Tämä tarkoittaa sitä, että jokaista puuta vastaa yksikäsitteinen luku. Määritellään apufunktio f, joka kuvaa negatiiviset kokonaisluvut parillisiksi luonnollisiksi luvuiksi ja positiiviset kokonaisluvut parittomiksi luonnollisiksi luvuiksi. f(z) = { 2 z + 1 jos z 0 2 (z + 1) jos z < 0 Selvästikin f on bijektio Z N. Siis f kuvaa jokaista kokonaisluvun z Z eri luonnolliseksi luvuksi, ja jokaiselle n N löytyy z Z siten, että f(z) = n. Käytetään hyväksi tietoa, että jokaisella positiivisella kokonaisluvulla on yksikäsitteinen alkulukuhajotelma. Siis asetetaan binääripuun solmuihin talletettujen avainten arvot funktiolla f alkulukujen eksponenteiksi. Avaimen paikka binääripuussa määrää sen, minkä alkuluvun eksponentiksi kyseinen avain päätyy. Juuri vastaa ensimmäistä alkulukua. Juuren lapset vastaavat toista ja kolmatta alkulukua. Yleisemmin puun tason l solmut vastaavat 2 l :stä alkuluvusta 2 l+1 1:een alkulukuun. Jos jokatin alkulukua vastaavaa solmua ei ole puussa asetetaan sen eksponentiksi 0, jolloin se ei vaikuta tuloon. Kuvan 4 binääripuu kuvattaisiin luvuksi 2 f(19) 3 f(0) 5 f(43) 7 f(2) 11 0 13 f( 13) 17 0 19 0 = 2 39 3 1 5 87 7 5 11 0 13 24 17 0 19 0 = 97232649107490003446431393630366102343032252974808216094970703125 10 39 Tämänlainen kuvaus ei ole vielä bijektio puiden joukosta luonnollisille luvuille, koska löydetään luonnollisia lukuja, jotka eivät vastaa yhtäkään puuta. Esimerkiksi koodi 2 12 3 0 5 0 7 99 ei viittaa yhteenkään puuhun yo. koodauksen mukaisesti. 9

19 0 43 2-13 22 Kuva 4: esimerkki binääripuu Edellisessä esimerkissä nähtiin ettei edellä kuvailtu koodaus ole kovinkaan hyvä, sillä käytettävät luvut kasvavat liian nopeasti, jonka vuoksi käytännön laskeminen on hidasta ja lukujen esittäminen tehotonta tai ongelmallista ylivuotojen vuoksi. Käytäntöön paremmin sopiva menetelmä olisi esimerkiksi muuntaa puut merkkijonoiksi ja sitten käyttää jotain tehtävän 1 tai 2 kaltaista algoritmia kuvaamaan merkkijono kokonaisluvuksi. Eräs kuvan 4 binääripuuta kuvaava merkkijono voisi olla vaikkapa 19 0 43 2-13. Jos käytetään ensimmäisen tehtävän tyylistä algoritmia muuntamaan merkkijono kuvittellisesta lukujärjestelmästä tosiin, niin 12 on sopiva luku, koska käytössä on vain 12 eri merkkiä (numerot, erotin merkki ja - ). Asetetaan Koodi( i ) = i, kun i {0,..., 9, Koodi( ) = 10 ja Koodi( - ) = 11. Tällöin edellinen binääripuu saisi arvokseen 1 12 0 + 9 12 1 + 10 12 2 + 0 11 3 + + 2 12 12 + 10 12 13 Nyt luvut pysyvät huomattavasti pienempinä eikä arvon laskeminen eroa tavallisesta polynomin laskemisesta, joka osataan tehdä lineaarisessa ajassa. 10