Tietorakenteet, laskuharjoitus, ratkaisuja. Seuraava kuvasarja näyttää B + -puun muutokset lisäysten jälkeen. Avaimet ja 5 mahtuvat lehtisolmuihin, joten niiden lisäys ei muuta puun rakennetta. Avain 9 ei mahdu lehtisolmuun, joten lehtisolmu täytyy halkaista ja sisäsolmuun ilmestyy uusi viitta-arvo. Tämän jälkeen avain mahtuu lehtisolmuun ja avaimen 9 lisäys aiheuttaa taas lehtisolmun halkaisun. 5 9 5 5 5 5 9 9 5 5 9 5 5 5 5 9 9 5 5 9 5 9 5 5 5 5 9 9 5 5 9 5 9 5 5 5 5 9 9 5 5 5 9 5 9 5 9 5 5 5 9 9
. Seuraava kuvasarja näyttää B + -puun muutokset lisäysten jälkeen. Avaimet mahtuvat samaan lehtisolmuun. Avaimen 5 lisäyksen jälkeen lehtisolmu täytyy halkaista, jolloin puuhun ilmestyy sisäsolmu. Tämän jälkeen joka toisen avaimen lisäyksen jälkeen lehtisolmu täytyy halkaista. Avaimen lisäyksen jälkeen myös sisäsolmu täytyy halkaista, jolloin sisäsolmuja tarvitaan kaksi kerrosta. 5 5 5 5 5 9 5 9 0 5 9 0 5 9 0
. Seuraava kuvasarja näyttää B + -puun muutokset poistojen jälkeen. Avaimen 5 poisto ei muuta puun rakennetta, koska lehtisolmuun jää riittävästi avaimia. Avaimen poiston jälkeen lehtisolmussa on liian vähän avaimia ja lehtisolmu yhdistetään viereiseen lehtisolmuun, koska avaimet mahtuvat samaan lehtisolmuun. Avaimen poiston jälkeen lehtisolmussa on liian vähän avaimia ja lehtisolmun avaimet tasataan viereisen lehtisolmun kanssa, koska avaimet eivät mahdu samaan lehtisolmuun. 9 5 5 5 5 9 9 9 5 5 5 5 9 9 9 9 5 5 5 5 9 9 5. Avaimen 5 poiston jälkeen vierekkäiset lehtisolmut yhdistetään. Tällöin sisäsolmuun jää vain yksi lapsi ja keskitason sisäsolmut yhdistetään. Tämän seurauksena juurisolmuun jää vain yksi lapsi, jolloin se poistetaan. 9 5 5 5 9 9
. Tutkitaan, kuinka monta alkiota lehtisolmussa voi olla ja kuinka monta lapsiosoitinta sisäsolmussa voi olla. Solmun täytyy mahtua yhteen lohkoon, eli sen koko saa olla korkeintaan kilotavua eli 09 tavua. Yksi lehtisolmussa oleva alkio tarvitsee tavua tilaa avaimelle ja tavua tilaa datakentälle, joten alkio tarvitsee yhteensä tavua tilaa. Lehtisolmuun mahtuu siis korkeintaan 09/ = 5 alkiota. Tästä saadaan t:n arvoksi, mikä tarkoittaa, että yhdessä lehtisolmussa täytyy olla ainakin alkiota. Jokainen sisäsolmun lapsiosoitin tarvitsee tavua tilaa. Lisäksi solmuun täytyy tallentaa avaimia yksi vähemmän kuin lapsiosoittimia ja jokainen niistä tarvitsee tavua tilaa. Sisäsolmuun mahtuu siis korkeintaan (09 )/(+) + = lapsiosoitinta. Tästä saadaan t:n arvoksi, mikä tarkoittaa, että yhdessä sisäsolmussa täytyy olla ainakin lapsiosoitinta. Yritetään nyt muodostaa mahdollisimman korkea B + -puu. Joka vaiheessa solmujen kannattaa antaa olla mahdollisimman tyhjiä, jotta puusta tulee mahdollisimman korkea. Puun juurisolmussa voi olla poikkeuksellisesti vain yksi alkio tai kaksi lapsiosoitinta. Muuten pienimmät sallitut määrät ovat lehtisolmussa alkiota ja sisäsolmussa lapsiosoitinta. Alkioiden vaaditusta määrästä eri korkeuksilla saadaan seuraava taulukko: korkeus alkioita vähintään 0 = 5 = 95 = 9 = 00 Kun puun korkeus on tai suurempi, alkioita on oltava yli 00 miljoonaa, joten suurin mahdollinen puun korkeus on. Tällaisesta puusta alkion hakeminen vie aikaa keskimäärin 0 ms, koska yhden solmun hakeminen vie aikaa keskimäärin 0 ms ja solmuja täytyy hakea. Luennoilla on osoitettu, että jos AVL-puun korkeus on h, siinä on ainakin F h+ alkiota, missä F 0 = 0, F = ja F n = F n + F n, kun n > (Fibonaccin lukusarja). F 9 = 59 ja F 0 = 055, joten suurin korkeus, jossa alkioiden määrä voi olla 00 miljoonaa, on. Jos tällaisesta puusta haetaan mahdollisimman syvällä sijaitsevaa alkiota ja jokainen siirtyminen puussa vaatii erillisen levyhaun, aikaa kuluu keskimäärin 0 ms. Puun alkiot ovat keskittyneet sen alaosaan, joten tämä laskelma antaa suuntaa myös tapaukseen, jossa puusta haetaan satunnaista alkiota.
. Seuraava algoritmi käy läpi puuta, jonka juuri on pelin aloitustilanne, solmut ovat pelin tilanteita ja solmujen lapset vastaavat kiertoja eri tilanteissa. Algoritmi suorittaa joukon peräkkäisiä hakuja ja tutkii joka vaiheessa yhden tason enemmän puussa olevia tilanteita. Algoritmin ensimmäinen haku ei tee kiertoja, toinen haku tekee yhden kierron, kolmas haku tekee kaksi kiertoa jne. Algoritmi päättyy, kun kierrot johtavat haluttuun lopputilanteeseen. Rotation() valmis = False raja = 0 while not valmis Haku(0, raja) 5 raja = raja + Haku(kierto, raja) if ruudukko on järjestyksessä tulosta ratkaisu valmis = True return 5 if kierto == raja return for x = to n for y = to n 9 kierrä myötäpäivään kohdassa (x, y) 0 Haku(kierto +, raja) kierrä vastapäivään kohdassa (x, y) Seuraava kuva esittää hakua, jossa kiertoja tehdään kaksi: 5 9 9 5 5 5 5 9 9 9 9 5 5 5 5 9 9 9 5
Algoritmi löytää lyhimmän kiertosarjan, koska se kasvattaa käsiteltävän puun korkeutta yhden kierron kerrallaan. Osoittautuu, että kaikki x-ruudukot voi ratkaista korkeintaan 5 kierrolla. Tästä seuraa, että myös kaikki suuremmat ruudukot voi ratkaista, koska niiden ratkaisun voi palauttaa helposti x- tapaukseen. Vain x-ruudukkoa ei voi välttämättä ratkaista. Algoritmin aikavaativuus riippuu ratkaisun pituudesta p. Algoritmi haarautuu jokaisen tilanteen kohdalla kokeilemaan k = (n ) eri kiertoa, ja sen työläin vaihe on viimeinen haku, jossa käsiteltävien tilanteiden määrä on yhteensä + k + k + + k p eli luokkaa O(k p ). Tämän vuoksi algoritmi on hyvin hidas, jos lyhin ratkaisu vaatii paljon kiertoja. Algoritmin tilavaativuus on kuitenkin vain rekursiosta johtuva O(p). Yksi keino nopeuttaa algoritmia on arvioida haun edetessä sopivalla heuristiikalla, kuinka monta kiertoa vielä vähintään tarvitaan ratkaisuun pääsemiseksi. Tällöin hakupuussa ei tarvitse jatkaa eteenpäin, jos käsiteltävä tilanne ei voi johtaa ratkaisuun ottaen huomioon annetun kiertorajan. Yksinkertainen heuristiikka perustuu seuraavaan havaintoon: jokainen kierto voi parhaimmillaan siirtää neljää lukua kohti niiden oikeaa paikkaa. Siis varma alaraja vielä tarvittavien kiertojen määrälle saadaan laskemalla yhteen lukujen matkat oikeille paikoilleen ja jakamalla tulos neljällä. Seuraavassa kuvassa on kaksi ruudukkoa: toinen on mahdollinen haussa esiintyvä tilanne, ja toinen kertoo, kuinka pitkä matka vastaavissa kohdissa olevilla luvuilla on oikeille paikoilleen. 9 5 0 Esimerkiksi luku 9 voi päästä oikealle paikalleen liikkumalla kaksi askelta alas ja yhden oikealle eli yhteensä kolme askelta. Tästä saadaan varma alaraja tarvittavalle kiertojen määrälle: ( + + + 0 + + + + + )/ =. Yleensä kiertoja tarvitaan tätä enemmän, koska ei ole luultavaa, että jokainen kierto liikuttaa ihanteellisesti lukuja oikeaan suuntaan. Jos yllä olevaan tilanteeseen olisi päästy 5 kierron jälkeen ja kiertoraja olisi, hakua ei olisi syytä jatkaa eteenpäin. Nimittäin vaikka kiertojen vaikutus olisi paras mahdollinen, kiertojen yhteismäärä olisi silti 5 + = 9. Heuristiikaksi kelpaa mikä tahansa nopeasti laskettava alaraja kiertojen määrälle: mitä lähempänä arvio on oikeaa tulosta, sitä vähemmän tilanteita täytyy tutkia. Yllä olevan yksinkertaisen heuristiikan lisääminen hakuun on helppoa, ja se nopeuttaa usein algoritmia merkittävästi.
. Seuraava algoritmi käy läpi puuta, jonka juuri on tyhjä ruudukko, solmut ovat osittain luvuilla täytettyjä ruudukkoja ja solmujen lapset vastaavat eri tapoja sijoittaa luku ruudukon seuraavaan kohtaan. Algoritmi laskee mukaan ne puun lehtisolmut, jotka ovat taikaneliöitä eli joissa kunkin pystyrivin, vaakarivin ja lävistäjän lukujen summa on sama. Taikanelio() maara = 0 Haku(, ) print maara Haku(x, y) if y == n + / ruudukko on täytetty if ruudukko on taikaneliö maara = maara + return 5 if x == n + / vaakarivi on täytetty Haku(, y + ) return for i = to n 9 if i ei ole ruudukossa 0 ruudukko[x][y] = i Haku(x +, y) Seuraavassa kuvassa näkyvät osittain kaksi puun ensimmäistä tasoa: 5 9 5 9
Algoritmi haarautuu puun ensimmäisellä tasolla n osaan, toisella tasolla n osaan, kolmannella tasolla n osaan jne. Käsiteltävien ruudukkojen määrä on + (n ) + (n )(n ) + (n )(n )(n ) + eli luokkaa O((n )!). Ruudukkojen määrä on hyvin suuri pienilläkin n:n arvoilla. Algoritmia voi tehostaa laskemalla haarautumisen sijaan suoraan, mitkä luvut tulevat kunkin vaakarivin viimeiseen ruutuun sekä mitkä luvut tuleva viimeiselle vaakariville. Tietyllä n:n arvolla rivien yhteinen summa on aina sama: ruudukon lukujen summa on yhteensä n (n + )/ ja luvuista pitää muodostaa n samaa summaa, joten yhteinen summa on n (n + )/. Esimerkiksi kun n =, yhteinen summa on ( + )/ =. Lisäksi on mahdollista ottaa huomioon ruudukoissa esiintyvät symmetriat: jokaiseen taikaneliöön liittyy seitsemän muuta taikaneliötä, jotka saadaan kiertämällä ja peilaamalla ruudukkoa. Tämän vuoksi algoritmin riittää muodostaa jokaisesta kahdeksan symmetrisen taikaneliön ryhmästä vain yksi edustaja ja kertoa lopputulos kahdeksalla. Erikokoisten taikaneliöiden määrät ovat seuraavat: koko (n) määrä 0 00 5 09 Tapaukset n = ja n = ratkeavat yllä olevilla menetelmillä. Tapaus n = 5 vaatii tehokkaamman algoritmin, joka rajoittaa hakua paremmin. Tapaukset n > 5 ovat avoimia ongelmia, eli kukaan ei ole pystynyt laskemaan niiden vastauksia. Suurempia taikaneliöitä on olemassa hyvin paljon: esimerkiksi kun n =, lukumäärän on arvioitu olevan noin, 0 0.