Tietorakenteet ja algoritmit II Turun yliopisto, Informaatioteknologian laitos, periodi 3 / 2012 Lasse Bergroth Kurssin sisältö • Kurssi perustuu oppikirjaan … Cormen, T. H. – Leiserson, C. E – Rivest, R. L – Stein, C.: ”Introduction to Algorithms”, 3. painos, MIT press (2009) • … sekä Jouni Järvisen vuonna 2007 kirjoittamaan luentomonisteeseen. • Kyseinen, lähes 1300-sivuinen kirja löytyy myös verkosta brasilialaisen Recifessä sijaitsevan Pernambucon yliopiston sivuilta osoitteesta http://www.cin.ufpe.br/~ass4/ALGORITMOS/ALG_3rd.pdf. Kurssin sisältö (jatkoa) Sisällysluettelo • Luentokalvoissa on mahdollisuuksien mukaan käytetty samaa osa- ja lukunumerointia kuin oppikirjassa III Tietorakenteet 12. 13. Binääriset hakupuut - toteutus, eri selausjärjestykset - avaimien lisäys ja poisto Punamustat puut - tavoitteena binäärisen hakupuun tasapainottaminen - toteutus: väriattribuutin lisäys binääriseen hakupuuhun 13U. AVL-puut - vaihtoehtoinen tapa hakupuun tasapainottamiseksi - väriattribuutin tilalle lisätty tieto solmun alipuiden välisestä korkeuserosta - esitellään kirjassa vain lyhyesti 14. Laajennetut tietorakenteet - järjestysstatistiikkapuun muodostaminen punamustaan puuhun perustuen - tavoitteena tehostaa i. pienimmän alkion hakuaikaa luvussa 9 esitetystä Kurssin sisältö (jatkoa) V Edistyneitä tietorakenteita 18. 19X. 19. 21. B-puut - tietorakenne isojen datamäärien tallentamista varten - muutos luvussa III esitettyihin tasapainotettuihin puihin: samaan solmuun voidaan tallentaa useita avaimia Binomikeot - keolle vaihtoehtoinen tietorakenne prioriteettijonojen ylläpitoa varten - lukua ei esiinny kirjan uusimmassa painoksessa Fibonacci-keot - ennen luvun käsittelyä tarkastellaan lyhyesti kirjan luvussa 16 esiteltävää tasoitetun kustannuksen laskentaa - binomikekoja tehokkaampi tietorakenne prioriteettijonojen operaatioita varten tasoitetulla kustannuksella mitattuna Tietorakenteita erillisille joukoille - toimii johdatuksena graafialgoritmeihin VI Graafialgoritmeja 22. – 24. Tiivistelmä graafialgoritmeista - syvyys- ja leveyshaku - minimaalinen virittävä puu - lyhimpien polkujen määrääminen graafin yhdestä pisteestä lähtien 12 Binäärinen hakupuu 12.1 Mikä on binäärinen hakupuu? • Binäärinen hakupuu on tietynlainen binääripuu sille on määritelty ainakin seuraavat operaatiot: 1) avainarvon haku 2) alkion lisääminen 3) alkion poistaminen 4) minimin etsintä 5) maksimin etsintä 6) alkion edeltäjän määrääminen 7) alkion seuraajan määrääminen • Binääripuun solmualkiolla x on olemassa seuraavat attribuutit: avain[x]: esittää solmualkion x avainkentän arvoa vasen[x]: sisältää osoittimen solmun x vasempaan lapsisolmuun oikea[x]: sisältää osoittimen solmun x oikeaan lapsisolmuun vanhempi[x]: sisältää osoittimen solmun x isäsolmuun jos x on puun juuri, on isäosoitin arvoltaan NIL • Lisäksi itse binääripuulle P on määritelty attribuutti: juuri[P]: sisältää osoittimen puun P juuressa sijaitsevaan solmualkioon jos juuri[P] = NIL, kyseessä on tyhjä puu 12.1 Mikä on binäärinen hakupuu? • Binäärinen hakupuu -ominaisuus: Oletetaan, että x on jokin binääriseen hakupuuhun P kuuluva solmu jos jokin toinen puun P solmu y kuuluu x:n vasempaan alipuuhun, niin tällöin on voimassa avain[y] ≤ avain[x] vastaavasti, jos y kuuluu x:n oikeaan alipuuhun, niin avain[y] ≥ avain[x] Esimerkki binäärisestä hakupuusta: 24 16 7 51 19 29 43 12.1 Mikä on binäärinen hakupuu? • Tarkastellaan seuraavassa binääripuuta, jonka korkeus on h: Ensimmäiselle (ylimmälle) tasolle mahtuu 1 = 20 alkiota Toiselle tasolla mahtuu 2 = 21 alkiota Kolmannelle tasolle mahtuu 4 = 22 alkiota … Alimmalle tasolle (lehtiin) mahtuu 2h alkiota • Siten täyteen h-korkuiseen puuhun mahtuu alkioita: n = 20 + 21 + 22 + … + 2h = ∑ 2 = 2h + 1 – 1 kappaletta. • Tällöin 2h + 1 = n + 1 ja h + 1 = log2(n + 1). • Siten täyden binäärisen hakupuun korkeus h ≤ log2n, sillä h = log2(n + 1) – 1 ≤ log2(n + n) – 1 /* Silloin, kun n ≥ 1 */ = log2(2n) – 1 = log2n + log22 – 1 = log2n + 1 – 1 = log2n. 12.1 Mikä on binäärinen hakupuu? • Seuraavassa esitellään tärkeimmät tavat binäärisen hakupuun solmujen läpikäymiseksi. Oletetaan, että puun solmujen avainarvot halutaan tulostaa. 1) Välijärjestyskulku (solmu käsitellään heti, kun sen koko vasen alipuu on käsitelty, ja ennen oikeaan alipuuhun siirtymistä) Kulje vasen alipuu välijärjestyksessä Vieraile juuressa Kulje oikea alipuu välijärjestyksessä VÄLIJÄRJESTYSKULKU(x) 1 IF x ≠ NIL 2 THEN VÄLIJÄRJESTYSKULKU(vasen[x]) 3 Tulosta(avain[x]]) 4 VÄLIJÄRJESTYSKULKU(oikea[x]) • Välijärjestyksen mukaisesti etenemällä saadaan listattua binäärisen hakupuun solmut kasvavassa (ei-vähenevässä) järjestyksessä. 12.1 Mikä on binäärinen hakupuu? 2) Esijärjestyskulku (solmu käsitellään heti, kun siihen saavutaan ensimmäistä kertaa) Vieraile juuressa Kulje vasen alipuu esijärjestyksessä Kulje oikea alipuu esijärjestyksessä 3) Loppujärjestyskulku (solmu käsitellään vasta, kun sen molemmat alipuut on jo käsitelty) Kulje vasen alipuu loppujärjestyksessä Kulje oikea alipuu loppujärjestyksessä Vieraile juuressa Kohtien 2) ja 3) pseudokoodit voidaan muodostaa helposti kohdan 1) perusteella. Kannattaa huomioida, että triviaalina tapauksena pidetään sitä, kun osoitin x saa arvon NIL. • Lause 12.1: Jos x on n-solmuisen binääripuun juuri, niin kutsun VÄLIJÄRJESTYSKULKU(x) suorittaminen vie ajan Θ(n). Todistus: Välijärjestyskulun suoritusaikaa kuvaa rekursioyhtälö T(n) = T(k) + T(n – k – 1) + d, missä k on vasemman alipuun koko ja d on jokin vakio, joka kuvaa yhden VÄLIJÄRJESTYSKULKU-algoritmin kutsun suorittamiseen menevää aikaa ilman rekursiivisia kutsuja (ts. d kuvaa yksittäisen solmun käsittelyyn kuluvaa aikaa). 12.1 Mikä on binäärinen hakupuu? Induktiolla pystytään helposti osoittamaan, että T(n) = Ο(n): T(n) = T(k) + T(n – k – 1) + d ≤ c1k + c1(n – k – 1) + d = c1n – (c1 – d) ≤ c1n /* Silloin, kun c1 ≥ d */ Toisaalta on myös selvää, että T(n) ≥ c2n jollain vakiolla c2, sillä jokaisessa solmussa joudutaan vierailemaan kertaalleen. Täten T(n) = Θ(n). 12.2 Kyselyt • Seuraavassa tarkastellaan binäärisiin hakupuihin kohdistuvia kyselyalgoritmeja. • Rekursiivisessa hakualgoritmissa muuttuja x toimii osoittimena puun juureen, ja k on etsittävä avain. HAE_BINÄÄRISESTÄ_HAKUPUUSTA(x, k) 1 IF x = NIL OR k = avain[x] 2 THEN RETURN x 3 IF k < avain[x] 4 THEN RETURN HAE_BINÄÄRISESTÄ_HAKUPUUSTA(vasen[x], k) 5 ELSE RETURN HAE_BINÄÄRISESTÄ_HAKUPUUSTA(oikea[x], k) 12.2 Kyselyt • • • Hakualgoritmi voidaan helposti muuntaa iteratiiviseksi: HAE_BINÄÄRISESTÄ_HAKUPUUSTA_ITER(x, k) 1 WHILE x ≠ NIL AND k ≠ avain[x] DO 2 IF k < avain[x] 3 THEN x := vasen[x] 4 ELSE x := oikea[x] 5 RETURN x Ratkaisutavasta riippumatta haun kustannus on Ο(h), missä h on puun korkeus Puussa tarkastellaan joka tasolla yhtä alkiota Parhaassa tapauksessa pelkän juurisolmun tutkiminen riittää (puu tyhjä, tai etsitty alkio löytyy heti juuresta) Pahimmassa tapauksessa kuljetaan jokin polku juuresta aina kaukaisimpaan lehteen asti Minimin etsintä: Ensimmäisessä kutsussa syöteparametrina annetaan osoitin juuri[P]. BINÄÄRISEN_HAKUPUUN_MINIMI(x) 1 IF x ≠ NIL DO 2 WHILE vasen[x] ≠ NIL DO 3 x := vasen[x] 4 RETURN x 5 ELSE virheilmoitus ”Puu tyhjä: minimiä ei ole määritelty.” Algoritmi etsii puusta kaikkein vasemmanpuoleisimman alkion (edetään vasemmalle niin kauan kuin pystytään). 12.2 Kyselyt • Maksimin etsintä: BINÄÄRISEN_HAKUPUUN_MAKSIMI(x) 1 IF x ≠ NIL DO 2 WHILE oikea[x] ≠ NIL DO 3 x := oikea[x] 4 RETURN x 5 ELSE virheilmoitus ”Puu tyhjä: maksimia ei ole määritelty.” Algoritmi etsii puusta kaikkein oikeanpuoleisimman alkion (edetään oikealle niin kauan kuin pystytään). • Edellä esitettyjen minimin- ja maksiminhakualgoritmien kompleksisuus on Ο(h), sillä niissä käydään tarkalleen yksi polku juuresta vasemmalle (tai oikealle) niin pitkälle, kunnes polku päättyy eli vasenta (oikeaa) lapsisolmua ei enää ole olemassa. • Pahimmassa tapauksessa joudutaan etenemään juurisolmusta kaukaisimpaan lehteen asti. • Seuraajan ja edeltäjän etsintä: Seuraajan määritelmä: Jos puun alkiot lajiteltaisiin avainten mukaan ei-vähenevään suuruusjärjestykseen, solmun x seuraaja olisi solmu, joka sisältäisi x:n avainarvoa lähinnä suuremman (tai yhtä suuren) arvon. 12.2 Kyselyt • Solmun x seuraajan hakemisen idea: Jos solmun x oikea alipuu ei ole tyhjä, niin x:n seuraaja on sen oikean alipuun pienin alkio, sillä kaikki x:ää suuremmat sijaitsevat sen oikeassa alipuussa, ja valitaan niistä se, jonka avainarvo on pienin. Seuraaja on nyt siis x:n oikean alipuun vasemmanpuoleisin solmu. Jos x:n oikea alipuu on kuitenkin tyhjä, joudutaan puussa nousemaan taso kerrallaan ylöspäin, kunnes kohdataan ensimmäinen solmu y, jonka vasempaan alipuuhun x kuuluu. Kyseisessä alkiossa y on nyt avainarvo, joka on lähinnä avain[x]:ää suurempi, joten y on siten x:n seuraaja. Ellei tällaista alkiota y ole olemassa, seuraajaa ei ole määritelty, vaan tuolloin solmussa x on puun arvoltaan suurin avain, jolloin palautetaan tyhjä osoitin NIL. SEURAAJA_BINÄÄRISESSÄ_HAKUPUUSSA(x) 1 IF oikea[x] ≠ NIL DO 2 THEN RETURN BINÄÄRISEN_HAKUPUUN_MINIMI(oikea[x]) 3 y := vanhempi[x] 4 WHILE y ≠ NIL AND x = oikea[y] DO 5 x := y 6 y := vanhempi[y] 7 RETURN x Algoritmissa edetään solmusta x jotain polkua aina yhteen suuntaan joko alas- tai ylöspäin. ⇒ Aikakompleksisuudeksi saadaan siten Ο(h). • Solmun x edeltäjä määräytyy puolestaan peilikuvamaisesti edellä esitettyyn nähden seuraavasti: Jos solmulla x on ei-tyhjä vasen alipuu, x:n edeltäjä on solmu, jossa sijaitsee vasemman alipuun maksimi. Kyseinen solmu on x:n vasemman alipuun oikeanpuoleisin solmu. Ellei x:llä ole vasenta lasta, joudutaan puussa nousemaan ylöspäin niin kauan, kunnes kohdataan ensimmäinen solmu y, jonka oikeaan alipuuhun x kuuluu. Kyseisessä alkiossa on nyt avainarvo, joka on lähinnä pienempi kuin x:n avainarvo. Ellei tällaista alkiota y ole olemassa, edeltäjää ei ole määritelty, vaan tuolloin solmussa x sijaitsee arvoltaan puun pienin avain. Tuolloin palautetaan tyhjä osoitin NIL. 12.3 Lisäys ja poisto • • • Tarkastellaan ensiksi alkion lisäämistä binääriseen hakupuuhun. Puuhun lisätään sellainen solmualkio z, jolle on voimassa: avain[z] = v (jokin avaimen tyypin mukainen arvo) vasen[z] = NIL oikea[z] = NIL Lisäysalgoritmin toiminta-ajatus: Etsitään sellainen solmu y, jonka lapseksi lisättävä solmu z avainarvonsa puolesta kelpaa ja lisätään z oikealle paikalleen y:n joko vasemmaksi tai oikeaksi pojaksi. LISÄÄ_BINÄÄRISEEN_HAKUPUUHUN(P, z) 1 y := NIL 2 x := juuri[P] 3 WHILE x ≠ NIL DO 4 y := x 5 IF avain[z] < avain[x] 6 THEN x := vasen[x] 7 ELSE x := oikea[x] 8 vanhempi[z] := y 9 IF y = NIL 10 THEN juuri[P] = z /* Lisäys tapahtuu tyhjään puuhun. */ 11 ELSE IF avain[z] < avain[y] 12 THEN vasen[y] := z 13 ELSE oikea[y] := z Algoritmissa edetään juuresta jokin polku alaspäin. ⇒ Aikakompleksisuudeksi saadaan siten Ο(h). 12.3 Lisäys ja poisto • Seuraavaksi esitetään, miten tapahtuu solmualkion z poistaminen binäärisestä hakupuusta. • Poistossa joudutaan huomioimaan kolme eri tapausta: 1) z on lehtisolmu: z poistetaan pelkästään isäsolmun viittauksia päivittämällä 2) z:lla on vain yksi lapsi: z ohitetaan päivittämällä sen ainoan lapsisolmun ja isäsolmun linkkejä 3) z:lla on molemmat lapset: z:n seuraajalla y ei ole tällöin vasenta lasta (minkä tähden?) z:n seuraaja y on seuraajan määritelmän perusteella nyt selvästikin z:n oikean alipuun minimi, koska z:lla on molemmat lapset talletetaan seuraajan y avainarvo (ja mahdollinen satelliittidata) ja poistetaan seuraajaalkio y kopioidaan seuraajan tiedot poistettavan alkion z tietojen päälle. 12.3 Lisäys ja poisto • Solmun poistamisalgoritmi on esitetty seuraavassa: POISTA_BINÄÄRISESTÄ_HAKUPUUSTA(P, z) 1 IF vasen[z] = NIL OR oikea[z] = NIL 2 THEN y := z 3 ELSE y := SEURAAJA_BINÄÄRISESSÄ_HAKUPUUSSA(z) 4 IF vasen[y] ≠ NIL 5 THEN x := vasen[y] 6 ELSE x := oikea[y] 7 IF x ≠ NIL 8 THEN vanhempi[x] := vanhempi[y] 9 IF vanhempi[y] = NIL 10 THEN juuri[P] = x 11 ELSE IF y = vasen[vanhempi[y]] 12 THEN vasen[vanhempi[y]] := x 13 ELSE oikea[vanhempi[y]] := x 14 IF y ≠ z 15 THEN avain[z] := avain[y] 16 kopioidaan samalla myös y:n mahdollinen satelliittidata solmuun z 17 RETURN y 12.3 Lisäys ja poisto • • • • Algoritmin toimintaperiaate on seuraavanlainen: Riveillä 1 – 3 määräytyy solmu, joka poistetaan puusta fyysisesti Jos poistettavaksi tarkoitetulta solmulta z puuttuu ainakin toinen lapsista, kopioidaan osoitin y osoittamaan solmuun z. Jos z:lla on molemmat lapset olemassa, merkitään y osoittamaan z:n seuraajaa, jolla ei tässä tilanteessa voi olla vasenta lasta. Riveillä 4 – 6 asetetaan x osoittamaan siihen y:n lapseen, joka ei ole NIL (nyt jompikumpi y:n lapsista puuttuu väkisin). Mikäli tällaista solmua ei ole olemassa, tehdään x:stä solmun y oikea lapsi. Riveillä 7 – 13 alkio y poistetaan solmujen vanhempi[y] ja x osoittimia päivittämällä Rivejä 14 – 16 tarvitaan, jos poistettavaksi alkioksi määräytyi z:n seuraaja. Tällöin y:n tietokenttien sisältö kopioidaan solmuun z. Rivillä 17 palautetaan vielä osoitin fyysisesti poistettavaan solmuun y, jotta sille dynaamisesti varattu muistitila voidaan vapauttaa. Alkion poistamisen kompleksisuus on sama kuin lisäämisen, eli Ο(h). Lause 12.2: Operaatioiden haku, minimin etsintä, maksimin etsintä, seuraajan hakeminen ja edeltäjän hakeminen aikakompleksisuus on Ο(h), missä h on binäärisen hakupuun korkeus. joudutaan pahimmassa tapauksessa kulkemaan juuresta kaukaisimpaan lehteen tai päinvastoin. Lause 12.3: Myös operaatioiden lisäys ja poisto aikakompleksisuus on samoin Ο(h). haun osuuden kustannus samat kuin edellä, ja linkkien päivitykset ovat vakioaikaisia 12.3 Lisäys ja poisto • Miten voidaan arvioida kustannustermin Ο(h) suuruutta? Kuten jo edellä todettiin, täyden binäärisen hakupuun korkeus h ≤ log2n. Pulmallista on, että usein on mahdotonta aavista etukäteen, millaiseksi puun rakenne kehittyy. Varoittava esimerkki: tallennetaan alkiot 11, 15, 24, 29, 39, 71 ja 93 mainitussa järjestyksessä binääriseen hakupuuhun. Saadaan seuraavanlainen hakupuu (syöttöjärjestys määrää yksikäsitteisesti sen, miltä puu tulee näyttämään lisäyksen jälkeen): 11 juuri[P] NIL 15 NIL 24 29 NIL 39 NIL 71 NIL NIL 93 NIL NIL • Hakupuusta muodostuukin juuresta oikealle aukeava lista, jolloin Ο(h) = Ο(n)! Puu pitäisi pystyä jollain tavoin tasapainottamaan, jotteivät operaatioiden suoritusajat heikkenisi lineaarisiksi logaritmisista, jollaiset ne olisivat täydelle puulle. 13 Punamustat puut • Punamusta puu esiteltiin ensimmäistä kertaa vuonna 1972 artikkelissa Bayer, Rudolf: Symmetric binary B-trees: data structure and maintenance algorithms, Acta Informatica I (1972), sivut 290 – 306 • Edellisen luvun perusteella muistetaan, että tavallisessa binäärisessä hakupuussa operaatiot haku, lisäys ja poisto voidaan suorittaa ajassa O(h), missä h on puun korkeus. • Pulmaksi muodostuu tosiasia, että alkioiden syöttöjärjestys vaikuttaa ratkaisevasti muodostuvan puun muotoon. Jos vaikkapa lisätään alun perin tyhjään puuhun järjestyksessä avaimet n, n – 1, …, 1, kehkeytyy binäärisestä hakupuusta tällöin vasemmalle aukeava lista. Vastaavasti päinvastainen syöttöjärjestys tuottaisi juuresta oikealle aukeavan listamaisen rakenteen. Mikäli kaikki puuhun tallennetut avaimet sijaitsevat hakupuussa listamaisesti, tulee puun korkeudeksi nyt selvästikin n – 1, mikä johtaa pahimmassa tapauksessa Ο(n)-mittaiseen suoritusaikaan, kun taas täydestä n avainta sisältävästä puusta hakuaika olisi Ο(log2n). On siten kannattavaa pyrkiä tasapainottamaan puu jollain tavalla! 13.1 Punamustien puiden ominaisuuksia • Samoin kuin binäärisessä hakupuussa, niin myös punamustassa puussa ovat käytettävissä seuraavat solmualkion x attribuutit: avain[x], vasen[x], oikea[x] ja vanhempi[x]. • Punamustien puiden solmuilla on näiden lisäksi käytössä attribuutti väri[x]. värin on oltava joko punainen tai musta, ja se voidaan ilmaista yhdellä lisäbitillä. 13.1 Punamustien puiden ominaisuuksia • Punamustan puun tulee täyttää luvussa 12 esitettyjen binäärisen hakupuun ominaisuuksien lisäksi seuraavat viisi kriteeriä: • • • • • 1) Jokainen solmu on joko punainen tai musta. 2) Puun juuri on musta. 3) Puun jokainen lehtisolmu (NIL-solmu) on musta 4) Puussa ei saa esiintyä kahta punaista solmua peräkkäin jos solmu on punainen, ovat sekä sen isäsolmu että lapset välttämättä mustia 5) Kuljettaessa mielivaltaisesta punamustapuun solmusta kyseisen alipuun lehtiin kohdataan matkalla aina sama määrä mustia solmuja riippumatta tehdystä etenemispolun valinnasta. Lisäksi koko puulla on käytettävissä attribuutti juuri[P] eli osoitin puun juureen. Punamustapuussa ainoastaan sisäsolmut ovat sisältönsä kannalta mielenkiintoisia, sillä ne sisältävät hakuavaimen sekä mahdollista lisä- eli satelliittidataa. Ulko- eli lehtisolmut toimivat puolestaan yksinomaan pysäytysalkioina (ei dataa säilöttynä). Koska lehtisolmuilla ei ole mitään muuta hyötykäyttöä kuin polun loppumisen tunnistaminen, voidaan kaikki lehtisolmut yhdistää käsitteellisti yhdeksi alkioksi. Seuraavassa esitetään esimerkki punamustasta puusta, johon on tallennettu 20 avainta – ensiksi kaikkine lehtisolmuineen ja myöhemmin yhdistetyn pysäytysalkion avulla. jälkimmäiseen kuvaan on lisätty myös tieto solmun ns. mustasta korkeudesta numeromerkinnällä 13.1 Punamustien puiden ominaisuuksia 26 17 41 14 10 19 16 NIL 7 3 NIL 15 12 NIL NIL NIL NIL NIL NIL 28 23 NIL NIL NIL NIL NIL 39 35 NIL NIL NIL 38 20 NIL 47 30 21 NIL NIL NIL NIL 13.1 Punamustien puiden ominaisuuksia 3 26 3 2 17 2 2 14 7 10 16 1 1 15 12 2 47 1 19 23 28 1 38 20 NIL[P] 1 1 1 3 1 1 30 21 1 1 2 1 41 35 1 39 13.1 Punamustien puiden ominaisuuksia • Solmun x mustalla korkeudella mk(x) tarkoitetaan matkalla vastaan tulevien mustien solmujen lukumäärää, kun kuljetaan solmusta x alaspäin mihin tahansa ulkosolmuun (tai edellisen kuvan yhdistettyyn pysäytyssolmuun NIL[P]. solmua x itseään ei lasketa mukaan mustaan korkeuteen, mutta kylläkin lehtisolmu. • Mustan korkeuden voidaan sanoa olevan hyvin määritelty, sillä punamustan puun määritelmän kohdan 5 mukaan se on aina sama riippumatta siitä, mitä polkua pitkin lehtitasolle edetään. • Koko puun P musta korkeus on juuren musta korkeus. • Lemma 13.1: Punamustapuun korkeus h ≤ 2 log2(n + 1) Todistus: Määritelmän perusteella vähintään puolet solmuista pitää olla mustia, kun matkustetaan juuresta mihin tahansa lehteen, sillä 1) Punaista solmua edeltää ja seuraa aina musta solmu 2) Jokaista polulla esiintyvää punaista solmua kohti on olemassa siten vähintään yksi musta solmu. Tarkastellaan nyt pisintä polkua juuresta lehteen. Kyseisellä polulla h = mustien solmujen lukumäärä + suurin esiintyvä punaisten solmujen määrä - 1. • • mustien lukumäärä = mk(P) punaisten lukumäärä ≤ mk(P) Kannattaa huomioida, ettei pysäytyssolmuja lasketa mukaan puun normaaliin korkeuteen h. Siten h ≤ mk(P) + mk(P) - 1 ≤ 2mk(P), joten mk(P) ≥ h/2. Jos nyt ajatellaan puuta, jossa on vain mustia solmuja, saadaan: 13.1 Punamustien puiden ominaisuuksia n ≥ 20 + 21 + 22 + … + 2mk[P] - 1 = 2mk[P] - 1 Tästä seuraa, että n ≥ 2mk[P] - 1 ≥ 2h/2 - 1. Kun tämä ratkaistaan edelleen h:n suhteen, saadaan: log2(n + 1) ≥ h/2 ⇔ h ≤ 2log2(n + 1). • Edellisen lemman perusteella siis h ≤ 2log2(n + 1), josta saadaan edelleen h ≤ 2log2(n + 1) ≤ 2 log2(3n/2) /* kun n ≥ 2 */ = 2 log2(3/2) + 2log2n = log2(9/4) + 2log2n ≤ log2n + 2log2n /* kun n ≥ 3 */ = 3 log2n Siten h = Ο(log2n), ja binäärisen hakupuun operaatiot haku, minimin etsintä, maksimin etsintä, seuraaja ja edeltäjä voidaan suorittaa nyt ajassa Ο(log2n). 13.1 Punamustien puiden ominaisuuksia • Kannattaa kuitenkin huomioida, että puun muoto muuttuu sitä mukaa, kun siihen lisätään uusia solmuja tai kun solmuja poistetaan. Pulma: Miten voidaan taata, että punamustapuun ominaisuudet säilyvät voimassa myös alkion lisäys- ja poisto-operaatioiden jälkeen? • Ratkaisu ongelmaan: kierto-operaatiot eli rotaatiot • Kierrolla tarkoitetaan paikallista operaatiota, jonka avulla tilapäisesti rikkoutunut punamustapuu saadaan korjattua takaisin määritelmän vaatimukset täyttäväksi. • Käytettävissä ovat vasen ja oikea kierto. • Kiertoja suoritettaessa joudutaan tekemään muutoksia solmujen väriattribuuttien arvoihin. erilaisia huomioitavia tilanteita on useita 13.2 Kierto-operaatiot • Paikallisia operaatioita, jotka säilyttävät binäärisen hakupuuominaisuuden. Hahmotelma: x a≤x≤b≤y≤c vasen kierto kohtaan x oikea kierto kohtaan y y a c x y b c a b 13.2 Kierto-operaatiot • Esitellään seuraavaksi solmulle x vasemman kierron suorittava algoritmi. VASEN_KIERTO(P, x) 1 y := oikea[x] /* Asetaan solmu y solmun x oikeaksi lapseksi. */ 2 oikea[x] := vasen[y] /* Solmun y vasen alipuu siirretään x:n oikeaksi alipuuksi. */ 3 IF vasen[y] ≠ NIL[P] 4 THEN vanhempi[vasen[y]] := x 5 vanhempi[y] := vanhempi[x] /* Solmun x isäsolmusta tulee nyt y:n isäsolmu. */ 6 IF vanhempi[x] = NIL[P] 7 THEN juuri[P] := y 8 ELSE IF x = vasen[vanhempi[x]] 9 THEN vasen[vanhempi[x]] := y 10 ELSE oikea[vanhempi[x]] := y 11 vasen[y] := x 12 vanhempi[x] := y • Oikea kierto tapahtuu symmetrisesti (kts. edellinen esimerkkikuva) • Kumpikin kierto-operaatio saadaan suoritettua vakioajassa Θ(1). puun koko ei millään tavalla vaikuta algoritmin tekemien operaatioiden määrään. 13.2 Kierto-operaatiot • Seuraavaksi esiteltävä algoritmi lisää uuden solmun z punamustaan puuhun P. LISÄÄ_PUNAMUSTAAN_PUUHUN(P, z) 1 y := NIL[P] 2 x := juuri[P] 3 WHILE x ≠ NIL[P] DO 4 y := x 5 IF avain[z] < avain[x] 6 THEN x := vasen[x] 7 ELSE x := oikea[x] 8 vanhempi[z] := y 9 IF y = NIL[P] 10 THEN juuri[P] = z /* Lisäys tapahtuu tyhjään puuhun. */ 11 ELSE IF avain[z] < avain[y] 12 THEN vasen[y] := z 13 ELSE oikea[y] := z 14 vasen[z] := NIL[P] 15 oikea[z] := NIL[P] 16 väri[z] := punainen 17 KORJAA_LISÄYS_PUNAMUSTAPUUHUN(P, z) /* Solmun z lisäys saattoi rikkoa punamustan puun määritelmän mukaisia ominaisuuksia. */ 13.2 Kierto-operaatiot • Edellä esitetty, solmun z punamustaan puuhun P lisäävä algoritmi toimii täysin samalla periaatteella kuin tavalliseen binääriseen hakupuuhun solmun lisäävä algoritmi (esiteltiin luvussa 12). • Ainoat muutokset tapahtuvat algoritmin neljällä viimeisellä rivillä: 1) lisätyn solmun z lapsilinkit asetetaan osoittamaan pysäytyssolmuun NIL[P] 2) lisättävälle solmulle määrätään väri, ja se on aina punainen 3) lopuksi kutsutaan punamustapuun korjausalgoritmia • Kannattaa huomioida, että koska uusi solmu värjätään alussa punaiseksi, ei solmun lisääminen tuo puuhun koskaan uutta mustaa solmua. Punamustapuun määritelmän ehdot 1, 3, ja 5 pitävät edelleen paikkansa. • Sen sijaan voi syntyä kaksi pulmatilannetta: 1) kun tyhjään puuhun lisätään ensimmäinen solmu, tulee siitä samalla puun juuri, joka värjätään nyt punaiseksi vastoin määritelmän sääntöä 2 2) jos puu oli alun perin ei-tyhjä, mutta z:n isäsolmu y on punainen, puuhun tulee nyt kaksi punaista solmua peräkkäin, mikä rikkoo määritelmän sääntöä 4. • Pulmatilanteista toipuminen: Tilanteessa 1 riittää, kunhan juuri jälkikäteen värjätään mustaksi. Juurisolmulla sekä isä- että kumpikin lapsilinkki osoittavat solmuun NIL[P], joka on musta, eli puu täyttää jälleen punamustapuun määritelmän kaikki vaatimukset. 13.2 Kierto-operaatiot • Tilanteessa 2 joudutaan tekemään edellistä enemmän työtä. Tuolloin ollaan tilanteessa, jossa sekä z että sen isäsolmu ovat kumpikin punaisia, ja z:n molemmat lapsiosoittimet osoittavat mustaa pysäytyssolmuun NIL[P]. • Korjaaminen käynnistyy nyt tarkastelemalla z:n setäsolmua eli z:n isän velisolmua. Tähän liittyy kolme erilaista tapausta. Lisäksi oletetaan, että z:n isäsolmu on z:n isoisän vasen poika (päinvastaisessa tapauksessa tapausta käsitellään tässä esitettävän peilikuvana). I) z:n setä on punainen tällöin siis sekä z:n isä että setä ovat punaisia z:n isoisä välittämättä musta, jotta puu olisi laillinen ennen lisäystä alustava ratkaisu: värjätään z:n isä ja setä mustiksi sekä isoisä punaiseksi. Selvästikään musta korkeus ei muutu näiden toimenpiteiden johdosta poluilla, jotka kulkevat x:n isoisän kautta, sillä niillä tapahtui vain kahden perättäisen kerroksen värien keskinäinen vaihto. mahdollinen uusi pulma: entä jos z:n isoisän isä on punainen? alkuperäinen pulmatilanne toistuu, mutta nyt kahta puun tasoa korkeammalla kuin viimeksi. joudutaan jatkamaan korjausalgoritmin suorittamista siirtämällä z alkuperäisestä puuhun lisätystä solmusta isoisäänsä asettamalla z := vanhempi[vanhempi[z]]. 13.2 Kierto-operaatiot II) z:n setä on musta, ja z on isänsä oikea lapsi siirretään z osoittamaan punaista isäänsä asettamalla z := vanhempi[z] suoritetaan vasen kierto solmun z kohdalla nyt uudesta z:sta tulee isänsä vasen lapsi, mikä johtaa tapaukseen (sekä z että sen isä ovat edelleen kumpikin punaisia). III) z:n setä on musta, ja z on isänsä vasen lapsi värjätään z:n isä mustaksi värjätään z:n isoisä punaiseksi suoritetaan z:n isoisän kohdalla oikea kierto korjausalgoritmin suorittaminen päättyy, sillä sen aloitusehto – z:n isä on punainen – ei enää toteudu • Korjausalgoritmin päätteeksi varmistetaan vielä juuren päätyvän mustaksi. • Tapauksessa II ei tehdä värien vaihtoja, joten minkään alipuun musta korkeus ei muutu siinä tehtävien toimenpiteiden ansiosta. • Myöskään tapaus III ei aiheuta muutoksia puun mustan korkeuden tasapainoon, sillä z:n isän ja isoisän värit vaihdetaan päittäin, ja lopuksi tehtävä oikea kierto z:n isoisän kohdalla vaihtaa z:n mustaksi värjätyn isän ja punaiseksi värjätyn isoisän keskinäisen järjestyksen. Siten isoisän kautta kulkevat polut saavat aikaisemmin värien vaihdossa tilapäisesti menettämänsä yhden mustan takaisin. esimerkki valaisee asiaa tarkemmin! 13.2 Kierto-operaatiot • Esimerkki: Lisätään avaimen 4 sisältävä solmu seuraavaan punamustapuuhun (kuvasta on jätetty pois pysäytyssolmu ja siihen johtavat linkit): 11 2 1 14 19 7 6 10 y 4 z Kelvoton tilanne: Punaiseksi lisätyn z:n isä on punainen. z:n isän veli y on punainen Tapaus 1 13.2 Kierto-operaatiot • Ensimmäinen korjausyritys: värjätään z:n isä ja setä mustiksi ja isoisä punaiseksi 11 2 1 7 6 z 4 14 punaiseksi 10 mustaksi y 19 13.2 Kierto-operaatiot • Tilanne 1. korjausyrityksen jälkeen: 11 2 1 19 7 6 z 14 10 y 4 Kelvoton tilanne: Edelleen kaksi punaista peräkkäin, mutta kahta tasoa ylempänä kuin edellä. Siirretään z osoittamaan isoisäänsä. uuden z:n isän veli y on musta, ja z on isänsä oikea lapsi Tapaus 2 13.2 Kierto-operaatiot • Toinen korjausyritys: siirretään z osoittamaan isäänsä, ja tehdään sen kohdalla vasen kierto-operaatio (värit eivät muutu) 11 2 14 uusi y 1 7 6 19 uusi z 10 y 4 z Kelvoton tilanne: Edelleen kaksi punaista peräkkäin, mutta kahta tasoa ylempänä kuin edellä. Siirretään z osoittamaan isoisäänsä. uuden z:n isän veli y on musta, ja z on isänsä oikea lapsi Tapaus 2 13.2 Kierto-operaatiot • Toinen korjausyritys: siirretään z osoittamaan isäänsä, ja tehdään sen kohdalla vasen kierto-operaatio (värit eivät muutu) 11 vasen kierto uusi z 2 1 14 19 7 z 6 4 y 10 13.2 Kierto-operaatiot 11 y 14 7 z 10 2 19 6 1 4 Kelvoton tilanne: Edelleen kaksi punaista peräkkäin (z ja isänsä) uuden z:n setä eli isän veli y pysyy ennallaan (edelleen musta), mutta nyt z muuttui isänsä vasemmaksi lapseksi Tapaus 3 13.2 Kierto-operaatiot • Kolmas korjausyritys: värjätään z:n isä mustaksi, isoisä punaiseksi, ja tehdään tämän kohdalla oikea kierto-operaatio oikea kierto z:n isoisän kohdalla 11 z:n isoisä punaiseksi 7 z 10 2 6 1 4 z:n isä mustaksi 14 y 19 13.2 Kierto-operaatiot 7 z 11 2 6 1 10 y 14 19 4 Nyt saatiin lopputulokseksi jälleen kaikki viisi kriteeriä täyttävä punamustapuu! • Kannattaa erityisesti huomioida, että viimeksi suoritettu oikea kierto takaa sen, että z:n aikaisemman isäsolmun (arvo 11) kautta kulkevien polkujen musta korkeus ei pienene värin vaihdon takia. 13.2 Kierto-operaatiot • Seuraavassa on esitelty punamustan puun lailliseksi korjaava algoritmi KORJAA_LISÄYS_PUNAMUSTAPUUHUN(P, z) 1 WHILE väri[vanhempi[z]] = punainen DO 2 IF vanhempi[z] = vasen[vanhempi[vanhempi[z]]] 3 THEN y = oikea[vanhempi[vanhempi[z]]] /* z:n setäsolmun määrääminen */ 4 IF väri[y] = punainen /* Tapaus 1 */ 5 THEN väri[vanhempi[z]] := musta 6 väri[y] := musta 7 väri[vanhempi[vanhempi[z]]] := punainen 8 z := vanhempi[vanhempi[z]] 9 ELSE IF z = oikea[vanhempi[z]] /* Tapaus 2 */ 10 THEN z := vanhempi[z] 11 VASEN_KIERTO(P, z) 12 väri[vanhempi[z]] := musta 13 väri[vanhempi[vanhempi[z]]] := punainen 14 OIKEA_KIERTO(P, vanhempi[vanhempi[z]]) 15 ELSE /* kuten rivien 3 – 14 lauseet, mutta oikea vasen 16 väri[juuri[P]] := musta 13.2 Kierto-operaatiot • Lisäyksen kompleksisuuden analyysi: Alkuosa algoritmista (rivit 1 – 13) on lähes identtinen binääriseen hakupuuhun tehtävän lisäyksen kanssa, joten sen kompleksisuus on Ο(h). Rivit 14 – 16 ovat vakioaikaisia sijoituksia (lapsilinkit NIL[P]:hen + väriattribuutin asettaminen uudelle solmulle) Myös riviltä 17 kutsuttavan korjausalgoritmin suoritusaika on Ο(h), sillä riviltä 1 käynnistyvää WHILE-silmukkaa joudutaan toistamaan vain tapauksessa 1, jossa osoitinta z nostetaan joka kierroksella 2 tasoa ylöspäin. Kaikki muut operaatiot korjausalgoritmissa ovat vakioaikaisia. Koko lisäysalgoritmin suoritusaika on siten Ο(h) + Ο(h) = Ο(h) Koska puun korkeus on h = Ο(log2n), on algoritmin kompleksisuus Ο(log2n). • Lisäksi kannattaa huomioida, että kierto-operaatioita joudutaan suorittamaan korkeintaan kahdesti (tapauksen 2 esiintyessä). 13.3 Solmun poistaminen • Punamustasta puusta solmun poistamisen perusajatus on sama kuin tavallisessa binäärisessä hakupuussakin. • Poistomenettely perustuu poistettavan solmun z lapsien määrään. Jos solmulla z on 0 – 1 lasta, kyseinen solmu poistetaan fyysisesti, ja tehdään lisäksi solmun ohitus linkkejä päivittämällä, jos z:lla on 1 lapsi. Jos z:lla on 2 lasta, poistetaankin fyysisesti z:n seuraaja y, jolla on korkeintaan 1 lapsi (vasen lapsi puuttuu väistämättä), ja seuraajan arvot kopioidaan solmun z vastaaviin datakenttiin. • Jos fyysisesti poistettava solmu on punainen, ei seuraa mitään ongelmia. • Sen sijaan mustan solmun poistaminen aiheuttaa väkisin jonkin tyyppisiä ongelmia: 1) Puun juurisolmu y poistetaan, ja uudeksi juureksi päätyy tämän punainen lapsi x. rikotaan määritelmän vaatimusta 2, että puun juuri on musta 2) Jos sekä x että vanhempi[y] (josta tulee nyt vanhempi[x]) ovat punaisia, tulee puuhun kaksi punaista solmua peräkkäin 3) Niiltä poluilta, jotka kulkivat poistetun solmun y kautta, häviää yksi musta, joten musta korkeus näillä poluilla pienenee • Ratkaisuyritys: pyritään siirtämään poistetun solmun musta väri alaspäin sen lapseen x jos lapsi x on jo ennestään musta, siitä tulee nyt ”tuplamusta” uusi ratkaisu: esitellään korjausalgoritmi KORJAA_POISTO_PUNAMUSTAPUUSTA(P, x) 13.3 Solmun poistaminen • Seuraavassa on esitelty punamustasta puusta poiston perusalgoritmi: POISTA_PUNAMUSTAPUUSTA(P, z) 1 IF vasen[z] = NIL[P] OR oikea[z] = NIL[P] 2 THEN y := z 3 ELSE y := SEURAAJA_BINÄÄRISESSÄ_HAKUPUUSSA(z) 4 IF vasen[y] ≠ NIL[P] 5 THEN x := vasen[y] 6 ELSE x := oikea[y] 7 vanhempi[x] := vanhempi[y] 8 IF vanhempi[y] = NIL[P] 10 THEN juuri[P] = x 11 ELSE IF y = vasen[vanhempi[y]] 12 THEN vasen[vanhempi[y]] := x 13 ELSE oikea[vanhempi[y]] := x 14 IF y ≠ z 15 THEN avain[z] := avain[y] 16 kopioidaan samalla myös y:n mahdollinen satelliittidata solmuun z 17 IF väri[y] = musta 18 THEN KORJAA_POISTO_PUNAMUSTAPUUSTA(P, x) 19 RETURN y 13.3 Solmun poistaminen • • • • • • Lähdetään seuraavaksi tarkastelemaan punamustapuusta poistoon liittyvää korjausalgoritmia. Jos väri[x] = musta, tulee x:stä tilapäisesti tuplamusta. Tarkastellaan solmun x veljeä w, joka ei voi olla NIL[P], koska x on tuplamusta muussa tapauksessa puun musta korkeus ei olisi ollut tasapainossa joko x:n tai sen veljen w kohdalla ennen poistoa. Punamustapuusta tapahtuvan mustan solmun poistoon liittyy neljä eri tapausta peilikuvineen (riippuen siitä, miltä puolelta solmun x veli löytyy). Tapaus 1: Poistetun solmun lapsisolmun x veli w on punainen, jolloin w:n lapset ovat välttämättä mustia. Vaihdetaan w:n ja vanhempi[x]:n värit keskenään Tätä seuraa vasen kierto x:n isäsolmun eli vanhempi[x]:n kohdalla Tapaus muuttuu joksikin tapauksista 2, 3 tai 4, joissa kaikissa x:n velisolmu w on musta. Tapaus 2: Poistetun solmun lapsisolmun x veli w on musta, ja myös veljen lapset ovat kumpikin mustia. Koska velisolmu w on nyt musta, ja x on tuplamusta, poistetaan näistä molemmista yksi musta. Tämä tapahtuu poistamalla tuplamustasta x toinen musta värjäämällä w punaiseksi Poluilta, jotka kulkevat x:n ja w:n isäsolmun kautta, hävisi täten yksi musta. Tämä palautetaan polulle takaisin värjäämällä kyseinen isäsolmu mustaksi. kaikki sujuu hyvin, jos mainittu isäsolmu on punainen muussa tapauksessa isäsolmusta tulee nyt kaksinkertaisesti musta, eli alkuperäinen ongelma uusiutuu yhtä tasoa ylempänä. 13.3 Solmun poistaminen • Kannattaa huomioida, että jos tapaukseen 2 tultiin tapauksen 1 kautta, on x:n isäsolmu punainen (entinen velisolmun w väri) Lisäksi kannattaa huomioida, että jos x on puun juuri ja samalla tuplamusta, ei tarvitse tehdä enää mitään (musta korkeus jo korjautunut, sillä juuren musta väri vaikuttaa kaikkiin polkuihin). Tapaus 3: Poistetun solmun lapsisolmun x veli w on musta, ja sillä on punainen vasen ja musta oikea lapsi Nyt veljen w vasemman lapsen lasten on oltava mustia. Vaihdetaan päittäin w:n ja sen vasemman lapsen väri. Lisäksi tehdään vielä oikea kierto velisolmun kohdalla. Nyt x:n uusi veli on musta (se on nyt veljen aikaisempi vasen lapsi, joka värjättiin edellä mustaksi), ja sillä on punainen oikea lapsi (entinen x:n veli, joka värjättiin punaiseksi) ja musta vasen (alkuperäinen) lapsi. Tilanne johtaa tapaukseen 4. • • Tapaus 4: Poistetun solmun lapsisolmun x veli w on musta, ja sillä on punainen oikea lapsi (paikka ylimääräisen mustan sijoittamista varten) Asetetaan w:n väriksi x:n (ja samalla w:n) isäsolmun väri (ei tiedossa) Asetetaan x:n isäsolmun väriksi musta (x:n sisältämä ylimääräinen musta) Veljen w oikean lapsen väriksi asetetaan musta (w:ssä sijainnut musta) Tehdään vasen kierto x:n isäsolmun kohdalla, jolloin isäsolmu siirtyy vasempaan haaraan. Lopuksi siirretään osoitin x osoittamaan puun juureen, mihin poiston korjausalgoritmin suoritus päättyy. Esitetään seuraavaksi esimerkkejä kaikista neljästä tapauksesta! 13.3 Solmun poistaminen • Lähtötilanne: On poistettu musta solmu y avainarvot B ja A sisältävien solmujen välistä, jolloin solmun A kohdalta alkava alipuu on noussut tasoa ylöspäin. B x (y) w D A a b E C c d e f Tapaus 1: Poistetun mustan solmun y lapsisolmusta x tulee tuplamusta, kun y:n musta väri pakotetaan tämän ainoaan lapsisolmuun (avainarvo A), joka sattuu olemaan väriltään musta. Tuplamustasta väristä solmussa x pitää päästä eroon. Solmun x veli w on punainen. 13.3 Solmun poistaminen • 1. korjausvaihe: i) Vaihdetaan x:n isä- ja velisolmujen värit keskenään ii) Suoritetaan vasen kierto x:n isäsolmun kohdalla. B x (y) w D A a b E C c d e f 13.3 Solmun poistaminen • Muodostuu jokin tapauksista 2, 3 tai 4, joissa kussakin x:n velisolmu on musta. D E B x e C A a f b c d Tapaus 2: Oletetaan seuraavaksi, että x:n veljen molemmat lapset ovat mustia. 13.3 Solmun poistaminen • Tapaus 2: Solmun x veli on musta, ja sen kumpikin lapsi on musta. Sen sijaan solmun x isän väri voi olla kumpi tahansa (merkitty vihreällä). B x D A a b c Toimenpiteet: w E C d e f i) Solmusta x poistetaan toinen musta ii) Solmu w värjätään punaiseksi (sieltäkin poistuu yksi musta) iii) Värjätään näiden isäsolmu B mustaksi Jos solmu B on alun perin punainen, tilanne tulee kuntoon Muussa tapauksessa B:stä tulee nyt tuplamusta, jolloin osoitin x siirretään isäänsä eli tasoa ylemmäs. 13.3 Solmun poistaminen • Tilanne edellisten toimenpiteiden jälkeen: x Mustien lukumäärä 1 vai 2? B D A a b E C c Toimenpiteet: d e f i) Solmusta x poistetaan toinen musta ii) Solmu w värjätään punaiseksi (sieltäkin poistuu yksi musta) iii) Värjätään näiden isäsolmu B mustaksi Jos solmu B on alun perin punainen, tilanne tulee kuntoon Muussa tapauksessa B:stä tulee nyt tuplamusta, jolloin osoitin x siirretään isäänsä eli tasoa ylemmäs. 13.3 Solmun poistaminen • Tapaus 3: Solmun x velisolmu w on musta, veljen vasen lapsi on punainen ja oikea musta B x D A a w b E C c Toimenpiteet: d e f i) Vaihdetaan x:n veljen w ja sen vasemman lapsen värit keskenään veljestä tulee punainen ja vasemmasta lapsesta musta ii) Suoritetaan oikea kierto w:n kohdalla tulokseksi saadaan tapaus 4, joka on nähtävissä seuraavalla sivulla 13.3 Solmun poistaminen • Tapaus 4: Solmun x velisolmu w on musta, veljen vasen lapsi on musta ja oikea punainen B x a w C A b c D d E e f 13.3 Solmun poistaminen • Tapaus 4: Solmun x velisolmu w on musta, ja veljen oikea lapsi on punainen B x a D A w b E C c Toimenpiteet: d e f i) Vaihdetaan veljen w väriksi x:n isän väri ii) Siirretään x:n ylimääräinen musta sen isäsolmuun iii) Asetetaan w:n oikean lapsen väriksi musta (velisolmun musta) iv) Tehdään vasen kierto x:n isäsolmulle v) Siirretään osoitin x koko puun juureen. 13.3 Solmun poistaminen • Lopputilanne: Puu täyttää jälleen punamustapuulle asetettavat kriteerit D E B e a f C A b c w d 13.3 Solmun poistaminen • Seuraavassa on esitelty punamustasta puusta poiston korjausalgoritmi: KORJAA_POISTO_PUNAMUSTAPUUSTA(P, x) 1 WHILE x ≠ juuri[P] AND väri[x] = musta DO 2 IF x = vasen[vanhempi[x]] 3 THEN w := oikea[vanhempi[x]] /* Määrätään solmun x velisolmun sijainti. */ 4 IF väri[w] = punainen 5 THEN väri[w] := musta /* Tapaus 1 */ 6 väri[vanhempi[x]] := punainen 7 VASEN_KIERTO(P, vanhempi[x]] 8 w := oikea[vanhempi[x]] 9 IF väri[vasen[w]] = musta AND väri[oikea[w]] = musta 10 THEN väri[w] = punainen /* Tapaus 2 */ 11 x := vanhempi[x]] 12 ELSE IF väri[oikea[w]] = musta 13 THEN väri[vasen[w]] := musta /* Tapaus 3 */ 14 väri[w] = punainen 15 OIKEA_KIERTO(P, w) 16 w := oikea[vanhempi[x]] 17 väri[w] := väri[vanhempi[x]] /* Tapaus 4 */ 18 väri[vanhempi[x]] := musta 19 väri[oikea[w]] := musta 20 VASEN_KIERTO(P, vanhempi[x]) 21 x := juuri[P] 22 ELSE samoin kuin rivit 3 – 21, mutta vasen oikea 23 väri[x] := musta 13.3 Solmun poistaminen • Analysoidaan seuraavaksi vielä poiston kompleksisuus Poiston alkuosan (eli rivien 1 – 15) kompleksisuus on sama kuin poistettaessa solmua tavallisesta binäärisestä hakupuusta siis O(h). Korjausalgoritmin kompleksisuus on samoin O(h), sillä tapauksissa 1, 3 ja 4 suoritus loppuu heti, kun on tehty vakiomäärä värien muutoksia ja enintään kolme kiertoa WHILE-silmukkaa joudutaan (mahdollisesti) toistamaan ainoastaan tapauksessa 2. Tällöin osoitinta x siirretään joka kierroksella yksi askel ylöspäin puussa. • Siten koko poisto-operaation kompleksisuus on: O(h) + O(h) = O(h) = O(log2n) 14 Laajennetut tietorakenteet 14.1 Dynaaminen järjestysstatistiikka • Syksyllä pidetyllä kurssilla Tietorakenteet ja algoritmit I tarkasteltiin luvussa 9 seuraavaa ongelmaa: Mikä on annetun lukujoukon i. pienin alkio? • Tuolloin osoitettiin, että ongelma voidaan ratkaista ajassa Ο(n), jos luvut on tallennettu taulukkoon. • Tässä luvussa osoitetaan, että ongelma ratkeaa kuitenkin ajassa Ο(log2n), mikäli sen ratkaisemisessa käytetään hyväksi punamustia puita. • Lisäksi esitellään menetelmä, joka selvittää mielivaltaisen alkion arvoasteen ajassa Ο(log2n). • Termillä arvoaste tarkoitetaan, monentenako kysytty alkio esiintyisi järjestetyssä lukujoukossa. • Järjestysstatistiikkapuu: Sisältää kaikki punamustan puun ominaisuudet Solmualkioilla on lisäksi yksi uusi attribuutti • Solmualkion attribuutit: vanhempi, avain, vasen, oikea, väri, … … ja näiden lisäksi vielä koko 14.1 Dynaaminen järjestysstatistiikka • Attribuutti koko[x] osoittaa solmun x kohdalta alkavan alipuun koon eli solmujen lukumäärän (pysäytyssolmuja ei lasketa tähän mukaan) Ominaisuudet: koko[NIL[P]] = 0 koko[x] := koko[vasen[x]] + koko[oikea[x]] + 1 Esimerkki järjestysstatistiikkapuusta: 13 15 16 5 8 9 11 3 5 5 3 3 1 1 6 1 4 1 9 1 18 3 14 1 12 1 17 1 19 1 14.1 Dynaaminen järjestysstatistiikka • • • • • Tarkastellaan, miten tiettyä arvoastetta i oleva luku löydetään järjestysstatistiikkapuusta. Seuraava algoritmi VALITSE_I._ALKIO(x, i) palauttaa osoittimen sellaiseen solmualkioon y, jossa avain[y] on x-juurisen alipuun i. pienin alkio. VALITSE_I._ALKIO(x, i) 1 r := koko[vasen[x]] + 1 2 IF i = r 3 THEN RETURN x 4 ELSE IF i < r 5 THEN RETURN VALITSE_I._ALKIO(vasen[x], i) 6 ELSE RETURN VALITSE_I._ALKIO(oikea[x], i – r). Jos etsitään koko puun P i. alkiota, kutsu on muotoa VALITSE_I._ALKIO(juuri[P], i) Algoritmin toiminta-ajatus: muistuttaa pitkälti luvussa 9 esitetyn algoritmin ideaa arvo koko[vasen[x]] kertoo, kuinka monta alkiota on järjestyksessä ennen x:ää siten r = koko[vasen[x]] + 1 on alkion x arvoaste sellaisessa puussa, jonka juuri on solmu x. jos i = r, on x samalla etsitty solmu ja voidaan lopettaa jos i < r, löytyy i. alkio rekursiivisesti vasemmasta alipuusta jos i > r, niin i. alkio löytyy oikeasta alipuusta. Koska ennen x:n oikean alipuun alkioita on puussa r kappaletta tätä pienempiä alkioita, on puun i. alkio nyt oikean alipuun i – r. alkio. Algoritmin kompleksisuus on Ο(log2n), sillä jokainen rekursiivinen kutsu johtaa puussa aina tasoa alemmas, kullakin tasolla tehdään vakiomäärä työtä, ja kutsuja muodostuu korkeintaan Ο(log2n). 14.1 Dynaaminen järjestysstatistiikka • • • Seuraavaksi tutkitaan, miten pystytään selvittämään tietyn yksittäisen järjestysstatistiikkapuun alkion x arvoaste . Käytettävissä on osoitin puun P johonkin solmuun x. Seuraavassa esitetty algoritmi selvittää, monesko solmuun x tallennettu avain on arvoasteen mukaisessa järjestyksessä puussa P. ARVOASTE(P, x) 1 r := koko[vasen[x]] + 1 2 y := x 3 WHILE y ≠ juuri[P] DO 4 IF y = oikea[vanhempi[y]] 5 THEN r := r + koko[vasen[vanhempi[y]]] + 1 6 y := vanhempi[y] 7 RETURN r • Algoritmin toiminta-ajatus: 1) jos solmu x on puun P juuri, saadaan sen arvoaste suoraan selville laskemalla sen vasemman alipuun koko ja lisäämällä siihen ykkönen (juuri itse), ja lopetetaan 2) muuten, jos solmu x kuuluu isäsolmunsa oikeaan alipuuhun, on arvoasteeseen lisättävä myös isäsolmu sekä tämän vasempaan alipuuhun kuuluvat solmut 3) jos taas x kuuluu isänsä vasempaan alipuuhun, ei tehdä kyseisellä silmukan kierroksella mitään tapausten 2 ja 3 jälkeen siirrytään lopuksi puussa tasoa ylöspäin nostamalla osoitin y osoittamaan nykyiseen isäsolmuunsa; tehtävä on valmis, kun y osoittaa lopulta puun juureen 14.1 Dynaaminen järjestysstatistiikka • Todetaan seuraavassa vielä algoritmin oikeellisuus: 1) Kunkin silmukassa tehtävän kierroksen lopussa siis nostetaan osoitinta y tason verran ylöspäin solmuun vanhempi[y]. Mikäli silmukan alkaessa r edustaa avain[x]:n arvoastetta puussa, jonka juurena esiintyy y, niin silmukan lopussa r osoittaa arvoastetta puussa, jonka juurena on vanhempi[y]. 2) Suoritettaessa WHILE-silmukkaa tarkastellaan aina alipuuta, jonka juuri on vanhempi[y]. Tässä vaiheessa on jo ehditty summaamaan x:n arvoasteeseen r ne y-juurisen puun solmut, jotka edeltävät x:ää. Silmukan kierroksen aikana kasvatetaan arvoa r y:n isän vasemman alipuun koolla + 1:llä, sillä nämä edeltävät puussa x:ää, kunhan y esiintyy isänsä oikeassa alipuussa. 3) Jos puolestaan y esiintyy isänsä vasemmassa alipuussa, eivät sen kummemmin vanhempi[y] kuin tämän oikeassa alipuussakaan olevat solmut edellä x:ää. Tällöin ei ole tarvetta päivittää x:n arvoasteen r arvoa silmukassa kyseisellä kierroksella. • Esitellään luennolla esimerkki algoritmin toiminnasta. • Algoritmin ARVOASTE(P, x) kompleksisuus on Ο(log2n), sillä pahimmassa tapauksessa x osoittaa juuresta katsottuna kaukaisimpaan datasolmuun (ei NIL-arvon sisältävään solmuun). Koska puun korkeus on logaritminen ja silmukassa toistettavat lauseet ovat vakioaikaisia, kuten myös rivien 1, 2 ja 7 operaatiot, on kokonaissuoritusaika pahimmassa tapauksessa logaritminen. 14.1 Dynaaminen järjestysstatistiikka • Siirrytään seuraavaksi tarkastelemaan alipuiden kokojen ylläpitoa. Pulma: sekä solmun lisäys- että poisto-operaatiot muuttavat puuta. on osattava päivittää attribuuttia koko • Tarkastellaan ensiksi lisäystä: Lisäysalgoritmi LISÄÄ_PUNAMUSTAAN_PUUHUN(P, z) on kaksivaiheinen: 1) Ensiksi uusi solmu viedään oikealle paikalleen kuten tavalliseen binääriseen hakupuuhun lisättäessä. 2) Algoritmilla KORJAA_LISÄYS_PUNAMUSTAPUUHUN(P, z) korjataan värit, jotta puu täyttää lisäyksen jälkeenkin kaikki punamustapuun kriteerit. • Muutokset järjestysstatistiikkapuussa: Vaihe 1: Solmun paikkaa etsittäessä lisätään jokaisen vastaantulevan solmun x attribuutin koko arvoa yhdellä. Vaihe 2: Ainoastaan kierto-operaatiot eli rotaatiot muuttavat puun rakennetta. Kierto on paikallinen operaatio: ainoastaan kahden solmun koko-attribuutin arvoa joudutaan päivittämään (solmujen x ja y). vasen kierto kohtaan x 51 26 x oikea kierto kohtaan y y 19 19 y 6 4 51 12 x 7 6 26 11 7 4 14.1 Dynaaminen järjestysstatistiikka • Edellisestä – kiertoja yleisesti esittävästä – kuvasta voidaan päätellä, että kierrossa keskenään paikkoja vaihtavien solmujen x ja y alipuiden koot pysyvät ennallaan. • Myöskään kiertokohdan yläpuolella puussa ei selvästikään tapahdu mitään muutoksia. • Sen sijaan solmujen x ja y koko-attribuuttien arvot määräytyvät seuraavasti: vasemmassa kierrossa y:n attribuutin koko arvoksi tulee x:n koko ennen kierron aloitusta attribuutin koko[x] arvoksi saadaan puolestaan laskemalla x:n vasemman alipuun (sama kuin ennenkin) ja uuden oikean alipuun (entinen y:n vasen alipuu) koot yhteen ja lisäämällä summaan ykkönen (solmu x itse). • Jos suoritetaan puolestaan oikea kierto, ovat tehtävät päivitykset edellisen peilikuvia: x:n uudeksi kokoattribuutin arvoksi tulee y:n koko ennen kierron käynnistymistä y:n uusi koko on vastaavasti sen alipuiden kokojen summa + 1 (y:n oikea alipuu on sama kuin ennenkin, ja vasemman alipuun kooksi tulee x:n entisen oikean alipuun koko). • Algoritmiin VASEN_KIERTO(P, x) joudutaan siten lisäämään seuraavat rivit: 13 koko[y] := koko[x] 14 koko[x] := koko[vasen[x]] + koko[oikea[x]] + 1 • Algoritmiin OIKEA_KIERTO(P, y) (algoritmia ei ole esiteltynä monisteessa eksplisiittisesti) tehdään vastaavasti lisäykset: 13 koko[x] := koko[y] 14 koko[y] := koko[vasen[y]] + koko[oikea[y]] + 1 14.1 Dynaaminen järjestysstatistiikka • • • Järjestysstatistiikkapuuhun lisäämisen kompleksisuus: Vaihe 1: Koska puun jokaisella tasolla joudutaan vaihtamaan korkeintaan yhden solmun koko-attribuutin arvoa, on tarvittavan lisätyön kustannus Ο(log2n). Vaihe 2: Korjausalgoritmissa suoritetaan korkeintaan 2 rotaatiota, joiden yhteydessä joudutaan suorittamaan edellisellä sivulla esiintyvät ylimääräiset kaksi komentoa. Näistä aiheutuvan lisätyön kustannus on siten Ο(1). Siten alkion järjestysstatistiikkapuuhun lisäämisen kokonaiskustannus on Ο(log2n). Tarkastellaan seuraavaksi poiston yhteydessä tarvittavia muutoksia. 1) Aluksi poistetaan haluttu solmu (tai sen seuraaja) aivan kuten tavallisesta binäärisestä hakupuusta. 2) Algoritmilla KORJAA_POISTO_PUNAMUSTAPUUSTA(P, x) saadaan puu palautettua lailliseksi tekemällä tarpeelliset kierrot ja värien korjaukset. Korkeintaan kolme kiertoa joudutaan suorittamaan (tapausjono 1 3 4). Tarpeelliset muutokset järjestysstatistiikkapuussa: Vaihe 1: Kun fyysisesti poistettava solmu y on paikallistettu, palataan sieltä kohti juurta ja pienennetään jokaisen vastaan tulevan solmun koko-attribuutin arvoa yhdellä. (KYSYMYS: Miksi ei voida koko-attribuutin päivityksiä tehdä vielä ’menomatkalla’?) Vaihe 2: Korjausalgoritmia suoritettaessa joudutaan koko-attribuutteja päivittämään ainoastaan kiertoja tehtäessä. Tämä tapahtuu täysin samaan tapaan kuin solmua lisättäessä (kts. edellä). Rotaatioita tapahtuu korkeintaan kolme. Koska vaiheessa 1 joudutaan pahimmassa tapauksessa kulkemaan pisin polku alimmalta tasolta juureen ja tekemään joka tasolla yhden koko-attribuutin päivitys, ja vaiheessa 2 tehdään vakiomäärä lisätyötä, on alipuiden kokotiedon ylläpidosta koituvan lisätyön kustannus logaritminen, pysyy poiston kokonaiskustannus suuruusluokassa Ο(log2n). järjestysstatistiikkapuun käsittelyn asymptoottinen kustannus on siis sama kuin tavallisen punamustan puun. 14.2 Kuinka tietorakennetta laajennetaan? • • • Edellä tarkasteltiin, miten punamustapuun ominaisuuksia laajentamalla saatiin aikaan tietorakenne, joka soveltuu järjestysstatistiikkatiedon ylläpitoon. Yleisesti tietorakenteen laajentamiselle on olemassa neljä perusperiaatetta: Vaihe 1: Valitaan lähtökohdaksi sopiva perustietorakenne, jonka ominaisuudet peritään ja säilytetään. Vaihe 2: Määritellään tarvittava lisäinformaatio. Vaihe 3: Varmistetaan, että lisäinformaatiota voidaan ylläpitää (tarpeeksi helposti!) modifioimalla perustietorakenteen operaatioita. Vaihe 4: Toteutetaan uudet operaatiot. Palataan esimerkin vuoksi takaisin järjestysstatistiikkapuihin: Vaihe 1: Valitaan punamustat puut lähtökohtaiseksi tietorakenteeksi. kaikki niiden ominaisuudet ovat käytettävissä Vaihe 2: Määritellään punamustalle puulle uusi attribuutti koko, joka sisältää tarvitsemamme lisäinformaation. Vaihe 3: Selvästikään uuden attribuutin lisäämisestä ei ole kiusaa kohdistettaessa puuhun kyselyoperaatioita. Sen sijaan pitää varmistaa, etteivät lisäyksen ja poiston teoreettiset kustannukset kasva uuden attribuutin käyttöönoton takia. Edellä analysoitiin, että näiden operaatioiden kustannus pysyy yhä edelleen suuruusluokan Ο(log2n) mukaisina. HUOM! Kannattaa huomioida, että järjestyksessä i. alkion palauttamiseen tai alkion x arvoasteen määräämiseen tarkoitetut algoritmit nopeutuisivat jopa vakioaikaisiksi (!), jos alipuun kokotiedon sijaan solmuun tallennettaisiinkin suoraan sen arvoaste … . Mutta hyvällä ajatuksella on tällä kertaa kuitenkin hintansa: solmun lisäämisen tai poistamisen vaikutukset eivät enää rajoittuisikaan paikallisiksi vaan heijastuisivat koko puuhun. Siten nopeiden, ajassa Ο(1) tapahtuvien kyselyjen vastapainoksi tulisivat selvästi hitaammat, Ο(n)-aikaiset päivitykset! Vaihe 4: Toteutetaan tietorakenteen uudet operaatiot VALITSE_I:_ALKIO(x, i) ja ARVOASTE(P, x). 13U AVL-puut • AVL-puun nimitys tulee kehittäjiensä sukunimistä G. M. Adelson-Velskij ja E. M. Landis. Tietorakenne on esitelty jo vuonna 1962 Neuvostoliitossa artikkelissa An algorithm for the organisation of information, Доклады Академии Наук СССР [Soviet Mathematics Doklady] 3 (1962), sivut 1259 – 1263. • Edellä luvussa 13 esiteltiin punamustat puut, jotka ovat tasapainotettuja binäärisiä hakupuita. • Näiden tavoin myös AVL-puut ovat tasapainotettuja. • Kuten edellä todettiin, AVL-puita käsittelevä artikkeli ilmestyi jo vuonna 1962. Punamustat puut esiteltiin 10 vuotta myöhemmin vuonna 1972. • Siinä missä punamustien puiden tasapainotus perustuu väriattribuutin käyttöön, on AVL-puissa tähän tarkoitukseen tarvittava tieto säilötty alipuun korkeusattribuuttiin. jokaisella solmulla alipuiden korkeudet saavat erota toisistaan korkeintaan yhdellä. • Kyseessä on selvästikin tavallisen binäärisen hakupuun laajennos: uutena attribuuttina solmualkioilla on korkeus. • Sovitaan, että pysäytys- eli NIL-solmujen korkeus on -1. Siten alimman tason datasolmujen korkeutena on 0, ja yleisesti määritellään: korkeus[x] = max(korkeus[vasen[x]], korkeus[oikea[x]]) + 1 • Seuraavalla sivulla esitetään esimerkki AVL-puusta. 13U AVL-puut 3 1 44 78 17 2 -1 0 -1 32 1 50 0 -1 -1 0 -1 48 -1 0 -1 Kuvaan merkityt numerot edustavat solmujen korkeuksia. 62 -1 88 -1 13U.1 AVL-puun korkeus • Lause: n-alkioisen AVL-puun korkeus on Ο(log2n). Todistus: Merkitään n(h):lla h-korkuisen AVL-puun solmujen minimimäärää. On helppo nähdä, että n(0) = 1 ja n(1) = 2. Kun n ≥ 2, niin pienin mahdollinen h-korkuinen puu koostuu (i) juuresta (ii) AVL-puusta, jonka korkeus on h – 1 ja (iii) AVL-puusta, jonka korkeus on h – 2 Täten n(h) = n(h – 1) + n(h – 2) + 1. Koska väistämättä n(h – 1) > n(h – 2), niin n(h) > 2n(h – 2). Tästä saadaan iteroimalla: n(h) > 2n(h – 2) ⇔ n(h) > 4n(h – 4) ⇔ n(h) > 8n(h – 6) ⇔… ⇔ n(h) > 2in(h – 2i) Kun nyt ratkaistaan h – 2i = 0, saadaan i = h/2 ja n(h) > 2h/2n(0) = 2h/2 ⋅ 1. Tästä saadaan ottamalla 2-kantainen logaritmi molemmilta puolilta edelleen h/2 < log2n(h), eli h < 2log2n(h), mistä väite seuraa. 13U.2 Solmun lisääminen AVL-puuhun • Solmualkion lisääminen AVL-puuhun tapahtuu samoin kuin normaaliin binääriseen hakupuuhun (tai punamustaan puuhun). Lisäyksen jälkeen kuljetaan sen aikana edetty polku takaisin juureen ja päivitetään matkan varrelle osuvien solmujen korkeus-attribuutit käyttämällä edellä annettua korkeuden laskukaavaa ja tarvittaessa tasapainottamalla puu. • Esimerkki: lisätään aikaisemmin esitettyyn AVL-puuhun solmu, jossa avaimena on 54. 3 1 44 78 17 2 -1 0 -1 32 1 50 0 -1 -1 0 -1 48 -1 0 -1 62 -1 88 -1 13U.2 Solmun lisääminen AVL-puuhun Havaitaan, että puu päätyy epätasapainoon juurisolmun oikean pojan 78 kohdalta alkavasta alipuusta alkaen: vasemman alipuun korkeutena on 2, mutta oikean 0. Ei kelpaa – puu on saatava täältä tasapainoon (alue merkitty kuvaan kolmiolla). 4 44 1 78 17 3 -1 0 -1 32 2 50 0 -1 -1 0 -1 1 48 -1 0 62 -1 54 88 -1 13U.2 Solmun lisääminen AVL-puuhun Puun uudelleenjärjestäminen: • Jos puu P on epätasapainossa, niin kuljetaan puussa ylöspäin niin kauan, kunnes törmätään sellaiseen solmuun x, että sen isovanhempi z on epätasapainossa. Merkitään näiden väliin jäävää x:n isäsolmua y:llä. • Olkoon nyt (a, b, c) näiden kolmen solmun x, y, z välijärjestys. • Suoritetaan rotaatiot, joilla solmusta b tulee kolmikon juurisolmu. • Tämän jälkeen pitää solmujen x, y, ja z korkeus-attribuutin arvot laskea uudelleen (muiden kohdalla ne eivät muutu). • Jatketaan seuraavaksi puussa ylöspäin aina juureen asti ja tehdään tarvittavat tasapainotukset ja korkeus-attribuuttien päivitykset kuten edellä. • Esitellään seuraavaksi, millaisilta AVL-puiden kierto-operaatiot näyttävät. • AVL-puiden kierto-operaatiot ovat joko yksin- tai kaksinkertaisia. Kaksinkertainen rotaatio koostuu itse asiassa kahdesta kierrosta, joiden suunta on päinvastainen mutta joissa keskussolmu vaihtuu kiertosuunnan vaihtuessa. 13U.2 Solmun lisääminen AVL-puuhun • Yksinkertaiset rotaatiot: 1) Rotaatio vasemmalle a=z b=y b=y a=z P0 c=x c=x P1 P0 P2 P3 P1 P2 P3 13U.2 Solmun lisääminen AVL-puuhun • Yksinkertaiset rotaatiot: 2) Rotaatio oikealle c=z b=y b=y a=x P3 a=x P2 P0 c=z P1 P0 P1 P2 P3 13U.2 Solmun lisääminen AVL-puuhun • Kaksoisrotaatiot: 1) Kaksoisrotaatio oikealla alipuulla a=z b=x c=y a=z c=y P0 b=x P3 P1 P2 P0 P1 P2 P3 13U.2 Solmun lisääminen AVL-puuhun • Kaksoisrotaatiot: 2) Kaksoisrotaatio vasemmalla alipuulla c=z b=x a=y c=z P3 a=y P0 b=x P0 P1 P2 P1 P2 P3 13U.2 Solmun lisääminen AVL-puuhun Kun alkio 54 on lisätty, on solmu x (avainarvo 62) ensimmäinen, jonka isoisän z kohdalla vallitsee epätasapaino toipuminen: kaksoisrotaatio z:n vasemmalla alipuulla 4 44 2. 1 3 78 17 z 1. -1 0 -1 32 2 50 0 y -1 0 -1 1 48 -1 0 -1 x 62 -1 54 88 -1 13U.2 Solmun lisääminen AVL-puuhun Esimerkin kaksoisrotaatio koostuu ensinnä vasemmasta kierrosta solmun y = 50 kohdalla, mitä seuraa oikea kierto solmun z = 78 kohdalla. kokonaisvaikutus: lisätty solmu 54 nousee tasoa ylemmäs 3 44 1 2 62 17 x -1 0 -1 32 1 50 1 y -1 -1 0 -1 0 48 -1 -1 78 z 0 88 x 54 -1 -1 -1 13U.3 Solmun poistaminen AVL-puusta • Solmu poistetaan AVL-puusta vastaavalla tavalla kuin tavallisesta binäärisestä hakupuustakin (poistetaan siis joko halutun avainarvon sisältävä solmu itse tai sen seuraaja/edeltäjä) • Oletetaan, että z on ensimmäinen epätasapainossa oleva solmu, joka tulee vastaan edettäessä poistetusta solmusta kohti puun juurta. • Oletetaan lisäksi, että y on solmun z lapsista se, jolla attribuutin korkeus arvo on suurempi (tämän velisolmulla on siis pienempi korkeus). • Edelleen oletetaan, että x on solmun y lapsista sellainen, että sen korkeus on vähintään yhtä suuri kuin velisolmullaan. • Puu on mahdollista tasapainottaa solmujen x, y ja z suhteen samoin kuin solmun lisäyksenkin yhteydessä. • Kierto-operaation suorittamisen jälkeen puu saattaa edelleen jäädä epätasapainoon, minkä tähden on kuljettava korjauskohdasta eteenpäin aina juureen asti ja tehdä matkan varrella tarpeelliset tasapainotukset ja korkeus-attribuuttien päivitykset. • Tarkastellaan seuraavassa esimerkkiä, jossa AVL-puusta poistetaan arvon 32 sisältävä solmu. Kyseessä on sama tilanne kuin edellä ennen uuden avaimen 54 lisäämistä. 13U.3 Solmun poistaminen AVL-puusta • Esimerkki: Poistetaan avaimen 32 sisältävä solmu. puu päätyy epätasapainoon juuren kohdalta. 3 3 44 1 17 32 78 0 -1 1 50 -1 0 88 0 -1 0 2 -1 48 -1 0 -1 78 17 2 -1 1 88 0 50 -1 -1 62 -1 44 0 -1 -1 48 -1 0 -1 62 -1 -1 13U.3 Solmun poistaminen AVL-puusta • Puuta joudutaan korjaamaan kaksoisrotaatiolla oikealla alipuulla siten, että z = a = 44, x = b = 50 ja y = c = 78 Tarvittava kaksoisrotaatio koostuu ensinnä oikeasta rotaatiosta solmulla 78, ja sitä seuraa vasen rotaatio solmun 44 kohdalla. • 3 2 44 a=z 0 2 1 44 78 17 -1 c=y -1 0 b=x 50 1 88 48 -1 -1 -1 62 -1 -1 0 a=z 48 1 0 62 78 0 c=y 88 1 -1 -1 17 b=x 50 -1 -1 -1 -1 -1 -1 -1 13U.3 Solmun poistaminen AVL-puusta AVL-puiden käsittelyn kustannukset: • Kierto-operaatiot tapahtuvat vakioajassa Θ(1) • Kyselyoperaatiot avaimen haku, minimin etsintä, maksimin etsintä, seuraajan määrääminen ja edeltäjän määrääminen saadaan jokainen suoritettua ajassa Ο(log2n), sillä puun korkeus h = Ο(log2n). • Myös lisäysoperaation aikavaativuus on luokkaa Ο(log2n), sillä ”normaalin” eli binääriseen hakupuuhun tehtävän lisäyksen kaltaisen osuuden kompleksisuus on Ο(log2n) ja korkeuksien päivitysten ja uudelleenjärjestämisten kompleksisuus on samoin Ο(log2n) • Poistollakin on sama kompleksisuus Ο(log2n), sillä ”normaalin” poiston osuuden kustannus on Ο(log2n) korkeuksien päivitysten ja uudelleenjärjestämisten kompleksisuus on samoin Ο(log2n) • AVL-puiden käsittelyyn tarkoitettujen algoritmien pseudokoodit sivuutetaan tässä. Ne esitetään tämän kurssin tarpeita ajatellen riittävästi AVL-puita käsittelevien TRAKLA-tehtävien yhteydessä. 18 B-puut • B-puut esiteltiin ensimmäistä kertaa artikkelissa R. Bayer & E. M. McCreight: Organization and maintenance of large ordered indexes, Acta Informatica 1 (1972), sivut 173 – 189. Taustaa: • Tietokoneen muistilaitteet voidaan karkeasti jakaa keskusmuistiin ja tukimuistiin (massamuistiin, levymuistiin) • Näistä keskusmuisti jakaantuu edelleen lukumuistiin ja työmuistiin. • Lukumuisti (ROM, Read-Only Memory) sisältää koneen käynnistystoimenpiteet ja muita lyhyitä toimintoja, kuten koneen käskykannan primitiiviset mikro-ohjelmat. Lukumuistin sisältö on pysyvää, ja kun se kertaalleen on sijoitettu paikoilleen, se on vain luettavissa – ei päivitettävissä. • Työmuisti (RAM, Random Access Memory) toimii ohjelmien ja niiden käyttämien tietojen tallennuspaikkana tietokoneen toiminnan aikana. Työmuistia voidaan käyttää sekä kirjoittamiseen että lukemiseen, eli sen sisältöä voidaan päivittää. Kun koneesta katkaistaan virta, työmuistin sisältö menetetään. • Levymuistin tallennuskapasiteetti on yleensä hyvin paljon suurempi kuin keskusmuistin, mutta vastaavasti sen käyttäminen on paljon hitaampaa kuin keskusmuistin. levymuisti koostuu päällekkäin pinotuista levyistä, joista kukin jakautuu sisäkkäisiin uriin, ja urat puolestaan asteittaisiin sektoreihin kaikkien levyjen luku- ja kirjoituspäät liikkuvat yhtä aikaa levypakan päällekkäiset urat muodostavat sylinterin – yhden ja saman sylinterin sisältä tietojen lukeminen tulee halvemmaksi (eli kestää lyhyemmän aikaa) kuin useilta eri urilta luettaessa (eri levyjen luku- ja kirjoituspäät kohdistuvat kaikki yhteen sylinteriin kerrallaan eri levypinnoilla. levymuisti on yleensä selvästi keskusmuistia halvempaa ostettaessa 18 B-puut • Termillä saantiaika tarkoitetaan aikaa, joka kuluu luku- ja kirjoituspään sijoittamiseen oikealle uralle ja sieltä oikean sektorin löytämiseen. Levyt pyörivät tietyllä nopeudella akselinsa ympäri, joten levyn pyörimisnopeus vaikuttaa saantiaikaan (oikean sektorin löytämiseen menevään aikaan). Kiintolevyjen saantiajat vaihtelevat usein välillä 6 – 14 millisekuntia (eli sekunnin tuhannesosaa). • Keskusmuistin normaali saantiaika on sen sijaan 50 – 100 nanosekuntia (eli sekunnin miljardisosaa, nano = 10-9). Täten pahimmassa tapauksessa saantiaika levyltä on ∗ ∗ = 280 000 kertaa suurempi kuin keskusmuistista • Seuraus: kun levyltä joudutaan lukemaan tietoja, kannattaa lukea samalla iso määrä tietoja kerrallaan toistuvien hakujen aiheuttamien saantiaikojen välttämiseksi. • Täten levyltä luetaan aina isohko määrä tietoja kerrallaan. Tällaista vakiokokoista levyltä luettavaa jaksoa kutsutaan muistisivuksi. Tyypillinen sivun pituus vaihtelee välillä 211 – 214 tavua. 18 B-puut • B-puut ovat tavallisen binäärisen hakupuun yleistyksiä. • Ne sopivat hyvin sellaisiin hakemistoihin, joiden alkiojoukko on niin iso, ettei se mahdu kerrallaan tietokoneen keskusmuistiin. tavoitteena on aikaa vievien luku- ja kirjoitusoperaatioiden minimoiminen • B-puiden solmuihin sijoitetaan binäärisestä hakupuusta poiketen yleensä useita avaimia, ja samoin solmuilla voi olla useita lapsia (yhteensä solmun avainten lukumäärä + 1) • Sen sijaan toisin kuin tavalliset binääriset hakupuut, B-puut ovat tasapainotettuja. • Puun minimiaste t määrää sen, kuinka monta avainta puun yksittäiseen solmuun pitää vähintään olla, ja montako avainta siihen voi enintään olla talletettuna. tallennettavien alkioiden vähimmäismäärä: t – 1 avainta (solmulla oltava vähintään t lasta) tallennettavien alkioiden enimmäismäärä: 2t – 1 avainta poikkeuksen alkioiden vähimmäismäärästä muodostaa juurisolmu, jossa avaimia on 1..2t – 1 lisäksi triviaalin poikkeuksen vähimmäismäärästä muodostaa tyhjä B-puu, jonka juuressa avaimia on 0 poikkeus lasten lukumäärään: lehtisolmuilla lapsien lukumäärä on 0 B-puun solmut: • Jokaisessa B-puun solmussa siihen tallennetut avaimet ovat kasvavassa (ei-vähenevässä) suuruusjärjestyksessä • Solmuissa esiintyy myös lapsiosoittimia (avainten lukumäärä + 1) 18 B-puut Määräämällä puulle sopivanlainen minimiaste saadaan aikaan seuraavaa: • Puun jokaisen solmun avainjoukko mahtuu aina tiettyyn tilaan. Levyltä luetaan kerrallaan suuri joukko avaimia, sillä – kuten edellä todettiin – ei ole kannattavaa lukea pelkästään yhtä avainta levyltä lukeminen vie runsaasti aikaa verrattuna keskusmuistissa tehtäviin operaatioihin. • Puu haarautuu voimakkaasti (sitä enemmän, mitä suurempi sen minimiaste on). Tämän takia avainta etsittäessä joudutaan varsin vähän siirtymään solmusta toiseen. • Esimerkki: B-puun solmulla, jolla on n avainta, on n + 1 lasta. Kaikki lehdet sijaitsevat samalla etäisyydellä eli yhtä kaukana juuresta. • M • juuri[P] • Q • T • X • • D • H • B C E F J K L N P R S V W Y Z 18 B-puut Levyoperaatioista: • B-puuta käsiteltäessä seuraavassa oletetaan, että dataa on niin paljon, ettei se kerrallaan mahdu keskusmuistiin • B-puiden algoritmit kopioivat valitun osan tiedoista levyltä keskusmuistiin. Kun halutut muutokset on tehty, tallennetaan päivitetyt tiedot takaisin levylle. • B-puiden algoritmit on suunniteltu siten, että vain vakiomäärä solmuja on keskusmuistissa kerrallaan. Siten keskusmuistin koko ei rajoita käsiteltävien B-puiden kokoa. Suoritusmalli: • Oletetaan, että x on viittaus johonkin objektiin. • Jos x:n viittaama objekti sijaitsee keskusmuistissa, voidaan sen attribuutteihin viitata suoraan (esimerkiksi avain[x]). • Jos kyseinen objekti sijaitsee levyllä, pitää ensin suorittaa operaatio LUE_LEVYLTÄ(x), joka lukee x:n osoittaman objektin keskusmuistiin. • Vasta silloin, kun x:n osoittama objekti on noudettu keskusmuistiin, sen sisältämiin tietoihin päästään käsiksi ja tarvittaessa muuttamaan niitä. • Operaatiolla TALLENNA_LEVYLLE(x) saadaan keskusmuistissa sijaitseva objekti x tallennettua takaisin levylle (päivitysten tultua tehdyiksi). 18 B-puut Esimerkki: Suuressa B-puussa voi yhdessä solmussa sijaita jopa 50 – 2 000 objektia (avainta) juuri[P] 1 000 1 000 1 000 1 solmu, avaimia 1 000, lapsisolmuja 1 001 1 000 1 000 1 000 1 001 solmua, avaimia 1 001 000 lapsisolmuja 1 002 001 1 000 1 000 1 000 1 000 1 000 1 002 001 solmua, avaimia 1 002 001 000 • Seuraavassa oletetaan, että jokaiseen avaimeen liittyvä mahdollinen lisä- eli satelliittidata on myös tallennettuna samaan solmuun kuin itse avainkin. • B+-puu: Kaikki satelliittidata tallennetaan lehtiin, kun taas sisäsolmuihin tallennetaan ainoastaan avaimet ja lapsiosoittimet. • B+-puuta käsitellään tarkemmin tietokantoihin liittyvillä erikoiskursseilla. 18.1 B-puun määritelmä 1. Kuhunkin yksittäiseen solmuun x liittyvät tiedot: (i) Attribuutti n[x] sisältää tiedon solmuun x tallennettujen avainten lukumäärästä. (ii) Solmuun x sijoitetut avaimet on tallennettu kasvavaan (ei-vähenevään) järjestykseen: avain1[x] ≤ avain2[x] ≤ … ≤ avainn[x][x] (iii) Attribuutti lehti[x] sisältää totuusarvon tosi, jos solmu x on lehtisolmu, ja muulloin arvon epätosi. 2. Lisäksi jokainen solmu (paitsi lehtisolmut) sisältävät lisäksi n[x] + 1 kappaletta osoittimia: c1[x], c2[x], c3[x], …, cn[x][x], cn[x] + 1[x] Kyseiset osoittimet, ci :t, osoittavat solmun x järjestyksessä i. lapsisolmuun. Koska lehtisolmuilla ei ole lapsia, niiden ci-arvot ovat määrittelemättömiä. 3. Merkitään ki:llä solmua, joka on tallennettu sellaiseen alipuuhun, jonka juurena esiintyy solmu ci[x]. Tällöin on voimassa: k1 ≤ avain1[x] ≤ k2 ≤ avain2[x] ≤ … ≤ avainn[x][x] ≤ kn[x] + 1 Toisin sanoen, kaikki solmun i. lapsiosoittimen takaa aukeavan alipuun sisältämät avaimet ovat suurempia tai yhtä suuria kuin solmun x i – 1. avain (kun i > 1) sekä pienempiä tai yhtä suuria kuin solmun x i. avain (kun i < n[x] + 1). 18.1 B-puun määritelmä 4. Kaikki lehdet ovat samalla etäisyydellä juuresta; puun korkeutta ilmaisee merkintä h. 5. B-puulle on määriteltynä minimiaste t (t ≥ 2): (i) Jokaisessa solmussa (paitsi juuressa) on oltava vähintään t – 1 avainta. Siten jokaisella sisäsolmulla on oltava vähintään t lapsisolmua. Mikäli kyseessä ei ole tyhjä B-puu, pitää juureen olla sijoitettuna vähintään yksi avain. (ii) Jokaisessa solmussa voi olla enintään 2t – 1 avainta. Täten solmulla voi olla korkeintaan 2t kappaletta lapsia. Solmu on täynnä, mikäli se sisältää tarkalleen 2t – 1 avainta. • • Yksinkertaisin mahdollinen B-puu on sellainen, että t = 2. Tällöin jokaisella sisäsolmulla on joko 2, 3 tai 4 lasta. Siten tällaista puuta kutsutaan myös 2-3-4-puuksi. Lause 18.1: Jos n ≥ 1, niin sellaisen n-avaimisen B-puun, jonka minimiaste t ≥ 2, korkeus on h ≤ logt Todistus: Pienin mahdollinen B-puu on seuraavanlainen: Juureen on tallennettu yksi alkio. Jokaisessa muussa solmussa on minimaalinen määrä eli t – 1 alkiota. Etäisyydellä i olevalla tasolla (juuresta laskettuna) on 2ti – 1 solmua (solmuilla on t lasta, paitsi juurella aina 2 riippumatta t:n valinnasta, kunhan n > 2). Täten n ≥ 1 + (t – 1) ∑ 2 = 1 + 2(t – 1) ∑ = 1 + 2(t – 1) = 2th – 1. Nyt ≥ th ⇔ h ≤ logt . 18.1 B-puun määritelmä • Lemma: Jos x ≠ 1, niin ∑ = Todistus: Kaikilla x ≠ 1 ja n ≥ 0: ∑ = x ∑ = (x – 1) ∑ = x0 + x1 + x2 + … + xn x1 + x2 + … + xn -1 ja + xn + 1 + xn + 1 (vähennettiin alemmasta summasta ylempi). 18.2 B-puun perusoperaatiot HAKU, LUONTI JA LISÄYS Sopimus: Puun juuri on aina keskusmuistissa, joten operaatiota LUE_LEVYLTÄ(juuri[P]) ei milloinkaan tarvitse suorittaa. Sen sijaan operaatio TALLENNA_LEVYLLE(juuri[P]) on tarpeellinen, mikäli juurisolmuun tehdään muutoksia. 18.2 B-puun perusoperaatiot Haku B-puusta • Muistuttaa melkoisesti hakua binäärisestä hakupuusta. • Nyt joudutaan kuitenkin suorittamaan hakua myös solmun sisällä, koska B-puun solmussa voi sijaita useita avaimia. Syöte ja tuloste hakua suoritettaessa (oletetaan, että puusta haetaan avainta k): • • • • Syötteenä annetaan parametri x, joka viittaa (ali)puun juureen. Lisäksi syötteenä annetaan haettava avain k. Ylimmän tason kutsu: HAE_B-PUUSTA(juuri[P], k) Jos etsittävä avain k esiintyy puussa, palautetaan järjestetty pari (y, i), missä y on B-puun solmu ja i on sellainen indeksi, jolle on voimassa avaini[y] = k • Ellei etsittyä avainta k löydy puusta, palautetaan arvo NIL. • Seuraavaksi esitetään B-puusta hakua suorittavan algoritmin HAE_B-PUUSTA pseudokoodi. 18.2 B-puun perusoperaatiot HAE_B-PUUSTA(x, k) 1 i := 1 2 WHILE i ≤ n[x] AND k > avaini[x] DO 3 i := i + 1 4 IF i ≤ n[x] AND k = avaini[x] 5 THEN RETURN (x, i) 6 IF lehti[x] 7 THEN RETURN NIL 8 ELSE LUE_LEVYLTÄ(ci[x]) 9 RETURN HAE_B-PUUSTA(ci[x], k) Algoritmin toimintaperiaate: luetaan nykyisestä solmusta x avaimia niin pitkään, kunnes joko 1) kohdataan ensimmäinen avain, jonka arvo on suurempi tai yhtä suuri kuin etsittävä avain k tai 2) solmun x kaikki avaimet on jo ehditty tutkia, jolloin k oli kaikkia näitä suurempi tapauksessa 1) tutkitaan seuraavaksi, oliko solmun x i. avain arvoltaan k jos oli, palautetaan järjestetty pari (x, i) ellei, jatketaan hakua x:n i. lapsisolmusta, jos x ei ole lehti, ja muussa tapauksessa haun todetaan epäonnistuneen palauttamalla NIL-osoitin tapauksessa 2) jatketaan hakua solmun x viimeisestä lapsisolmusta, mikäli x ei ole lehtisolmu. Jos x on lehti, haku on epäonnistunut ja palautetaan NIL-osoitin. 18.2 B-puun perusoperaatiot Algoritmin kompleksisuus: Levyoperaatioiden kustannus on Ο(h) = Ο(logtn). Pahimmassa tapauksessa etsintää joudutaan jatkamaan aina lehtitasolle asti, jolloin puun jokaisella tasolla juurta lukuun ottamatta suoritetaan yhden solmun haku levyltä. Kokonaissuoritusaika: Koska n[x] < 2t, niin suoritusaika on Ο(th) = Ο(t logtn), sillä jokaisella tasolla joudutaan tutkimaan yksi solmu, ja pahimmassa tapauksessa sen kaikki avaimet joudutaan selaamaan läpi (oletuksena on, että solmujen avaimia selataan lineaarihaulla). • Esimerkki: Etsitään seuraavassa kuvassa esitetystä B-puusta alkiota R. • M • juuri[P] • Q • T • X • • D • H • B C E F J K L N P R S V W Y Z Haun aikana joudutaan tutkimaan vain esitetyn puun punaisella värjätyt solmut. 18.2 B-puun perusoperaatiot Tyhjän B-puun perustaminen • • Algoritmi PERUSTA_B-PUU(P) on (konstruktori-)operaatio, joka luo uuden tyhjän B-puun P. LUO_SOLMU on vakioaikainen (Ο(1)) operaatio, joka varaa tilaa levyltä juurisolmua varten. PERUSTA_B-PUU(P) 1 x := LUO_SOLMU() 2 lehti[x] := tosi 3 n[x] := 0 4 TALLENNA_LEVYLLE(x) 5 juuri[P] := x Algoritmin PERUSTA_B-PUU kompleksisuus on vakioaikainen: kaksi vakioaikaista levyoperaatiota ja kolme asetuslausetta. B-puuhun lisääminen • • • • B-puuhun lisääminen on hankalampaa kuin tavalliseen binääriseen hakupuuhun lisääminen, jossa uusi solmu lisätään aina uudeksi lehdeksi. B-puuhun avain lisätään sitä vastoin jo olemassa olevaan lehtisolmuun. Ongelmatilanne: Täyteen solmuun ei enää pystytä lisäämään avaimia (solmu on täynnä, jos se sisältää jo 2t – 1 avainta). Ratkaisu: Täysi solmu halkaistaan seuraavasti: avain1[y], …, avaint – 1[y], avaint[y], avaint + 1[y], …, avain2t – 1[y] mediaani t – 1 kappaletta t – 1 kappaletta 18.2 B-puun perusoperaatiot • Halkaistavan solmun y alkuosan t – 1 avainta ja loppuosan t – 1 avainta muodostavat uudet solmut, ja y:n keskimmäinen avain eli avaint[y] siirretään vanhempaansa, jonka sekä avaimien että lasten lukumäärä kasvaa nyt yhdellä. • Kannattaa huomioida, että etsittäessä oikeaa sijoituspaikkaa uudelle lisättävälle avaimelle lehtitasolta jokainen vastaantuleva täysi solmu joudutaan halkaisemaan. Lapsisolmun halkaiseminen Syöte: • Ei-täysi isäsolmu x (sinne voidaan sijoittaa vielä ainakin yksi avain lisää) • Sellaiset solmu y ja indeksi i, että y = ci[x], ja lisäksi solmu y on täysi (ei mahdu enää lisäavaimia) Toiminta: • Solmu y halkaistaan edellä kuvatulla tavalla. • Solmua x joudutaan järjestelemään uudelleen siten, että sille luodaan uusi lapsisolmu. • Mikäli juurisolmu joudutaan halkaisemaan, luodaan ensiksi uusi juurisolmu, jonka lapseksi vanha juurisolmu asetetaan. • Seuraavaksi esitetään hahmotelma lapsisolmun halkaisualgoritmin toiminnasta. 18.2 B-puun perusoperaatiot Lapsisolmun halkaiseminen: x • • • N • W • • • • • • N • S • W • • • x i i i+1 y y • P • Q • R • S • T • U • V• • P • Q • R • z • T • U • V • • Ylempänä näkyvässä hahmotelmassa ollaan avainta lisättäessä edetty solmuun y, jonka havaitaan olevan täynnä. solmun ensimmäiset kolme avainta P, Q ja R (tässä t = 4) jäävät paikoilleen solmuun y solmun keskimmäinen eli mediaaniavain S nousee tasoa ylöspäin isäsolmuunsa solmun y kolmea viimeistä avainta varten perustetaan uusi solmu z, jonne ne viedään kaikkine mahdollisine viittauksineen ja satelliittidatoineen. • Seuraavaksi esitellään lapsisolmun halkaisun suorittavan algoritmin pseudokoodi. 18.2 B-puun perusoperaatiot HALKAISE_B-PUUN_LAPSISOLMU(x, i, y) 1 z := LUO_SOLMU() 2 lehti[z] := lehti[y] 3 n[z] := t – 1 4 FOR j := 1, 2, …, t – 1 DO 5 avainj[z] := avainj + t[y] 6 IF NOT lehti[y] 7 THEN FOR j := 1, 2, …, t DO 8 cj[z] := cj + t[y] 9 n[y] := t – 1 10 FOR j := n[x] + 1, n[x], …, i + 1 DO 11 cj + 1[x] := cj[x] 12 ci + 1[x] := z 13 FOR j := n[x], n[x] – 1, …, i DO 14 avainj + 1[x] := avainj[x] 15 avaini[x] := avaint[y] 16 n[x] := n[x] + 1 17 TALLENNA_LEVYLLE(x) 18 TALLENNA_LEVYLLE(y) 19 TALLENNA_LEVYLLE(z) 18.2 B-puun perusoperaatiot Algoritmin toimintaperiaate: • Rivit 1 – 8: Alustetaan uusi solmu z. Solmu z merkitään lehdeksi, jos halkaistava solmu y on sellainen. Solmun z avainten lukumääräksi asetetaan t – 1, ja siihen kopioidaan y:n t – 1 suurinta avainta. Jos kyseessä ei ole lehtisolmu, myös y:n t ylintä lapsiosoitinta kopioidaan solmuun z. • Rivi 9: Asetetaan t – 1 myös solmun y uudeksi avainten lukumääräksi. • Rivit 10 – 16: Näillä riveillä siirretään x:n jälkimäisen puolikkaan lapsiosoittimia yksi askel oikealle ja asetetaan uusi solmu z solmun x lapseksi. Lisäksi x:n jälkimmäisen puolikkaan avainarvoja siirretään yksi askel oikealle ja lisätään (entisen) solmun y mediaanialkio (järjestyksessä t. alkio) solmuun x. • Rivit 17 – 19: Kirjoitetaan muutetut solmut x, y ja z levylle. Lapsisolmun halkaisualgoritmin kompleksisuus: • Algoritmin aikavaativuus määräytyy silmukoiden perusteella, joissa tehtävien kierrosten määrää voidaan rajoittaa lausekkeella Θ(t). Rivien 1 sekä 17 – 19 levyoperaatiot ovat vakioaikaisia, kuten myös silmukoissa suoritettavat (rivit 5, 8, 11 ja 14) ja niiden ulkopuolelle (rivit 2, 3, 9, 12, 15 ja 16) jäävät lauseet. • Seuraavaksi esitellään ylimmän tason algoritmi LISÄÄ_B-PUUHUN, jolla voidaan tuoda puuhun uusi avain. Algoritmissa huomioidaan myös tilanne, jossa juurisolmu joudutaan halkaisemaan. • Algoritmin tarvitsema alialgoritmi LISÄÄ_EI-TÄYTEEN_B-PUUHUN esitellään heti tämän perään. 18.2 B-puun perusoperaatiot LISÄÄ_B-PUUHUN(P, k) 1 r := juuri[P] 2 IF n[r] = 2t – 1 /* Onko juurisolmu jo täyttynyt? */ 3 THEN s := LUO_SOLMU() 4 juuri[P] := s 5 lehti[s] := epätosi 6 n[s] := 0 7 c1[s] := r 8 HALKAISE_B-PUUN_LAPSISOLMU(s, 1, r) 9 LISÄÄ_EI-TÄYTEEN_B-PUUHUN(s, k) 10 ELSE LISÄÄ_EI-TÄYTEEN_B-PUUHUN(r, k) • Esimerkki täyden juuren halkaisusta: juuri[P] juuri[P] r • H • • A • D • F • H • L • N • P• • A • D • F • • L • N • P • 18.2 B-puun perusoperaatiot LISÄÄ_EI-TÄYTEEN_B-PUUHUN(x, k) 1 i := n[x] 2 IF lehti[x] 3 THEN WHILE i ≥ 1 AND k < avaini[x] DO 4 avaini + 1[x] := avaini[x] 5 i := i – 1 6 avaini + 1[x] := k 7 n[x] := n[x] + 1 8 TALLENNA_LEVYLLE(x) 9 ELSE WHILE i ≥ 1 AND k < avaini[x] DO 10 i := i – 1 11 i := i + 1 12 LUE_LEVYLTÄ(ci[x]) 13 IF n[ci + 1[x]] = 2t – 1 14 THEN HALKAISE_B-PUUN_LAPSISOLMU(x, i, ci[x]) 15 IF k > avaini[x] 16 THEN i := i + 1 17 LISÄÄ_EI-TÄYTEEN_B-PUUHUN(ci[x], k) 18.2 B-puun perusoperaatiot Algoritmin toimintaperiaate: • Rivi 1: alustetaan laskuri i solmun x avainten lukumäärällä • Rivi 2: testataan, ollaanko jo saavuttu lehtisolmuun • Rivit 3 – 8: Tarkastellaan tapausta, jolloin solmu x on lehti. Tällöin avain k tallennetaan kyseiseen solmuun • Rivi 9 – 12: Paraikaa tarkasteltava solmu x ei ole lehti. Valitaan x:n lapsiosoittimista se, jota pitkin lisäämisen hakupolku jatkuu eteenpäin k:n perusteella. • Rivit 13 – 16: Tutkitaan, onko valittu solmu täynnä vai ei. Mahdollinen täysi solmu halkaistaan ja ratkaistaan, kummalta puolelta halkaistun solmun mediaania lisäyspaikan hakua jatketaan. • Rivit 17: Kutsutaan rekursiivisesti lisäysalgoritmia ei-täydelle juurisolmulle sille x:n lapsisolmulle, jonka kautta k:n lopullinen sijoituspaikka löytyy. Lisäysalgoritmin kompleksisuus: • Jokainen algoritmin LISÄÄ_EI-TÄYTEEN_B-PUUHUN kutsu tekee vain vakiomäärän luku- ja kirjoitusoperaatioita levyltä/levylle. • Algoritmin LISÄÄ_B-PUUHUN suorituksen aikana tehdään Ο(h) = Ο(logtn) levyoperaatiota. • Kokonaissuoritusaika: Ο(th) = Ο(tlogtn). 18.2 B-puun perusoperaatiot Esimerkki: Lisätään seuraavaan B-puuhun järjestyksessä avaimet B, Q, L ja F. Puun minimiaste t = 3. G M P X A C D E J K N O R S T U V Y Z Vaihe 1: Lisätään B – lisäys onnistuu suoraan vasemmanpuoleisimpaan lehteen ainoastaan avaimia D ja E joudutaan siirtämään eteenpäin G M P X A B C D E J K N O R S T U V Y Z 18.2 B-puun perusoperaatiot Vaihe 2: Lisätään Q – kyseinen avain kuuluisi toiseksi viimeiseen lehteen, joka on nyt halkaistava ennen lisäystä avain T joudutaan nostamaan solmusta juureen juurisolmu täyttyy Q päätyy halkaistavaan solmuun, sillä se kuuluu ennen mediaanialkiota S G M P X A B C D E J K N O R S T U V Y Z G M P T X A B C D E J K N O Q R S U V Y Z 18.2 B-puun perusoperaatiot Vaihe 3: Lisätään L – kyseinen avain kuuluu toiseen lehteen, jossa on kylläkin tilaa, mutta juurisolmu on ehtinyt täyttyä ensi töiksi joudutaan juurisolmu halkaisemaan tämän jälkeen L sijoitetaan paikalleen lehtisolmuun, johon se kuuluu G M P T X A B C D E J K Q R S N O U V Y Z P G M A B C D E J K L T X N O Q R S U V Y Z 18.2 B-puun perusoperaatiot Vaihe 4: Lisätään F – kyseinen avain kuuluisi ensimmäiseen lehteen, joka on täyttynyt kyseinen solmu joudutaan halkaisemaan, ja C siirtyy tasoa ylemmäs tämän jälkeen F sijoitetaan halkaisun tuloksena syntyneeseen uuteen solmuun P G M A B C D E J K L T X Q R S N O U V Y Z P C G M A B D E F J K L T X N O Q R S U V Y Z 18.3 Avaimen poistaminen B-puusta • • • • • Algoritmi POISTA_AVAIN_B-PUUSTA poistaa avaimen k sellaisesta alipuusta, jonka juurena esiintyy x. Algoritmi on kirjoitettu siten, että aina kun sitä kutsutaan solmulla x, on kyseisessä solmussa vähintään minimiasteen t verran avaimia (poikkeuksena kuitenkin juuri). Määritelmän mukaan B-puussa pitää jokaisessa solmussa sijaita vähintään t – 1 avainta. Edellisessä kohdassa esitetyn tiukemman vaatimuksen ansiosta riittää, että poistettaessa avainta kuljetaan puussa yksistään alaspäin (KYSYMYS: Millä perusteella?). Tiukentamisesta seuraa kuitenkin joitakin ylimääräisiä toimenpiteitä. Mikäli juurisolmu x tulee tyhjäksi, se poistetaan, ja sen ainoasta lapsesta c1[x] tulee uusi juuri. Samalla puun korkeus pienenee. Koska poistoalgoritmi on verrattain pitkä ja monimutkainen, sen pseudokoodia ei esitetä tässä, vaan tyydytään ainoastaan kuvaamaan periaatteet, miten poisto etenee eri tapauksissa. Solmun poistamisen periaatteet 1. Jos poistettava avainta k ei löydy solmusta x, siirrytään puussa alaspäin sopivaan alipuuhun seuraamalla lapsiosoitinta ci[x] ja jatketaan etenemistä rekursiivisesti. Jos osoittimen ci[x] osoittamassa solmussa on vain t – 1 avainta, suoritetaan seuraavista joko toimenpide (a) tai (b) sen takia, että solmuun tulisi vähintään t avainta. (a) Jos ci[x]:ssä on vain t – 1 avainta mutta sen sisaruksella on vähintään t avainta, lainataan ci[x]:lle yksi avain tämän isäsolmulta x ja palautetaan sieltä lainatun avaimen tilalle yksi avain siltä ci[x]:n sisarukselta, jossa avaimia on vähintään t kappaletta. Mahdollinen lapsiviittaus siirretään samalla. 18.3 Avaimen poistaminen B-puusta (b) Jos sekä ci[x]:ssä että sen sisaruksilla on vain t – 1 avainta, limitetään solmu ci[x] toisen sisaruksensa kanssa ja lisätään vielä isäsolmulta x yksi avain limityksessä muodostuneen solmun mediaaniksi. 2. Mikäli k on lehtisolmussa, tuhoa avain k sieltä. 3. Jos k sijaitsee jossain sisäsolmussa x, toimitaan seuraavasti: (a) Jos avainta k edeltävässä lapsessa y on vähintään t avainta, etsitään k:n edeltäjä k’ kyseisestä alipuusta. Avain k’ tuhotaan rekursiivisesti alipuustaan, ja se sijoitetaan alun perin poistettavaksi tarkoitetun avaimen k tilalle. (b) Jos avainta k seuraavassa lapsessa z on vähintään t avainta, etsitään k:n seuraaja k’ kyseisestä alipuusta. Avain k’ tuhotaan jälleen rekursiivisesti alipuustaan kuten edellä, ja se sijoitetaan alun perin poistettavaksi tarkoitetun avaimen k tilalle. (c) Jos kummassakin x:n lapsisolmussa y ja z on vain t – 1 avainta, muodostetaan uusi solmu limittämällä solmut y ja z, liitetään muodostuneeseen solmuun avainarvo k ja poistetaan k rekursiivisesti. • Poiston kompleksisuus on Ο(t logtn), ja levyoperaatioita tehdään Ο(logtn). 18.3 Avaimen poistaminen B-puusta Esimerkki: Poistetaan seuraavasta B-puusta järjestyksessä avaimet F, M, G, D ja B. Kyseessä on samainen puu, joka saatiin aikaan lisäysoperaatiota käsitelleen esimerkin päätteeksi. Puun minimiaste t = 3. P C G M A B D E F J K L T X N O Q R S U V Y Z Vaihe 1: Poistetaan F – avain sijaitsee lehdessä (ensiksi kahdesti tapaus 1, sitten tapaus 2) hakupolun varrelle osuvissa solmuissa juuresta eteenpäin (merkitty kuvaan punaisella värillä) ≥ 3 avainta poistetaan F suoraan nykyisestä sijaintipaikastaan ilman lisätoimenpiteitä P T X C G M A B D E ■ J K L N O Q R S U V Y Z 18.3 Avaimen poistaminen B-puusta P T X C G M A B D E J K L N O Q R S U V Y Z Vaihe 2: Poistetaan M – avain sijaitsee sisäsolmussa (ensiksi tapaus 1, sitten tapaus 3a ja lopulta 2) hakupolun varrelle osuvissa solmuissa juuresta eteenpäin ≥ 3 avainta avainta M edeltävässä lapsisolmussa ≥ t = 3 avainta: etsitään kyseisestä alipuusta M:n edeltäjä L, joka kopioidaan M:n paikalle ja poistetaan lehdestä P T X C G L A B D E J K ■ N O Q R S U V Y Z 18.3 Avaimen poistaminen B-puusta P T X C G L A B D E J K N O Q R S U V Y Z Vaihe 3: Poistetaan G – tämäkin avain kuten edellä M sijaitsee sisäsolmussa (tapaukset 1, 3c, 2) hakupolun varrelle osuvissa lehtisolmuissa kummassakin vain 2 avainta limitetään solmut DE ja JK keskenään ja tuodaan sen mediaanialkioksi isäsolmusta poistettava avain G, joka nyt saman tien poistetaan lehdestä P T X C L A B D E G J K N O Q R S U V Y Z 18.3 Avaimen poistaminen B-puusta P T X C L A B Q R S N O D E J K U V Y Z Vaihe 4: Poistetaan D – tämä avain sijaitsee lehtisolmussa hakupolun varrelle osuvassa sisäsolmussa ja sen ainoassa sisaruksessa kummassakin kuitenkin vain 2 avainta (aluksi tapaus 1b) limitetään solmut CL ja TX keskenään ja tuodaan sen mediaanialkioksi isäsolmusta poistettava avain P, jolloin puu kutistuu ja uudeksi juureksi CLPTX lopuksi poistetaan D lehdestä DEJK ilman pulmia (tapaus 2) ■ C A B ■ E J K L N O P T X Q R S U V Y Z 18.3 Avaimen poistaminen B-puusta C A B L P T Q R S N O E J K X U V Y Z Vaihe 5: Poistetaan B – tämäkin avain sijaitsee lehtisolmussa (aluksi tapaus 1a) hakupolun varrelle osuvassa lehtisolmussa vain 2 avainta, mutta sen ainoalla sisaruksella – solmulla EJK – on avaimia minimiasteen t = 3 verran. tuodaan solmuun AB täytteeksi isäsolmusta sen ensimmäinen avain C, jonka paikan isäsolmussa ottaa tämän seuraaja E, joka menettää paikkansa toisessa lehtisolmussa lopuksi poistetaan B täydentyneestä lehdestä ABC ilman pulmia (tapaus 2) E A ■ C ■ J K L N O P T X Q R S U V Y Z 19X Binomikeko • Binomikeot esiteltiin ensimmäistä kertaa artikkelissa R. Bayer: Symmetric binary B-trees: data structure and maintenance algorithms, Acta Informatica 1 (1972), sivut 290 – 306. • Binomikeot ovat minimikekoja, jotka ovat limitettävissä. • Niille on määritelty prioriteettijonon operaatiot. Operaatiot: • • • • LUO_KEKO(): luo tyhjän binomikeon LISÄÄ(H, x): lisää alkion x binomikekoon H MINIMI(H): palauttaa viittauksen binomikeon H siihen alkioon, jonka avain on pienin POISTA_MINIMI(H): poistaa binomikeosta sen pienimmän alkion palauttamalla samalla osoittimen kyseiseen alkioon • PIENENNÄ_AVAINARVOA(H, x, k): pienennetään binomikeossa H sijaitsevaan solmuun x tallennettua avainarvoa arvoon k. Oletuksena on, ettei syötteenä annettava k ole nykyistä avainarvoa isompi. • POISTA(H, x): poistaa solmualkion x binomikeosta H • UNIONI(H1, H2): perustaa uuden binomikeon, joka sisältää kekojen H1 ja H2 alkiot 19X Binomikeko • Seuraavassa taulukossa on esitelty eri operaatioiden pahimman tapauksen suoritusaikoja tavalliselle minimikeolle sekä binomikeolle: Operaatio Keko Binomikeko Θ(1) Θ(1) Θ(log2n) Ο(log2n) Θ(1) Ο(log2n) Θ(log2n) Θ(log2n) Θ(n) Ο(log2n) PIENENNÄ_AVAINARVOA** Θ(log2n) Θ(log2n) POISTA** Θ(log2n) Θ(log2n) LUO_KEKO LISÄÄ MINIMI POISTA_MINIMI UNIONI* *) Operaation MUODOSTA_KEKO kustannus on Θ(n). **) Alkion paikan pitää olla tiedossa etukäteen. 19X.1 Binomipuut • Binomikeko muodostuu binomipuista. • Binomipuu Bk on järjestetty puu, joka määritellään rekursiivisesti seuraavalla tavalla: B0 koostuu yhdestä solmusta Bk (k > 0) koostuu kahdesta juurisolmuistaan toisiinsa linkitetystä Bk – 1-binomipuusta. B0 Bk Bk – 1 Bk – 1 • Binomipuun Bk korkeus on k. • Seuraavassa ovat nähtävissä binomipuut B0, B1, B2, B3 ja B4 vasemmalta oikealle. B1 B2 B3 B0 B4 19X.1 Binomipuut • Lemma: Tarkastellaan binomipuuta Bk. (a) Binomipuussa on 2k solmua. (b) Puun juuren lasten lukumäärä on k. Lisäksi, jos lapset numeroidaan vasemmalta oikealle k – 1, k – 2, …, 1, 0, niin solmu i (0 ≤ i ≤ k – 1) on binomipuun Bi juuri. Todistus: (a) Induktiolla k:n suhteen. i) Väite pitää paikkansa, kun k = 0, sillä puussa B0 on yksi solmu. ii) Oletetaan, että väite pitää paikkansa puulla Bk – 1. Tästä kuitenkin seuraa, että väite on voimassa myös puulla Bk, sillä koska Bk muodostuu kahdesta Bk – 1-puusta, on sen solmujen lukumäärä 2k – 1 + 2k – 1 = 2 ⋅ 2k – 1 = 2(k – 1) + 1 = 2k. (b) Samoin induktiolla k:n suhteen. i) Väite on voimassa arvolla k = 0, sillä puussa B0 on ainoastaan juurisolmu, jolla ei selvästikään ole yhtään lasta. ii) Koska Bk muodostuu kahdesta yhteen linkitetystä Bk – 1-puusta, on Bk :n juurella yksi lapsi enemmän kuin Bk – 1-puun juurella. Induktio-oletuksen mukaan Bk – 1:n juurella on k – 1 lasta, kun k > 0, joten Bk :lla on siten k lasta. Binomipuun B1 ainoa lapsi on B0. 19X.1 Binomipuut Induktio-oletuksen mukaan lisäksi puun Bk – 1 juuren lapset ovat vasemmalta lukien järjestyksessä Bk – 2, Bk – 3, Bk – 4, …, B2, B1, B0. Linkitettäessä kaksi Bk – 1-puuta (k > 1) yhteen, tulevat toisen Bk – 1-puun juuren lapset linkityksen tuloksena muodostuneen Bk -puun juuren lapsiksi. Lisäksi toinen Bk – 1-puu päätyy vastikään syntyneen Bk -puun lapseksi. Seuraus: n-solmuisen binomipuun solmujen maksimaalinen lasten määrä on log2n. Todistus: Solmujen määrä n = 2k, joten k = log2n. Koska juurella on eniten lapsia, ja juuren lasten lukumäärä on k, väite seuraa tästä. Binomikeko • Binomikeko H koostuu joukosta binomipuita, jotka täyttävät binomikeko-ominaisuudet: (1) Kukin keon H binomipuista on minimikekojärjestyksessä, eli jokaisen solmun avain on vähintään solmun isäsolmussa sijaitsevan avainarvon suuruinen (kunkin puun pienin avain on oltava juuressa). (2) Määritellään k binomipuun Bk asteeksi. Astetta k olevia binomipuita voi esiintyä binomikeossa H korkeintaan yksi. 19X.1 Binomipuut • Luvun n binääriesityksen pituus on log2n + 1 bittiä, ja se on muotoa: <blog2n, blog2n – 1, …, b2, b1, b0>. Siten n = ∑ 2 . • Esimerkki: Luvun 11 binääriesitys on 1011, joten 1110 = 1 ⋅ 20 + 1 ⋅ 21 + 0 ⋅ 22 + 1 ⋅ 23 • Tarkastellaan n-alkioista binomikekoa H. Koska binomikekoon voi kuulua vain enintään yksi tiettyä astetta oleva binomipuu ja puun Bk koko on 2k, muodostuu luvun n binääriesityksen ja binomipuun H välille seuraavanlainen yhteys: Puu Bk on binomikeossa H ⇔ bitti bk = 1 • Edellisen perustella n-alkioinen binomikeko H sisältää enintään log2n + 1 binomipuuta. • Koska binomipuun Bk korkeus on k ja n:n binääriesitys on muotoa n = ∑ 2, niin H:n korkeimman puun korkeus on log2n. 19X.1 Binomipuut • Esimerkki: Luvun 1110 binääriesitys on <b3, b2, b1, b0> = 1011, joten 11-alkioinen binomikeko muodostuu puista B0, B1 ja B3. alku[H] B0 10 B1 6 1 8 12 11 25 • Binomikeon esitysmuoto Vasemmanpuoleisin lapsi ja oikea sisarus -rakenne 17 14 35 29 B3 19X.1 Binomipuut • Solmualkion x attribuutit: avain[x] – solmun avainarvo mahdollinen lisädata (satelliittidata) vanhempi[x] – osoitin isäsolmuun lapsi[x] – osoitin vasemmanpuoleisimpaan lapsisolmuun sisarus[x] – osoitin oikeanpuoleiseen sisarukseen aste[x] – tieto x:n lasten lukumäärästä • Huomioitavaa: Jos solmu x on juuri, niin vanhempi[x] = NIL Jos solmu x on isänsä kaikkein oikeanpuoleisin lapsi, niin silloin sisarus[x] = NIL Lisäksi koko binomikeolle H on määritelty attribuutti alku[H], joka viittaa keon ensimmäisen binomipuun juureen. Binomipuut on sijoitettu kekoon H kasvavan asteluvun mukaisessa järjestyksessä: B0, B1, B2, …, Blog2n 19X.1 Binomipuut • Seuraavassa vielä pieni esimerkki binomikeosta: / / / 6 2 alku[H] / vanhempi[x] • • • • 14 20 1 0 • avain[x] • • / • • • / • • • • • • • • • 15 aste[x] 0 / / • lapsi[x] • • sisarus[x] / / / / / / / / / 19X.2 Binomikeon operaatiot Binomikeon luonti: tapahtuu algoritmilla PERUSTA_BINOMIKEKO() algoritmi on konstruktori, joka varaa muistista tilaa binomikekoa varten kompleksisuus Θ(1), eli operaatio on vakioaikainen Pienimmän avaimen etsintä: Tarkastellaan seuraavassa algoritmia BINOMIKEON_MINIMI(H), joka palauttaa viittauksen (ensimmäiseen) sellaiseen binomikeon H solmuun, josta löytyy keon pienin avain. Minimiavain löytyy väkisin jonkin kekoon kuuluvan binomipuun juuresta. BINOMIKEON_MINIMI(H) 1 y := NIL 2 x := alku[H] 3 min := ∞ /* Asetetaan minimille alkuarvoksi ”ääretön” eli jokin maksimin taatusti ylittävä luku */ 4 WHILE x ≠ NIL DO 5 IF avain[x] < min 6 THEN min := avain[x] 7 y := x 8 x := sisarus[x] 9 RETURN y • Algoritmin kompleksisuus on Ο(log2n), sillä binomipuiden juurilista on käytävä läpi kokonaan. 19X.2 Binomikeon operaatiot Kahden binomikeon yhdistäminen: Apumetodilla LINKITÄ_BINOMIPUUT(y, z) pystytään linkittämään kaksi Bk – 1-puuta y ja z siten, että muodostuu yksi Bk-puu, jonka juureksi päätyy z. z sisarus[y] (entinen lapsi[z]) y Bk – 1 Bk – 1 LINKITÄ_BINOMIPUUT(y, z) 1 vanhempi[y] := z 2 sisarus[y] := lapsi[z] 3 lapsi[z] := y 4 aste[z] := aste[z] + 1 • Kahden binomipuun yhdistäminen on vakioaikainen operaatio (Ο(1)), sillä siihen kuuluu pelkkiä yksinkertaisia asetuslauseita. 19X.2 Binomikeon operaatiot Kahden binomikeon limitys: Apumetodilla BINOMIKEKOJEN_LIMITYS(H1, H2) saadaan limitettyä binomikekojen H1 ja H2 yhdeksi ketjutetuksi juurilistaksi, jossa binomipuiden asteet ovat ei-vähenevässä järjestyksessä. Algoritmin toiminta on samanlaista kuin limityslajittelussa käytettävän limitysalgoritmin. Kannattaa huomioida, ettei operaation tulokseksi yleisesti saada vielä binomikekoa. • Esimerkki: alku[H1] 10 6 1 8 12 11 17 14 29 35 25 alku[H2] 18 3 2 20 29 41 30 19X.2 Binomikeon operaatiot Limityksen tulos: alku[H] 10 18 1 2 12 20 3 29 41 6 8 30 11 17 14 35 25 Tulokseksi saatu H ei selvästikään ole binomikeko, sillä binomipuita B0 ja B1 esiintyy limityksen lopputuloksessa kahdesti. Limitystä tarvitaan kuitenkin välivaiheena avuksi kahden binomikeon unionin muodostamisessa. Kahden binomikeon unionin muodostaminen: • Algoritmi BINOMIKEKOJEN_UNIONI yhdistää binomikeot H1 ja H2 yhdeksi binomikeoksi. 29 19X.2 Binomikeon operaatiot Unionin muodostamisen vaiheet: 1. Limitetään binomikekojen H1 ja H2 juurilistat kasvavaan järjestykseen käyttämällä algoritmia BINOMIKEKOJEN_LIMITYS(H1, H2). Limityksen tuloksena saadussa listassa on nyt samanasteisia binomipuita korkeintaan kaksi peräkkäin (kts. edellinen esimerkki). Limitysvaiheen kompleksisuus on Ο(m), missä m tarkoittaa binomikekojen H1 ja H2 juurien yhteenlaskettua lukumäärää. 2. Käydään limityksen tuloksen juurilista läpi aloittamalla pieniasteisimmasta puusta ja mikäli löydetään kaksi perättäistä samanasteista Bk – 1-puuta, ne linkitetään yhdeksi Bk-puuksi. Juurilistaa käsittelevässä algoritmissa osoittaa muuttuja x tarkasteltavaan juurisolmuun edell-x juureen, joka edeltää x:ää juurilistassa. Siten sisarus[edell-x] = x. seur-x juureen, joka seuraa x:ää juurilistassa. Siten sisarus[x] = seur-x. • Kannattaa huomioida, että jos juurilistassa on kaksi samanasteista solmua, ne sijaitsevat välttämättä peräkkäin kohdassa 1 tehdyn limityksen ansiosta. • Juurilistan käsittelyyn liittyy neljä erillistä tapausta, jotka käydään läpi seuraavassa. 19X.2 Binomikeon operaatiot Tapaus 1: aste[x] ≠ aste[seur-x] Tällöin x on Bk-puun juuri jollekin k < log2n. Vastaavasti seur-x osoittaa jonkin Bl-puun juureen siten, että l > k. Toiminta: ei ole tarpeen linkittää puita toisiinsa, joten siirretään kaikkia kolmea osoitinmuuttujaa edell-x, x ja seur-x yhdellä eteenpäin. Seuraavassa hahmotelma tilanteesta: … edell-x x … seur-x Bl Bk edell-x x Bk seur-x Bl … 19X.2 Binomikeon operaatiot Tapaus 2: aste[x] = aste[seur-x] = aste[sisarus[seur-x]] Tällöin puussa on kolme samanasteista binomipuuta peräkkäin (miten tämä voi olla mahdollista?). Näistä puista x osoittaa ensimmäiseen. Toiminta: ei tehdä mitään linkityksiä, vaan kaikkia kolmea osoitinmuuttujaa edell-x, x ja seur-x yhdellä eteenpäin. Seuraavassa hahmotelma tilanteesta: … edell-x x … seur-x Bk edell-x Bk x Bk Bk … seur-x Bk Bk 19X.2 Binomikeon operaatiot Tapaukset 3 ja 4: aste[x] = aste[seur-x] ≠ aste[sisarus[seur-x]] Puussa on nyt tarkalleen kaksi samanasteista binomipuuta peräkkäin. Näistä puista x osoittaa ensimmäiseen. Toiminta: samanasteiset binomipuut yhdistetään yhdeksi puuksi Tapaus 3: avain[x] ≤ avain[seur-x] Tapaus 4: avain[x] > avain[seur-x] Seuraavassa vielä hahmotelma tilanteesta: … edell-x x … seur-x Bk Bk edell-x x Bl (l > k) seur-x Bk Bk Bl (l > k) … 19X.2 Binomikeon operaatiot • Seuraavaksi esitetään algoritmi, joka muodostaa kahden binomikeon unionin: BINOMIKEKOJEN_UNIONI(H1, H2) 1 H := PERUSTA_BINOMIKEKO() 2 alku[H] := BINOMIKEKOJEN_LIMITYS(H1, H2) 3 vapauta keko-objekteille H1 ja H2 dynaamisesti varattu muistitila 4 IF alku[H] = NIL 5 THEN RETURN H 6 edell-x := NIL 7 x := alku[x] 8 seur-x := sisarus[x] 9 WHILE seur-x ≠ NIL DO 10 IF (aste[x] ≠ aste[seur-x]) OR ((sisarus[seur-x] ≠ NIL) AND (aste[sisarus[seur-x]] = aste[x])) 11 THEN edell-x := x 12 x := seur-x 13 ELSE IF avain[x] ≤ avain[seur[x]] 14 THEN sisarus[x] := sisarus[seur-x] 15 LINKITÄ_BINOMIPUUT(seur-x, x) 16 ELSE IF edell-x = NIL 17 THEN alku[H] := seur-x 18 ELSE sisarus[edell-x] := seur-x 19 LINKITÄ_BINOMIPUUT(x, seur-x) 20 x := seur-x 21 seur-x := sisarus[x] 22 RETURN H 19X.2 Binomikeon operaatiot Algoritmin analyysi: • Merkitään n1:llä ja n2:lla kekojen H1 ja H2 alkioiden määrää. • n = n1 + n2 on alkioiden yhteismäärä. • Keossa H1 on juuria enintään log2n1 + 1. • Keossa H2 on juuria enintään log2n2 + 1. • Täten keko H sisältää proseduurin BINOMIKEKOJEN_LIMITYS suorituksen jälkeen juurisolmuja enintään log2n1 + log2n2 + 2 ≤ 2 log2n + 2 ≤ 3 log2n (viimeinen epäyhtälö tosi, kun n ≥ 4) = Ο(log2n). • Jokainen algoritmin BINOMIKEKOJEN_UNIONI while-silmukan suoritus (rivit 9 – 21) vie ainoastaan vakioajanΟ(1), ja rivit 1 sekä 3 – 8 ovat samoin vakioajassa suoritettavia. Rivin 2 suoritusaika riippuu limityksen tuottaman juurilistan alkioiden lukumäärästä, ja se on edellisen tarkastelun perusteella logaritminen. Siten algoritmin kokonaissuoritusaika on Ο(log2n). • Seuraavaksi esitetään vielä esimerkki, miten kahden binomikeon unionin muodostaminen jatkuu eteenpäin limitysvaiheesta, joka esiteltiin kalvoissa 123 – 124. 19X.2 Binomikeon operaatiot Tilanne limitysvaiheen päätyttyä: alku[H] 10 18 1 2 12 20 3 29 6 8 30 17 11 41 25 1) Ensimmäinen korjausvaihe: Kaksi B0-puuta yhdistetään yhdeksi B1:ksi. juurilistaan tulee nyt kolme perättäistä B1-puuta! 10 1 2 18 12 20 3 29 41 6 8 30 11 25 17 14 35 29 14 35 29 19X.2 Binomikeon operaatiot 2) Toinen korjausvaihe: Toinen ja kolmas B1-puu yhdistetään yhdeksi B2:ksi (ensimmäinen puu jää paikoilleen lopulliseen kekoon). koska 1 < 2, linkitetään viimeinen B1-puu keskimmäisen B1-puun lapseksi. juurilistaan tulee nyt kaksi B2-puuta! 18 3 1 10 2 12 20 29 6 8 30 11 41 17 14 29 35 25 3) Kolmas korjausvaihe: B2-puut yhdistetään yhdeksi B3:ksi. koska 1 < 3, linkitetään jälkimmäinen B2-puu ensimmäisen B1-puun lapseksi. juurilistaan tulee nyt kaksi B3-puuta! 1 10 18 29 20 3 2 12 20 6 8 12 11 25 17 14 35 29 19X.2 Binomikeon operaatiot 4) Neljäs korjausvaihe: B3-puut yhdistetään yhdeksi B4:ksi. koska 1 < 6, linkitetään viimeinen B3-puu ensimmäisen B3-puun lapseksi. muodostuneen B4-puun sisaruksesta tulee NIL binomikekojen unioni on valmis. 10 1 18 6 11 8 14 17 35 29 29 3 2 12 20 12 20 25 Binäärilukujen yhteenlasku: • Edellisessä esimerkissä yhdistettiin kaksi binomikekoa, joiden suuruudet ovat 11 ja 7 alkiota. • Luvun 1110 binääriesitys on 1011 • Luvun 710 binääriesitys on puolestaan 0111 19X.2 Binomikeon operaatiot Esimerkki: Lasketaan yhteen lukujen 11 ja 7 4-bittiset binääriesitykset. 1 _+ 0 =1 0 B4 B3 • • • 0 1 1 1 1 1 0 1 0 B2 B1 B0 (1110) (710) (1810) Muodostettaessa 11- ja 7-kokoisten binomikekojen unioni saatiin tulokseksi 18-solmuinen binomikeko. Binääriluku 10010 vastaa lukua 1810, joka oli unionioperaatiossa syntyneen binomikeon koko. Kyseinen binääriluku vastaa binomipuita B1 ja B4. Solmun lisääminen binomikekoon: • Seuraavaksi esiteltävä algoritmi lisää solmualkion x binomikekoon H. LISÄÄ_BINOMIKEKOON(H, x) 1 H’ := PERUSTA_BINOMIKEKO() 2 vanhempi[x] := NIL 3 lapsi[x] := NIL 4 sisarus[x] := NIL 5 aste[x] := 0 6 alku[H’] = x 7 H := BINOMIKEKOJEN_UNIONI(H, H’) • • • Algoritmin suorituksen aikana perustetaan uusi binomikeko, jossa on vain solmu x. Tämän jälkeen yhdistetään binomikeot H ja H’. Algoritmin kompleksisuus on Ο(1) + Ο(log2n) = Ο(log2n). 19X.2 Binomikeon operaatiot Minimiavaimen poistaminen: • Seuraava algoritmi poistaa minimiavaimen binomikeosta H. POISTA_BINOMIKEON_MINIMI(H) 1 Etsi sellainen keon H juurisolmu x, jolla on pienin avainarvo ja poista x keon H juurilistasta. 2 H’ := PERUSTA_BINOMIKEKO() 3 Käännä x:n lapsisolmujen järjestys sisaruslistassa, aseta lasten isäksi NIL sekä aseta alku[H’] osoittamaan tämän listan alkuun. 4 H := BINOMIKEKOJEN_UNIONI(H, H’) 5 RETURN x • Algoritmin kompleksisuus: Ο(log2n) • Seuraavassa esitetään vielä numeerinen esimerkki minimiavaimen poistamisesta. Oletetaan lähtötilanteeksi seuraavanlainen binomikeko H: alku[H] 10 3 6 12 29 30 41 11 25 1 8 17 14 35 29 19X.2 Binomikeon operaatiot Minimiavain löytyy puun B3 juuresta. Poistetaan kyseinen puu keosta H ja merkitään sen juurta tunnuksella x. Saadaan: alku[H] 10 x 3 6 12 29 11 41 Irrotetaan solmu x keosta H, käännetään sen lapsilista ja viedään lapsisolmut kekoon H’: alku[H] 8 30 14 1 29 35 17 25 alku[H’] 10 3 6 12 29 41 30 29 8 14 35 11 25 17 19X.2 Binomikeon operaatiot Suoritetaan minimikekojen H ja H’ välinen limitys … : alku[H] 10 29 6 14 12 35 3 8 30 29 11 41 25 … ja lopuksi muodostetaan näiden unioni: alku[H] 10 29 3 6 14 35 12 11 25 8 29 17 41 30 17 19X.2 Binomikeon operaatiot Avainarvon pienentäminen: • • • • • Oletetaan, että solmu x kuuluu binomikekoon H. Solmun x avainarvo halutaan pienentää arvoon k. Syntyy virhetilanne, jos k > avain[x]. Lisäksi oletetaan, että on käytettävissä suora osoitin binomikeon solmualkioon x. Toimintaperiaate on samanlainen kuin tavallisessa minimikeossa. PIENENNÄ_BINOMIKEON_AVAIMEN_ARVOA(H, x, k) 1 IF k > avain[x] 2 THEN virheilmoitus ”uusi avain on nykyistä suurempi: ei kelpaa” 3 avain[x] := k 4 y := x 5 z := vanhempi[y] 6 WHILE z ≠ NIL AND avain[y] < avain[z] DO 7 vaihda avain[y] avain[z] 8 vaihda myös mahdollinen lisädata solmujen y ja z välillä päittäin 9 y := z 10 z := vanhempi[y] • Avainarvoa pienentävän algoritmin kompleksisuus: Ο(log2n), sillä minkä tahansa binomipuun maksimikorkeus keossa on log2n. 19X.2 Binomikeon operaatiot Alkion poistaminen: • Oletetaan jälleen, että on käytettävissä suora osoitin binomikeon solmualkioon x. POISTA_BINOMIKEOSTA(H, x) 1 PIENENNÄ_BINOMIKEON_AVAIMEN_ARVOA(H, x, -∞) 2 POISTA_BINOMIKEON_MINIMI(H) • Poiston kompleksisuus: Ο(log2n), sillä kummankin rivin (1 ja 2) suorituskustannus on Ο(log2n). 16. Tasoitettu kustannus • • • Edellä suoritetut algoritmien analyysit ovat perustuneet määräämällä kokonaiskustannus yksittäisten operaatioiden kustannusten summana. Tasoitetun kustannuksen analyysissä tarkastellaan yksittäisten operaatioiden sijasta operaatiojonojen suorituskustannuksia. Suoritetaan jono perättäisiä operaatioita. Ajatus: Vaikkakin algoritmin jotkin yksittäiset operaatiot voivat sinällään olla työläitä, niin tästä huolimatta keskimääräinen työmäärä yhtä operaatiota kohti saattaa jäädä vähäiseksi. On nimittäin mahdollista, ettei kalliiksi käyvää operaatiota ei ole teknisesti mahdollista suorittaa kuin vain harvoin! 16. Tasoitettu kustannus • Tarkastellaan seuraavassa lyhyesti kolmea eri lähestymistapaa tasoitetun kustannuksen määräämiseksi: 1) Kokonaistyömäärän laskeminen (artikkelissa Aho, Hopcroft, Ullman) 2) Talletusmenetelmä (artikkelissa M. R. Brown, R. E. Tarjan, S. Huddleston ja K. Mehlhorn) 3) Potentiaalimenetelmä (artikkelissa D. D. Sleator) • Käytetään asiaan tutustumiseksi kolmea esimerkkiongelmaa 1) Pino, jossa monen alkion poisto-operaatio 2) Lukujen binääriesitykseen perustuva laskuri 3) Alkioiden tallentamista dynaamiseen taulukkoon 16.1 Kokonaistyömäärän laskeminen 16.1.1 Pino, jossa monen alkion poisto-operaatio • Käytettävissä olevat operaatiot 1) LISÄÄ_PINOON(S, x): lisää alkion x pinon S päällimmäiseksi kompleksisuus Ο(1) ⇒ jos suoritetaan n kertaa peräkkäin, kustannus Ο(n) (vertaa silmukkarakenne, jossa vakioaikaista lausetta (lausejonoa) suoritetaan n kierrosta peräkkäin). 16.1 Kokonaistyömäärän laskeminen 16.1.1 Pino, jossa monen alkion poisto-operaatio 2) OTA_PINOSTA(S): poistaa pinon S päällimmäisen alkion kompleksisuus Ο(1) ⇒ jos suoritetaan n kertaa peräkkäin, kustannus Ο(n) 3) OTA_PINOSTA_USEITA(S, k): poistaa pinosta S k päällimmäisintä alkiota (tai kaikki saatavilla olevat alkiot, ellei pinossa ole tarpeeksi alkioita) 1 WHILE NOT tyhjä(S) AND k ≠ 0 DO 2 OTA_PINOSTA(S) 3 k := k – 1 Suoritusaika: – lineaarinen suhteessa poistettavien alkioiden lukumäärään – while-silmukkaa suoritetaan min(s, k) kertaa, missä s on pinon S alkioiden lukumäärä (merkitään |S|) Suoritetaan nyt operaatioita LISÄÄ_PINOON, OTA_PINOSTA ja OTA_PINOSTA_USEITA peräkkäin yhteensä n kappaletta jossain järjestyksessä. • Operaation OTA_PINOSTA_USEITA suoritusaika on pahimmillaan O(n). • Operaatioita suoritetaan yhteensä n kappaletta. • Siten operaatiojonon pahimman tapauksen kustannus vaikuttaisi yksinkertaisen analyysin perusteella olevan n ⋅ (Ο(n)) = Ο(n2) – oletetaan siis tehtävän pelkkiä usean alkion poistooperaatioita yhteensä n kertaa peräkkäin. Ajatuksena on siis varautua aina pahimpaan … . • … MUTTA: ihan näin huonosti ei kuitenkaan voi käydä! Analysoidaan asiaa syvällisemmin. 16.1 Kokonaistyömäärän laskeminen 16.1.1 Pino, jossa monen alkion poisto-operaatio • Jokainen pinon S alkioista tuodaan ensinnä jonoon ja poistetaan myöhemmin. olematonta alkiota ei pystytä poistamaan • Toisin sanoen, jotta pinosta voitaisiin ottaa pois yhteensä n alkiota (joko yksi kerrallaan tai useampia samanaikaisesti), ne on selvästikin täytynyt viedä pinoon sitä ennen. n alkion poistoa on täytynyt edeltää n kappaletta lisäysoperaatioita. • Kokonaistyömäärä on siis todellisuudessa Ο(n) pahimmassa tapauksessa (toisin kuin arvioitiin yksinkertaisessa analyysissä). • Keskimääräinen työmäärä yhtä operaatiota kohti on siis Ο(1). 16.1.2 Binäärilaskuri • Binäärilaskuri on luvun binääriesitykseen perustuva laskuri, joka laskee ykkösen välein nollasta eteenpäin. • • • • ! " ⋅2" Laskurin arvo on tietyllä hetkellä ∑ Edellä k tarkoittaa luvun bittiesityksen A pituutta. Laskuri alustetaan nollalla, joten aluksi A = <000…00>. Esitetään seuraavaksi binäärilaskurin toimintaa kuvaavan algoritmin pseudokoodi. Algoritmi kasvattaa laskurin arvoa ykkösellä nykyisestä arvostaan. 16.1 Kokonaistyömäärän laskeminen 16.1.2 Binäärilaskuri KASVATA_BINÄÄRILASKURIA(A, k) 1 i := 0 2 WHILE i < k AND A[i] = 1 DO 3 A[i] := 0 4 i := i + 1 5 IF i < k 6 THEN A[i] := 1 Esimerkki: Oletetaan, että luvun bittiesityksen pituus k = 3 Laskurin arvo Bittiesitys Kokonaiskustannus 0 000 0 1 001 1 2 010 3 3 011 4 4 100 7 5 101 8 6 110 10 7 111 11 16.1 Kokonaistyömäärän laskeminen 16.1.2 Binäärilaskuri • • • • Binäärilaskurin kasvatusoperaation kustannus on pahimmassa tapauksessa Θ(k) bittien vaihtojen lukumäärissä mitattuna. Mikäli laskurin arvon annetaan kasvaa n kertaa peräkkäin, olisi yksinkertaisen analyysin mukaan kyseisen operaatiojonon kustannus pahimmassa tapauksessa Θ(nk). taustaoletus: joka kerta vaihdetaan kaikki k bittiä päinvastaisiksi. Tarkemmin ajatellen on kuitenkin ilmeistä, että ainoastaan 1. bitti joudutaan vaihtamaan jokaisella operaatiolla 2. bitti vaihdetaan vain joka toinen kerta 3. bitti vaihdetaan joka neljäs kerta jos bittejä olisi neljä, niistä ylin vaihtuisi joka kahdeksas kerta, ja … … yleisesti: i. bitti joudutaan vaihtamaan joka 2i – 1. kerta. Siten n perättäisen binäärilaskurin kasvatusoperaation kokonaiskustannus on: T(n) = n + n/2 + n/22 + n/23 + … + n/2k – 1 ≤ n(1/20 + 1/21 + …. + 1/2k – 1) ( ) = n ⋅ ∑ =n • % % ( ) ≤ 2n(1 – (1/2)k) ≤ 2n Siten T(n) = Ο(n), joten yhtä operaatiota kohti laskettu kustannus (s. o. tasoitettu kustannus) on suuruusluokkaa Ο(1). 16.2 Talletusmenetelmä • Perustavana ajatuksena talletusmenetelmässä on, että operaatioista maksetaan eri suuruisia maksuja. Joistakin operaatioista maksetaan todellista kustannusta enemmän. Toista operaatioista taas maksetaan vähemmän kuin sen todellinen kustannus. Kuoletetulla kustannuksella tarkoitetaan maksettua yhteissummaa. Käytettäessä talletusmenetelmää on luonnollisempaa puhua kuoletetusta (eli siis jo maksetusta) kustannuksesta kuin tasoitetusta. • Mikäli kuoletettu kustannus on isompi kuin todellinen kustannus, talletetaan näiden erotus joihinkin tiettyihin objekteihin talletukseksi. • Talletusta voidaan käyttää maksettaessa sellaisia operaatioita, joiden kuoletettu kustannus on pienempi kuin todellinen kustannus. • Kuoletetut kustannukset pitää määritellä huolellisesti: Talletus ei saa koskaan olla miinuksella (eli ei saada jäädä ”velkaa” missään vaiheessa) Kuoletettu kustannus pitää saada todellisen kustannuksen ylärajaksi. • Merkitään, että ci on i. operaation todellinen kustannus ĉi on i. operaation kuoletettu kustannus • Vaatimus: ∑ ĉi ≥ ∑ ci (tehdyistä operaatioista pitää maksaa vähintään käypä hinta) • Talletus: ∑ ĉi – ∑ ci ≥ 0 (talletuksella tarkoitetaan käytettävissä olevaa ”tilin saldoa”) 16.2 Talletusmenetelmä 16.2.1 Pino, jossa monen alkion poisto-operaatio • Todellinen kustannus: Lisäys: 1 Poisto: 1 Usean alkion poisto: min(s, k), missä s on pinon koko tarkasteluhetkellä (eli |S|) • Näiden todellisten kustannusten valossa voidaan sopia seuraavista tasoitetuista kustannuksista: Lisäys: 2 Poisto: 0 Usean alkion poisto: 0 • Ajatuksena siis on, että jokaisesta poisto-operaatiosta maksetaan ennakkomaksu. Täten jokaisen lisäysoperaation kustannukseksi kirjataan sen todellinen kustannus tuplana. • Tämän jälkeen poistosta tulee kirjanpidollisesti ”ilmaista lystiä” – riippumatta siitä, otetaanko jonosta kerrallaan yksi alkio vai useita maksu poisto-operaatioista on jo maksettu etukäteen. 16.2 Talletusmenetelmä 16.2.1 Pino, jossa monen alkion poisto-operaatio • Vertauskuvallinen esimerkki ”reaalimaailmasta”: lautaspinon käsittely: Kun uusi lautanen asetetaan pinoon, maksetaan yhden euron suuruinen maksu pinoon asettamisesta ja lisäksi toinen euro myöhemmin tehtävää poistoa varten (ensimmäinen euro rahastetaan heti, mutta toinen jää lautaselle ”lepäämään”). Kun lautanen aikanaan poistetaan pinosta, maksetaan poisto lautaselle varastoon jääneellä euron kolikolla. Selvästikin talletus on aina ei-negatiivinen: jokaisella pinossa olevalla lautasella on oltava euron kolikko, ja talletus on nolla tarkalleen silloin, kun lautaspino on tyhjä. Kuoletettu kustannus on todellisen kustannuksen yläraja. Kuoletettu kustannus on lautasesimerkissä Ο(n). Jos n operaation aikana lautaspino ei tyhjene, on talletus positiivinen (ollaan maksettu enemmän kuin todellisten kustannusten verran). 16.2.2 Binäärilaskuri • Sovitaan seuraavat tasoitetut kustannukset: Bitin vaihto 0 1 maksaa 2 euroa. Tästä 1 euro on vaihdon todellinen kustannus. Toinen euro maksetaan etukäteen siitä, että bitti joskus myöhemmin tullaan nollaamaan. Talletusten määrää osoittaa nyt laskurin ykkösten lukumäärä, joka on aina ei-negatiivinen. 16.2 Talletusmenetelmä 16.2.2 Binäärilaskuri • Laskurin kasvatusoperaation kuoletettu kustannus määräytyy seuraavasti: While-silmukan osuus on maksettu etukäteen Tämän jälkeen enintään yksi bitti vaihdetaan ykköseksi. Kuoletettu kustannus on enintään 2 euroa. Kuoletettu kustannus n operaation jonolle on Ο(n). 16.3 Potentiaalimenetelmä Potentiaalimenetelmä muistuttaa talletusmenetelmää, mutta nyt talletus tulkitaan tietorakenteeseen varastoiduksi potentiaaliksi. • • • • Tallennusmenetelmässä talletus kohdistuu aina johonkin objektiin (lautanen, bitti jne.). Potentiaalimenetelmässä itse tietorakenne varastoi itseensä potentiaalia. Potentiaalia voidaan käyttää tuleviin operaatioihin. Se on operaatiojonojen kustannuksen arvioimismenetelmistä joustavin. 16.3 Potentiaalimenetelmä Merkitään: • Di = tietorakenteen tila i. operaation jälkeen • D0 = tietorakenne lähtötilanteessa • ci = i. operaation todellinen kustannus • ĉi = i. operaation tasoitettu kustannus Potentiaalifunktio: Φ: Di R • Φ(Di) = tietorakenteen Di potentiaali • ĉi = ci + Φ(Di) – Φ(Di – 1) = ci + ∆Φ(Di) Tasoitetun kustannuksen kokonaismäärä ∑ ĉi = ∑(ci + Φ(Di) – Φ(Di – 1)) = ∑ ci + Φ(Dn) – Φ(D0)) • Jos vaaditaan, että Φ(Dn) – Φ(D0) ≥ 0, niin voidaan olla varmoja, että tasoitetun kustannuksen kokonaismäärä on todellisen kustannuksen yläraja. • Yleensä on järkevintä määritellä Φ(D0) = 0 ja vaatia, että Φ(Di) ≥ 0 kaikilla i ≥ 1, niin liikutaan ”turvallisilla vesillä”. • ∆Φ(Di) kuvaa potentiaalin muutosta tietorakenteessa i operaation kohdalla. 16.3 Potentiaalimenetelmä 16.3.1 Pino, jossa monen alkion poisto-operaatio • Tyhjä pino D0: Φ(D0) = 0. • Potentiaalifunktio Φ: edustaa pinon alkioiden lukumäärää • Siten selvästikin Φ(Di) ≥ Φ(D0) kaikilla i ≥ 0. Lisäysoperaatio: • Φ(Di) – Φ(Di – 1) = 1 • ĉi = ci + ∆Φ(Di) = 1 + 1 = 2 Usean alkion poisto-operaatio: määritellään x = min(|S|, k) • Φ(Di) – Φ(Di – 1) = -x • ĉi = ci + ∆Φ(Di) = x – x = 0 Samoin yhden alkion poisto-operaatiolle ĉi = 0, sillä se voidaan mieltää edellisen erikoistapaukseksi, kun parametrin k arvo on 1. • Äskeisen perusteella n operaation tasoitettu kustannus on Ο(n). • Koska Φ(Di) ≥ Φ(D0) = 0 kaikilla i ≥ 0, niin tasoitettu kustannus on yläraja todellisen kustannuksen yläraja. 16.3 Potentiaalimenetelmä 16.3.2 Binäärilaskuri • • • • • • • Φ = bi, joka tarkoittaa ykkösbittien lukumäärää laskurin i. kasvatusoperaation jälkeen. Oletetaan, että i. operaatiossa nollataan xi bittiä. Todellinen kustannus ci ≤ xi + 1. Jos bi = 0, niin i. operaatio nollasi kaikki k bittiä. Tällöin välttämättä bi – 1 = xi = k. Jos bi > 0, niin i. operaatio nollaa xi bittiä. Täten bi = bi – 1 – xi + 1. Kumpi tapaus näistä sitten toteutuukin, on voimassa bi ≤ bi – 1 – xi + 1. Nyt ∆Φ(Di) = bi – bi – 1 ≤ (bi – 1 – xi + 1) – bi – 1 = 1 – xi. • Tasoitettu kustannus ĉi = ci + ∆Φ(Di) ≤ (xi + 1) + (1 – xi) = 2. • Koska Φ(D0) = 0, niin tasoitetun kustannuksen kokonaismäärä on yläraja todelliselle kustannukselle. • Siten n operaation tasoitettu kustannus on Ο(n). 16.4 Dynaamiset taulukot • Taulukko on olio, jolla on ominaisuutena rajattu koko. Kun taulukko on kerran perustettu, sen kokoa ei enää pysty muuttamaan, sillä se määritellään konstruktorissa. • Usein voi taulukon käytön ongelmaksi koitua se, ettei ennalta pystytä aavistamaan, paljonko taulukkoon tullaan alkioita sijoittamaan, eli ei ole tietoa siitä, minkä kokoinen taulukko olisi järkevää valita. • Sekä Javassa että C++:ssa dynaaminen taulukko määritellään Vector-luokassa. Ohjelmoijan ei tarvitse itse huolehtia taulukon kasvattamisesta. • Esimerkiksi Javan Vector-luokka varaa oletusarvoisesti tilaa vain 10 alkiolle. Kun tietorakenteeseen lisätään alkioita, luokka kasvattaa taulukon kapasiteettia automaattisesti kaksinkertaiseksi sopivaksi katsomallaan hetkellä ja kopioi alkiot alkuperäisestä taulukosta uuteen taulukkoon. • Tarkasteltavassa toteutuksessa toimitaan siten, että kun varattu taulukko täyttyy, varataan uusi – kaksi kertaa nykyisen kokoinen – taulukko, kopioidaan alkiot nykyisestä, pienemmästä taulukosta sinne ja jatketaan normaalisti. • Tarkastellaan seuraavaksi algoritmia, joka lisää solmualkion x dynaamiseen taulukkoon T. 16.4 Dynaamiset taulukot LISÄÄ_TAULUKKOON(T, x) 1 IF koko[T] = 0 2 THEN luo uusi 1-alkioinen taulukko T 3 koko[T] := 1 4 IF lkm[T] = koko[T] /* Onko taulukko T täynnä? */ 5 THEN Luo taulukko nimeltä uusi, joka on kooltaan 2 ⋅ koko[T] 6 kopio taulukon T alkiot taulukkoon uusi 7 vapauta taulukon T käytössä oleva muistialue 8 T := uusi 9 koko[T] := 2 ⋅ koko[T] 10 Lisää x taulukkoon T 11 lkm[T] := lkm[T] + 1 6.4.1 Kokonaistyömäärän laskeminen • ci = 1, jos taulukko ei ole täynnä • ci = (i – 1) + 1 = i, jos taulukko on täynnä Huom! Taulukko on täynnä, jos ja vain jos i = 2k jollakin kokonaisluvulla k. 16.4 Dynaamiset taulukot 6.4.1 Kokonaistyömäärän laskeminen Kokonaistyömääräksi saadaan: ∑ &" ≤ n + ∑ 2 = n + 2log2n + 1 – 1 ≤ n + 2 ⋅ 2log2n = 3n • Tasoitettu kustannus yhtä operaatiota kohti on siten 3. 6.4.2 Talletusmenetelmä Jokainen algoritmin LISÄÄ_TAULUKKOON kutsumiskerta maksaa 3 euroa: • 1 euro tarvitaan uuden alkion tallettamiseen • Toinen euro maksetaan etumaksua mahdollisesti myöhemmin tehtävästä alkion siirrosta • Lisäksi kolmas euro maksetaan jonkin toisen – taulukkoon aikaisemmin tuodun – alkion siirrosta Toimintaperiaate • Oletetaan, että taulukon koko on juuri äsken kaksinkertaistettu. Taulukon koko on tällöin m + m. • Oletetaan lisäksi, ettei meillä ole yhtään säästöjä käytettävissä. • Seuraavan kerran taulukon koko joudutaan tuplaamaan, kun siinä on m + m = 2m alkiota. • Näiden alkioiden tallettamisen kuoletettu kustannus on 3m euroa. 16.4 Dynaamiset taulukot • Alkioiden tallettamisen todellinen kustannus on m euroa. • Alkioiden uuteen taulukkoon siirtämisen todellinen kustannus on 2m euroa. • Uusia alkioita taulukkoon lisättäessä kertyneet säästöt menetetään, mutta tehtävä onnistui! 6.4.3 Potentiaalimenetelmä Potentiaalifunktio Φ(T) = 2 ⋅ lkm[T] – koko[T] • Aluksi lkm[T] = koko[T] = 0, joten Φ(T) = 0. • Heti taulukon laajennuksen tapahduttua koko[T] = 2 ⋅ lkm[T], joten Φ(T) = 0. • Juuri ennen laajennusta koko[T] = lkm[T], joten Φ(T) = lkm[T]. Koska lkm[T] ≥ ½ ⋅ koko[T], niin Φ(T) ≥ 0. Määritellään: • lkmi = alkioiden määrä i. operaation jälkeen • kokoi = taulukon koko i. operaation jälkeen • Φi = taulukon potentiaali i. operaation jälkeen 16.4 Dynaamiset taulukot Jos taulukkoa ei laajenneta, niin: • lkmi = lkmi – 1 + 1 • kokoi = kokoi – 1 • ci = 1 Tästä seuraa, että ĉi = ci + Φi – Φi – 1 = 1 + (2 ⋅ lkmi – kokoi) – (2 ⋅ lkmi – 1 – kokoi – 1) = 1 + (2 ⋅ lkmi – kokoi) – (2 ⋅ (lkmi – 1) – kokoi) =1+2=3 Jos taulukkoa laajennetaan, niin • kokoi = 2 ⋅ kokoi – 1 • kokoi – 1 = lkmi – 1 = lkmi – 1 • ci = lkmi – 1 + 1 = lkmi Tästä seuraa, että ĉi = ci + Φi – Φi – 1 = lkmi + (2 ⋅ lkmi – kokoi) – (2 ⋅ lkmi – 1 – kokoi – 1) = lkmi + (2 ⋅ lkmi – 2(lkmi – 1)) – (2 ⋅ (lkmi – 1 – 1) – (lkmi – 1) ) = lkmi + 2 – lkmi + 1 = 3 19 Fibonacci-keot Fibonacci-keot on esitelty ensi kertaa artikkelissa: Michael L. Fredman & Robert E. Tarjan: Fibonacci heaps and their uses in improved network optimization algorithms, Journal of the ACM 34,(1987), 596 – 615 Operaatio Keko Binomikeko Fibonacci-keko Θ(1) Θ(1) Θ(1) Θ(log2n) Ο(log2n) Θ(1) Θ(1) Ο(log2n) Θ(1) Θ(log2n) Θ(log2n) Ο(log2n)* Θ(n) Ο(log2n) Θ(1) PIENENNÄ_AVAINARVOA** Θ(log2n) Θ(log2n) Θ(1)* POISTA_SOLMU** Θ(log2n) Θ(log2n) Ο(log2n)* PERUSTA_KEKO LISÄÄ MINIMI POISTA_MINIMI UNIONI *) = tasoitettu kustannus **) = oltava käytettävissä osoitin tarkasteltavaan alkioon (alkiota ei jouduta erikseen etsimään) 19 Fibonacci-keot • Fibonacci-keko on kekorakenne, joka on tasoitetun kustannuksen suhteen tehokkaampi kuin tavallinen keko ja binomikeko. • Fibonacci-kekoa käyttämällä voidaan toteuttaa prioriteettijono, jonka vahvuuksina ovat vakioaikainen (Θ(1)) lisäys, pienimmän solmun löytäminen, avainarvon (prioriteetin) pienentäminen ja unioni, • Solmun ja pienimmän avaimen poistaminen voidaan suorittaa ajassa Ο(log2n). • Fibonacci-keon operaatioiden toimintafilosofia: pyritään olemaan laiskoja. Ei tehdä mitään, ennen kuin on pakko kun töitä lopulta joudutaan tekemään, tehdään kerralla paljon! 19.1 Fibonacci-keon rakenne • Jokaisessa Fibonacci-keon solmussa on neljä viittausta muihin solmuihin: vanhempaan, edeltävään sisarukseen, seuraavaan sisarukseen ja johonkin lapseen. • Sisaruslista on toteutettu kahteen suuntaan linkitettynä rengaslistana. • Fibonacci-keon jokainen puu on minimikekojärjestyksessä. • Tietoa keon pienimmästä alkiosta säilytetään erikseen attribuutissa min[H]. • On mahdollista, että samanasteisia puita on keossa useampia kuin yksi (toisin kuin binomikeoilla). • Juurilistan ei tarvitse olla asteluvun mukaan ei-vähenevässä järjestyksessä. 19.1 Fibonacci-keon rakenne • Seuraavassa yksi hahmotelma Fibonacci-keon rakenteesta: min[H] 19.1 Fibonacci-keon rakenne Esitysmuodoista Edellä kuvattuun esitystapaan perustuen seuraavat perusoperaatiot ovat vakioaikaisia: • Juurilistaan lisääminen ja poisto (lisätään esimerkiksi minimiarvon sisältävän solmun viereen) • Kahden juurilistan limittäminen • Kahden binomipuun linkittäminen toisiinsa • Jonkun solmun lapsilistan yhdistäminen juurilistaan (lapsien isäosoittimia NIL-arvoiksi päivittämättä) Tämä selittyy sillä, että mainitut operaatiot päivittävät vain vakiomäärän sisaruslistan viittauksia, sekä linkitys päivittää näiden lisäksi vielä yhden vanhempi-osoittimen. Minimin haku, lisäys ja unioni • • • Minimin hakeminen on helppoa, sillä meillä on käytettävissä suora min[H]-osoitin pienimmän avaimen sisältävään solmuun. Myös uuden alkion lisääminen on helppoa, sillä uusi solmualkio x vain lisätään juurilistaan, ja tarvittaessa päivitetään min[H]-viittaus ajan tasalle. Jos puolestaan yhdistetään kaksi Fibonacci-kekoa toisiinsa unionioperaatiolla, niin yksinkertaisesti vain limitetään niiden juurilistat yhteen ja päivitetään min[H]-viittaus kuntoon. Edellä esitetyt operaatiot ovat vakioaikaisia (Θ(1)). 19.2 Minimialkion poisto Minimiavaimen poisto algoritmilla POISTA_MINIMI on jossain määrin monimutkaisempaa. 1. Aluksi minimialkio poistetaan juurilistasta, ja sen lapsilista yhdistetään juurilistaan. Ellei huomioida vielä tässä vaiheessa lapsien isälinkkien asettamista arvoon NIL, tämä saadaan tehtyä ajassa Θ(1). 2. Tämän jälkeen käytetään puhdistusalgoritmia, joka päivittää poistetun solmun lapsilistasta juureen nousseiden solmujen vanhempi-viittaukset sekä linkittää pareittain saman kokoiset binomipuut, kunnes on jäljellä yksistään eri asteisia binomipuita. Puhdistusalgoritmin pseudokoodi esitellään seuraavassa: PUHDISTA(H) 1 uusiminimi := jokin juurilistaan kuuluva solmu 2 FOR i := 0, 1, …, log2n DO 3 B[i] := NIL /* B on puhdistuksessa käytettävä aputaulukko. */ 4 FOR jokaiselle juurilistan solmulle v DO 5 vanhempi[v] := NIL (∗) 6 IF avain[uusiminimi] > avain[v] 7 THEN uusiminimi := v 8 YHDISTÄ_SAMANASTEISET(v) 19.2 Minimialkion poisto YHDISTÄ_SAMANASTEISET(v) 1 w := B[aste[v]] 2 WHILE w ≠ NIL DO 3 B[aste[v]] := NIL 4 IF avain[v] ≥ avain[w] 5 THEN vaihda v w 6 Poista w juurilistasta (∗∗) 7 LINKITÄ(w, v) 8 w := B[aste[v]] 9 B[aste[v]] := v Seuraavassa on nähtävissä hahmotelma Fibonacci-keon puhdistamisen etenemisestä: 0 1 2 3 B v 19.2 Minimialkion poisto 0 1 2 3 B v 0 B 1 2 3 v 19.2 Minimialkion poisto Huomioita edellisiin kahteen algoritmiin: • Kannattaa huomioida, että algoritmi YHDISTÄ_SAMANASTEISET linkittää alkiot siten, että minimikeko-ominaisuus säilyy (kahden samaa astetta olevan binomipuun avainarvoja verrataan keskenään, ja näistä pienempi jää juurilistaan). • Algoritmin PUHDISTA kompleksisuus on Ο(r+), missä r+ on Fibonacci-keon juurilistan pituus ennen kyseisen algoritmin suorittamista. • Tämä voidaan todeta tarkastelemalla sitä, kuinka monta kertaa tähdellä merkityt rivit suoritetaan: Rivi (∗) suoritetaan yhden kerran kullekin juurilistan v solmulle. Rivi (∗∗) suoritetaan enintään yhden kerran kutakin juurilistan solmua w kohti (osoitin w kulkee v:n jäljessä). • Koska algoritmi POISTA_MINIMI tekee vain vakiomäärän työtä ennen puhdistusalgoritmin kutsumista, niin sen kokonaissuoritusaika on Ο(r+) = Ο(r + aste[min[H]], • • • missä r on juurilistan pituus ennen POISTA_MINIMI-algoritmin suorituksen aloittamista. Vaikka aste[min[H]] on luokkaa Ο(log2n), on silti mahdollista, että r = Θ(n) – tarkastellaan vaikkapa tapausta, jossa yhtään alkiota ei ole vielä ehditty poistaa. Täten algoritmin POISTA_MINIMI suoritusaika on luokkaa O(n). Algoritmin POISTA_MINIMI suorittamisen jälkeen juurilistan pituus on Ο(log2n), sillä kaikilla puilla on tuolloin eri aste, ja korkein aste on enintään log2n. 19.2 Minimialkion poisto Tasoitettu kustannus • Jokainen alkion lisäys Fibonacci-kekoon kasvattaa lukua r, joka on juurilistan pituus ennen alkion poistoa. • Lisätään jokaiseen lisäykseen kiinteän suuruinen ”puhdistusmaksu” ja käytetään näin säästyneitä maksuja puhdistusalgoritmin kustannuksiin. • Tämän vuoksi POISTA_MINIMI-algoritmin osalta maksamatta on vain Ο(aste[min[H]]) = Ο(log2n). • Se, että Ο(aste[min[H]]) = Ο(log2n), todistetaan myöhemmin. Tarkastellaan asiaa seuraavassa formaalisemmin potentiaalin avulla. Algoritmin tasoitettu kustannus ĉ on todellisen kustannuksen c ja potentiaalin muutoksen ∆Φ summa. Määritellään Fibonacci-keon potentiaaliksi Φ juurisolmujen lukumäärä. Olkoon r juurisolmujen määrä ennen POISTA_MINIMI-algoritmin suoritusta ja r’ algoritmin suorituksen jälkeen. Tällöin • ∆Φ = r’ – r • c = r + aste[min[H]] • Tasoitettu kustannus on edellisten summa, eli ĉ = c + ∆Φ = r’ + aste[min[H]]. • Koska r’ = Ο(log2n) ja kunkin solmun aste on Ο(log2n), siten algoritmin POISTA_MINIMI tasoitettu kustannus on Ο(log2n). 19.3 Arvon pienentäminen Tässä tarkastellaan, miten solmualkion avainarvoa voidaan pienentää. Suoritusaika on pahimmassa tapauksessa Ο(log2n), mutta tasoitettu kustannus on ainoastaan Ο(1). Algoritmissa, joka pienentää solmun v avainarvoa, on vain kaksi yksinkertaista sääntöä: 1. Korota v juurilistaan (tässä tulee samalla koko v-juurinen alipuu mukana). 2. Kun jonkin solmun w kaksi lasta on korotettu juurilistaan, korota myös w välittömästi. Jotta jälkimmäistä sääntöä voitaisiin soveltaa tehokkaasti, jatkossa joitain Fibonacci-keon solmuja ns. merkitään. Solmu on merkitty, jos tarkalleen yksi sen lapsista on korotettu juurilistaan. Kun merkitty solmu korotetaan juurilistaan, sen ”merkki” poistetaan. PIENENNÄ_AVAINARVOA(v, k) 1 avain[v] := k 2 IF k < avain[min[H]] THEN min[H] := v 3 KOROTA(v) 19.3 Arvon pienentäminen Seuraavaksi esiteltävä algoritmi KOROTA on rekursiivinen. Jos korotetun solmun vanhempi on merkitty, sekin korotetaan. KOROTA(v) 1 kumoa solmun v merkintä 2 IF vanhempi[v] ≠ NIL 3 THEN Poista v isäsolmunsa lapsilistasta 4 Lisää v juurilistaan 5 IF vanhempi[v] on merkitty 6 THEN KOROTA(vanhempi[v]) 7 ELSE merkitse vanhempi[v] Katsotaan seuraavaksi esimerkkiä Fibonacci-keon alkion avainarvon pienentämisestä. Aluksi pienennetään solmun f avainarvoa. Yhtään solmua ei ole alun perin merkittynä. a b l p f g m n h i o c d j k e 19.3 Arvon pienentäminen f l a b m p g h i n c d j k e o Koska f:n avainarvoa pienennettiin, se nostettiin alipuineen juurilistaan. Samalla solmu b merkittiin, sillä sen lapsilistassa esiintynyt f korotettiin. Oletetaan, että seuraavaksi pienennetään solmun d avainarvoa. Solmu d ei esiinny samalla haku polulla kuin ainoa merkitty solmu b. f l p b m g n h c i a d e k j o Nyt myös d on nostettu juurilistaan. Samasta syystä kuin edellä merkittiin solmu b, nyt merkitään myös solmu a. Sovitaan, että nyt vuorostaan pienennetään solmun j avainarvoa. Solmulla j ei ole lapsia, joten se nousee juurilistaan yksinään. 19.3 Arvon pienentäminen a f l b m p g c h j e d k i n o Nyt myös j on nostettu juurilistaan. Tällä kertaa merkittiin puolestaan solmu c, joka on juureen nostetun j:n isäsolmu. Pienennetään lopuksi vielä solmun h avainarvoa, ja katsotaan mitä nyt tapahtuu. l p f b m g n h a c e j d k i o Koska juurilistaan viimeksi korotetun h:n isäsolmu b oli merkitty – eli b:n jokin lapsista (f) – on jo aikaisemmin nostettu juurilistaan, joudutaan sinne nostamaan myös b alipuineen. Samalla b:n merkintä kumotaan. Merkintä poistuu kuitenkin myös a:lta, sillä a on viimeksi juurilistaan korotetun b:n merkitty isäsolmu. Rekursio päättyy tähän, sillä a on jo ennestään juurilistassa (sillä ei enää ole isäsolmua). 19.3 Arvon pienentäminen Tasoitettu kustannus Solmun avainarvon pienentämisen suoritusaika on Ο(1 + peräkkäisten merkittyjen v:n esivanhempien määrä) Binomipuilla korkeus on luokkaa Ο(log2n), joten jos avainarvon pienentämisen eri vaiheissa käsiteltäisiin vain täydellisiä binomipuita, suoritusaika olisi selvästikin Ο(log2n). Valitettavasti korottaminen tuhoaa tämän ominaisuuden! Seuraavassa suoritetaan tasoitetun kustannuksen analyysi. Siinä sovitaan, että potentiaali on merkittyjen solmujen lukumäärä. Kun tehdään vähän työtä, merkittyjen solmujen määrä kasvaa korkeintaan yhdellä – kun tehdään paljon työtä, merkittyjen solmujenkin määrä vähenee paljon. Olkoot m ja m’ merkittyjen solmujen määrä ennen ja jälkeen avainarvon pienennysoperaatiota. Todellinen kustannus on c = 1 + perättäisten merkittyjen v:n esivanhempien määrä, ja potentiaalin muutos on m’ – m ≤ 1 – peräkkäisten merkittyjen v:n esivanhempien määrä Koska ĉ = c + ∆Φ ≤ 2, niin algoritmin PIENENNÄ_AVAINARVOA tasoitettu kustannus on Ο(1). 19.4 Asteen yläraja Merkitään koko[v]:llä v-juurisen alipuun solmujen määrää (v mukaan lukien). Lemma: Kaikille Fibonacci-keon solmuille v on voimassa koko[v] ≥ Faste[v] + 2. Todistus: Luetellaan solmun v lapset siinä järjestyksessä, jossa ne on linkitetty solmuun v. Tarkastellaan tilannetta juuri ennen kuin i:nneksi vanhin lapsi wi linkitettiin v:hen. Tällöin v:llä oli vähintään i – 1 lasta. Koska puhdistusalgoritmi linkittää ainoastaan keskenään samanasteisia solmuja, on linkityksen alkaessa aste[wi] = aste[v] ≥ i – 1 Tämän jälkeen ainoastaan yksi wi:n lapsi on voitu korottaa juurilistaan – muutoinhan wi olisi korotettu sinne itsekin. Täten • • • • aste[wi] ≥ i – 2 Olkoon sk pienin mahdollinen solmujen lukumäärä puussa, jonka juuren aste on k. Selvästikin s0 = 1, s1 = 2 ja s2 = 3. Edellä olevan perusteella i:nneksi vanhimman lapsen wi aste on vähintään i – 2, ja täten sen koko on vähintään si – 2 (sk:n määritelmän perusteella). Voidaan kirjoittaa nyt seuraava yhtälö (oletetaan, että s-1 = 1) sk ≥ ∑ '" − 2 19.4 Asteen yläraja Havaitaan seuraavanlainen yhteys Fibonaccin lukuihin: F0 = 0, F1 = 1, = s-1, F2 = 1 = s0, F3 = 2 = s1, F4 = 3 = s2 Lisäksi voitaisiin osoittaa induktiolla, että sk ≥ 1 + ∑ )" = Fk + 2 Induktiolla voitaisiin niin ikään osoittaa, että Fk + 2 ≥ φk, missä φ= ≈ 1.618 on kultaisen leikkauksen luku. Yhdistämällä tulokset saadaan koko[v] ≥ Faste[v] + 2 ≥ φaste[v], josta edelleen saadaan aste[v] ≤ logφkoko[v] = Ο(log2n). 19.5 Lopullinen analyysi Kannattaa huomioida, että edellä POISTA_MINIMI ja PIENENNÄ_AVAINARVOA käyttävät erilaisia potentiaalifunktioita. Mikäli emme pysty löytämään yhtä potentiaalifunktiota, joka toimii molemmilla operaatioilla, emme voi taata luvattuja tasoitettuja kustannuksia. Arvataan, että sopiva potentiaalifunktio voisi olla Φ = r + 2m Tarkastellaan seuraavaksi huolellisesti kutakin keko-operaatiota ja sen vaikutusta sekä juurten että merkittyjen solmujen määrään. Kuten edellä, r ja m ovat juurisolmujen ja merkittyjen solmujen lukumäärät ennen kutakin operaatiota, ja r’ ja m’ ovat vastaavat lukumäärät operaation jälkeen. Operaatio Todellinen c ∆r ∆m ∆Φ Tasoitettu ĉ LISÄYS 1 1 0 1 2 UNIONI 1 0 0 0 1 POISTA_MINIMI r + aste(min) r’ – r 0 r’ – r r’ + aste(min) PIENENNÄ_AVAINARVOA 1 + m – m’ 1 + m – m’ m – m’ 1 + m’ – m 2 Huomioi erityisesti, että yhden solmun korottaminen algoritmissa PIENENNÄ_AVAINARVOA vaatii vakioajan ja se kasvattaa juurten lukumäärää yhdellä; lisäksi enintään yksi merkitsemätön solmu korotetaan. Koska r’ + aste[[min[H]] = O(log2n), niin kaikki on kunnossa. 22 Tiivistelmä graafialgoritmeista • Seuraavassa oletetaan, että graafi G koostuu pisteiden ja niitä yhdistävien kaarten joukosta. Merkitään, että G = (V, E). • Graafi voi olla suunnattu tai suuntaamaton. • Suunnattu graafi G on järjestetty pari (V, E), missä V on äärellinen joukko ja E koostuu järjestetyistä pareista (u, v). Joukkoa V kutsutaan pisteiden (kärkien, solmujen) joukoksi, ja E on puolestaan viivojen (kaarten) joukko. • Suuntaamattoman graafin G = (V, E) viivajoukko E koostuu suuntaamattomista pareista {u, v}, missä u ≠ v. Usein myös merkintää (u, v) käytetään (aaltosulkeiden tilalle kaarisulkeet). • Suunnatussa graafissa u on viivan (u, v) lähtöpiste ja v on kyseisen viivan päätepiste. Sen pisteen aste tarkoittaa pisteestä lähtevien, ja sisäaste pisteeseen saapuvien kaarten määrää. • Suuntaamattomassa graafissa molemmat pisteet, joita kaari yhdistää, ovat päätepisteitä, ja tällöin niiden sanotaan olevan vierekkäisiä. • Suunnatussa graafissa myös silmukat eli pisteestä u itseensä johtavat viivat ovat laillisia. • Graafin G = (V, E) vierekkäisyyslista on |V|:stä listasta koostuva taulukko Vlistat. • Kaikilla graafin pisteillä v ∈ V, Vlistat[v] koostuu sellaisista pisteistä u ∈ V siten, että (v, u) ∈ E. • Yleensä alkioiden järjestyksellä vierekkäisyysmatriisissa ei ole merkitystä. • Mikäli graafi on suuntaamaton, niin kaaren (u, v) esiintyminen takaa myös kaaren (v, u) esiintymisen samaisessa graafissa. Tästä ei kuitenkaan ole takeita suunnatussa graafissa. 22 Tiivistelmä graafialgoritmeista • Esimerkki suuntaamattomasta graafista: ote Suomen rautatieverkosta Seinäjoki Kuopio Vaasa Parkano Jyväskylä Haapamäki Joensuu Jämsä Pieksämäki Pori Tampere Orivesi Mikkeli Kokemäki Toijala Hämeenlinna Rauma Loimaa Riihimäki Turku Lahti Kouvola Kerava Salo Karjaa Hanko Lappeenranta Helsinki 22 Tiivistelmä graafialgoritmeista • Ohessa esimerkki suunnatusta graafista: G 2 1 3 4 5 • Graafin vierekkäisyyslistat näyttäisivät puolestaan seuraavilta: 1 2 2 3 3 1 4 5 5 2 3 2 4 22.1 Leveyshaku • • • • • Leveyshaku on yksi yksinkertaisimmista graafien hakualgoritmeista. Syöte: Suunnattu tai suuntaamaton graafi G = (V, E) ja lähtöpiste s ∈ V. Tuloste: Algoritmi laskee jokaiselle pisteelle v ∈ V lyhimmän etäisyyden etäisyys[v] lähtöpisteeseen s. Lisäksi lasketaan arvo π[v] = u, missä u on pisteen v edeltäjä lyhimmällä polulla s v. π-arvoja käytetään leveyshakupuun konstruoimiseen. Algoritmin idea: Pisteestä s lähetetään ”aalto” kaikkiin mahdollisiin etenemissuuntiin. Aalto osuu ensiksi lähtöpisteestä yhden etäisyydellä oleviin pisteisiin. Seuraavaksi osutaan kahden etäisyydellä oleviin pisteisiin jne. Jokaisella graafin pisteellä v ∈ V on attribuutit: etäisyys[v] osoittaa etäisyyden lähtöpisteestä π[v] = pisteen v edeltäjä lyhimmällä polulla s v Kannattaa huomioida, että jos graafin jotkin pisteet eivät ole saavutettavissa lähtöpisteestä s, niitä ei koskaan sieltä aloitettaessa löydetä. Aputietorakenteena käytetään jonoa Q. Voidaan osoittaa, että jono Q koostuu suorituksen aikana sellaisista pisteistä, joiden etäisyysarvot ovat i, i, i, …, i, i + 1, i + 1, …, i + 1 22.1 Leveyshaku • Seuraavassa on esitettynä leveyshakualgoritmin pseudokoodi … LEVEYSHAKU(G, s) 1 FOR jokaiselle solmulle u ∈ V[G] – {s} DO 2 etäisyys[u] := ∝ π[u] := NIL 3 4 etäisyys[s] := 0 5 π[s] := NIL 6 Q := ∅ /* Alustetaan jono Q tyhjäksi. */ 7 LISÄÄ_JONOON(Q, s) 8 WHILE Q ≠ ∅ DO 9 u := OTA_JONOSTA(Q) 10 FOR jokaiselle pisteelle v ∈ Vlistat[u] DO 11 IF etäisyys[v] = ∝ /* Pistettä v ei ole vielä tarkasteltu? */ 12 THEN etäisyys[v] := etäisyys[u] + 1 13 π[v] := u 14 LISÄÄ_JONOON(Q, v) • … ja seuraavalla sivulla esitetään esimerkki algoritmin toiminnasta yhdelle esimerkkigraafille: 22.1 Leveyshaku r s t u r s t u r s t u ∝ 0 ∝ ∝ 1 0 ∝ ∝ 1 0 2 ∝ ∝ ∝ ∝ ∝ ∝ 1 ∝ ∝ ∝ 1 2 ∝ v w x y v w x y v w x y w r s Q: 1 1 t s 0 s t u r 1 0 2 ∝ 1 0 2 1 2 ∝ 2 1 v w x y v w r Q: t x v Q: 2 2 2 r s u r 1 0 2 3 1 2 1 2 3 2 w x y v v Q: u y 3 3 s u r 2 3 1 0 2 3 2 ∝ 2 1 2 3 y v w x x v u 2 2 3 Q: t r t x s Q: 1 2 2 t t 0 1 w Q: y 3 Q: 2 u 3 r 1 s 0 u x y v u y 2 3 3 t 2 u 3 2 3 2 1 2 3 x y v w x y Q: ∅ 22.1 Leveyshaku Leveyshaun analyysi • • • Jokainen piste alustetaan riveillä 2 ja 3. Tämän vaiheen kompleksisuus on Ο(V) (kyseistä merkintää käytetään pidemmän ja oikeaoppisemmanΟ(|V|) asemesta). Lisäksi jokainen graafin piste laitetaan jonoon ja poistetaan jonosta enintään kerran. Yhden jonooperaation kompleksisuus on Ο(1). Näin ollen yhteenlaskettu jono-operaatioiden kompleksisuus on Ο(V). Suunnatuissa graafeissa vierekkäisyyslistojen yhteispituus on |E|ja suuntaamattomissa graafeissa se on 2|E|. Täten riviltä 8 käynnistyvän while-silmukan yhteiskompleksisuus on Ο(E). Koko algoritmin kompleksisuus on Ο(E + V). Leveyshakupuut Määritellään leveyshakupuu graafina Gπ = (Vπ, Eπ), missä Vπ = {v ∈ V | π[v] ≠ NIL} ∪ {s}. Eπ = {(π[v], v) | v ∈ Vπ – {s}}. Täten Vπ koostuu kaikista sellaisista pisteistä, jotka ovat saavutettavissa pisteestä s käsin (mukaan lukien piste s itse). Kulkemalla puussa Gπ pisteestä v pisteeseen s π-arvoja pitkin kuljetaan itse asiassa lyhin polku pisteestä s pisteeseen v käänteisessä järjestyksessä. 22.1 Leveyshaku • Seuraavassa esitetään vielä äskeisen esimerkin leveyshakupuu: π s π π r w π v π t π u π x π v • Kannattaa huomioida, että leveyshaku löytää aina parhaan mahdollisen ratkaisun, mutta se toimii hitaasti etsittäessä kaukana lähtöpisteestä olevia solmuja graafin ollessa laaja. 22.2 Syvyyshaku • Syvyyshaku on esitelty ensimmäistä kertaa artikkelissa Edward F. Moore: The shortest path through a maze, Proceedings of the International Symposium on the Theory of Switching (Harward University Press 1959), sivut 285 – 292. • Algoritmin perusajatus: haetaan aina vain syvemmältä tietyn strategian ohjaamana: peräännytään vasta sitten kun on pakko. • Jokaisella solmulla v ∈ V on seuraavat attribuutit: π[v]: solmun v edeltäjä (kuten leveyshaussa) väri[v]: solmun väri (valkoinen, musta, harmaa) löydetty[u]: aikaleima, jolloin solmu on ensi kertaa löydetty valmis[u]: aikaleima, jolloin solmun käsittely on saatu päätökseen • Lisäksi on käytössä globaali muuttuja aika, jota käytetään solmujen aikaleimojen asettamiseen. • Aikaleimat ovat väliltä 1, 2, …, 2|V|, sillä jokainen solmu löydetään ja saadaan käsiteltyä valmiiksi tarkalleen kerran. Täten pitää paikkansa 1 ≤ löydetty[v] ≤ valmis[v] ≤ 2|V| • Seuraavaksi esitetään syvyyshakualgoritmin sekä sen tarvitseman apumetodin pseudokoodit. 22.2 Syvyyshaku SYVYYSHAKU(G) 1 FOR jokaiselle solmulle u ∈ V[G] DO 2 väri[u] := valkoinen 3 π[u] := NIL 4 aika := 0 5 FOR jokaiselle pisteelle u ∈ V[G] DO 6 IF väri[u] = valkoinen /* Pisteessä u ei ole vielä käyty */ 7 THEN KÄSITTELE_SOLMU(u) KÄSITTELE_SOLMU(u) 1 väri[u] := harmaa /* Harmaa väri merkitsee solmun ottamista käsittelyyn. */ 2 aika := aika + 1 3 löydetty[u] := aika 4 FOR jokaiselle solmulle v ∈ Vlistat[u] DO 5 IF väri[v] = valkoinen 6 THEN π[v] := u 7 KÄSITTELE_SOLMU(v) 8 väri[u] := musta /* Solmun u käsittely päättyy. */ 9 aika := aika + 1 10 valmis[u] := aika 22.2 Syvyyshaku • Tarkastellaan seuraavaksi syvyyshaun etenemistä seuraavassa suunnatussa graafissa. Oletetaan, että taustalla on etenemisstrategia, jonka mukaan pyritään siirtymään aina aakkosissa mahdollisimman alussa olevalla kirjaimella nimettyyn solmuun. a e 1 | 12 g 13 | 16 8 | 11 b f 9 | 10 2|7 c 3|4 d 5|6 h 14 | 15 22.2 Syvyyshaku Syvyyshaun analyysi • • • Syvyyshakualgoritmin suoritusaika: Rivien 1 – 3 ja 5 – 7 silmukat vievät ajan Ο(V) pois lukien se aika, joka kuluu metodin KÄSITTELE_SOLMU suorittamiseen. Proseduuria KÄSITTELE_SOLMU kutsutaan tarkalleen kerran kullakin solmulla v, sillä sitä kutsutaan ainoastaan solmun ollessa valkoinen. Heti ensi töikseen se värjää käsittelyyn saamansa solmun harmaaksi. Suoritettaessa kutsua KÄSITTELE_SOLMU(v) rivien 4 – 7 silmukkaa suoritetaan |Vlistat[v]| kertaa. Selvästikin ∑/∈1 |+,"'- . | = Θ(E) • Koko algoritmin suoritusajaksi saadaan siten Θ(E + V). Syvyyshakupuut ”Syvyyshakupuu” voi muodostua useista erillisistä puista, joten sitä kutsutaan syvyyshakumetsäksi. • Syvyyshakumetsän Gπ = (Vπ, Eπ) kaarten joukko on Eπ = {(π[v], v) | v ∈ V ja π[v] ∉ NIL} 22.2 Syvyyshaku • Edellinen esimerkki tuottaisi seuraavanlaisen syvyyshakumetsän: π a π g π b π c π e h π d f • Kannattaa huomioida, ettei syvyyshaku löydä välttämättä parasta ratkaisua. Sen sijaan ratkaisu voi löytyä huomattavasti leveyshakua nopeammin, jos hakustrategia on toimiva (aavistetaan hyvin, minne päin haku kannattaa suunnata). 22.3 Dijkstran algoritmi • Painotetulla graafilla tarkoitetaan graafia, jossa jokaiseen kaareen liittyy paino w(e). • Tämä voidaan tulkita esimerkiksi suuntaamattomassa graafissa kaaren päätepisteiden väliseksi etäisyydeksi (esimerkiksi välimatkoja kuvaava karttagraafi). • Dijkstran algoritmi ratkaisee graafin lyhimmän polun ongelman, kun syötteenä on sellainen painotettu graafi G = (V, E) siten, että w(e) ≥ 0 kaikille e ∈ E, eli jokaisen viivan paino on einegatiivinen. • Kyseistä algoritmia voidaan soveltaa sekä suuntaamattomille että suunnatuille painotetuille graafeille. • Algoritmi on leveyshaun painotettu yleistys. Se laskee lyhimmät polut annetusta pisteestä s graafin muihin pisteisiin. • Aputietorakenteena Dijkstran algoritmissa käytetään graafin pisteiden prioriteettijonoa. • Seuraavaksi esitetään algoritmin sekä sen käyttämien kahden apurutiinin pseudokoodit. DIJKSTRA(V, E, w, s) 1 ALUSTA_LÄHTÖPISTE(G, s) 2 Q := V[G] 3 WHILE Q ≠ ∅ DO 4 u := POISTA_MINIMI(Q) 5 FOR jokaiselle solmulle v ∈ Vlistat[u] DO 6 TESTAA_ETÄISYYS(u, v, w) 22.3 Dijkstran algoritmi • Apualgoritmi ALUSTA_LÄHTÖPISTE asettaa tarpeelliset alkuarvot etäisyyksille ja edeltäjille. ALUSTA_LÄHTÖPISTE(G, s) 1 FOR jokaiselle solmulle v ∈ V[G] DO 2 etäisyys[v] := ∝ 3 π[v] := NIL 4 etäisyys[s] := 0 • Apualgoritmi TESTAA_ETÄISYYS tutkii, että löydettiinkö aikaisempaa lyhyempi yhteys pisteiden u ja v välille. TESTAA_ETÄISYYS(u, v, w) 1 IF etäisyys[v] > etäisyys[u] + w(u, v) 2 THEN etäisyys[v] := etäisyys[u] + w(u, v) π[v] := u 3 22.3 Dijkstran algoritmi • Seuraavassa esitetään pieni esimerkki Dijkstran algoritmin toiminnasta seuraavalle graafille G, kun lähtöpisteeksi valitaan a. G b 8 3 5 6 9 4 1 c e f a 2 7 5 d a b c d e f Alustus: 0 / NIL ∝ / NIL ∝ / NIL ∝ / NIL ∝ / NIL ∝ / NIL Valitaan a: 0 / NIL 3/a 9/a 7/a ∝ / NIL ∝ / NIL Valitaan b: 0 / NIL 3/a 8/b 7/a 9/b 11 / b 22.3 Dijkstran algoritmi a b c d e f Valitaan d: 0 / NIL 3/a 8/b 7/a 9/b 11 / b Valitaan c: 0 / NIL 3/a 8/b 7/a 9/b 11 / b Valitaan e: 0 / NIL 3/a 8/b 7/a 9/b 10 / e Valitaan f: 0 / NIL 3/a 8/b 7/a 9/b 10 / e Lyhimmät polut pisteestä a graafin G muihin pisteisiin ovat siten seuraavat: a b: 3 (suora yhteys ab) a c: 8 (reitti abc) a d: 7 (suora yhteys ad) a e: 9 (reitti abe) a f: 10 (reitti abef) 22.3 Dijkstran algoritmi • Tarkastellaan seuraavassa veilä Dijkstran algoritmin kompleksisuutta: Rivin 1 alustusrutiini vie ajan Ο(V). Prioriteettijonojen operaatioiden suoritusajat: LISÄYS-operaatiota suoritetaan rivillä 2. Yksi operaation suoritus vie ajan Ο(log2V), ja operaatioita suoritetaan |V| kappaletta. Täten lisäysoperaatioiden kokonaisaika on Ο(V log2V). Operaatiota POISTA_MINIMI suoritetaan rivillä 4. Yksi operaation suoritus vie ajan Ο(log2V). Myös näitä operaatioita tehdään |V| kappaletta, joten POISTA_MINIMIoperaatioiden kokonaissuoritusaika on samoin Ο(V log2V). Avainarvon pienennysoperaatioita suoritetaan rutiinin TESTAA_ETÄISYYS rivillä 8. Yksi operaation suoritus vie aikaa Ο(log2V). Näitä operaatioita tehdään joko |E| tai 2|E| kappaletta riippuen siitä, onko kyseessä suunnattu vai suuntaamaton graafi. Täten avainarvon pienentämisoperaatioiden kokonaissuoritusaika on Ο(E log2V). Siten koko algoritmin suoritusaika on Ο((V + E) log2V).
© Copyright 2024