Tietorakenteet ja algoritmit (syksy 0) Toinen välikoe, malliratkaisut. (a) Alussa puu näyttää tältä: Lisätään 4: 4 Tasapaino rikkoutuu solmussa. Tehdään kaksoiskierto ensin oikealle solmusta ja sitten vasemmalle solmusta. (RightLeftRotate) 4 Lisätään 0: 4 0 Tasapaino rikkoutuu solmussa. Tämä voidaan ratkaista kiertamällä vasem-
malle (LeftRotate): 4 0 (b) Alussa puu näyttää tältä: Poistetaan : Tasapaino rikkoutuu solmussa. Tässä tapauksessa kaksoiskierto ei toimi, vaikka tälle puulle se tuottaisi tasapainoisen lopputuloksen. Kierretään vasemmalle (LeftRotate): Poistetaan. Tilalle tulee seuraaja eli :
Tasapaino rikkoutuu solmussa. Kierretään ensin vasemmalle solmusta, ja sitten oikealle solmusta (LeftRightRotate):. (a) Keko on binääripuu jossa on löyhä järjestys, jotta voidaan pitää tehokkaasti yllä tietoa siitä mikä on kulloinkin pienin alkio jos keko on minimikeko, tai suurin alkio, jos se on maksimikeko. Yleensä keko toteutetaan taulukon avulla siten että solmun i lapset ovat solmut i ja i +. Keko on hyödyllinen tilanteissa jossa halutaan joukosta pienin tai suurin sillä hetkellä, esimerkiksi Dijkstran algoritmissa kun halutaan lähin solmu kullakin hetkellä. Minimikeon kekoehdot ovat: (K) Kaikki lehdet ovat kahdella vierekkäisella tasolla k ja k+ siten että tason k+ lehdet ovat niin vasemmalla kuin mahdollista ja kaikilla k:ta ylempien tasojen solmuilla on kaksi lasta. (K) Jokaisen solmun arvo on pienempi tai yhtä pieni kuin kumpikaan sen lasten arvoista. (b) Oletetaan että taulukko on -indeksoitu eli ensimmäisen alkion indeksi on, eikä 0. Aluksi taulukko näyttää tältä: Aluksi kutsutaan metodia build-heap, joka muuttaa taulukon keoksi. Aluksi taulukko näyttää puumuodossa tältä: Seuraavaksi kutsutaan operaatiota heapify indekseille,, (eli indeksistä taul.length/ alaspäin, jotka ovat sisäsolmuja): Heapify(taul, ) heapify(taul, )
heapify(taul, ) Nyt meillä on maksimikeko, ja voimme alkaa siirtämään siitä lukuja taulukon loppuun yksi kerrallaan. Poistetaan suurin, eli. Tilalle tulee taulukon viimeinen, eli. Tämän jälkeen keon kokoa pienennetään yhdellä, ja kutsutaan heapify(taul, ) jonka jälkeen keko näyttää tältä: Taulukko, jossa on yhä suurin luku, nyt viimeisenä, näyttää tältä: Siirretään taulukon loppuun (nyt toiseksi viimeiselle paikalle) ja kutsutaan heapify juurelle: Taulukko näyttää nyt tältä: 4
Taulukko näyttää nyt tältä: Taulukko näyttää nyt tältä: Siirretään vielä paikalleen, ja pienennetään kekoa yhdellä. Nyt keossa on jäljellä enää pienin luku, joka on oikealla paikallaan eli taulukon ensimmäisenä. Taulukko on järjestyksessä ja olemme valmiita. Järjestetty taulukko:. Ratkaisun ideana on muuttaa verkkoa jotta päästään käyttämään Dijkstran algoritmia. Ideana on se, että jos tietyllä tiellä on tietyö, kasvatetaan sen kaaren painoa jollain hyvin suurella luvulla. Kunhan luku on tarpeeksi suuri, mikä tahansa reitti jolla on tietyö on huonompi kuin reitti jolla ei ole tietyötä, ja samoin jos reitillä A on vähemmän tietöitä kuin reitillä B, reitti on aina parempi. Koska kaikkien tietyökaarien painoa on kasvatettu yhtä paljon, reitit joilla on sama määrä tietöitä ovat yhä samassa järjestyksessä. Esimerkiksi jos reiteillä A ja B on vaikka tietyötä, ja kasvatamme tietyökaarien paino tuhannella, molempien reittien pituus kasvaa 000:lla ja niiden järjestys säilyy. Tietyön painolisäykseksi voimme valita esimerkiksi kaikkien kaarten painojen summan. Oletetaan etteivät kaarien painot ole niin suuria että niistä olisi vaikutusta aikavaativuuteen. Käytämme tavallista Dijkstraa, jonka aikavaativuus on O(( V + E )log V ). Verkon muuttamisessa käymme kaikki kaaret läpi kerran, ja mahdollisesti muutamme kaaren painoa, eli tähän kuluu aikaa O( E ). Tämä on kuitenkin pienempää kuin Dijkstran aikavaativuus, eli algoritmimme aikavaativuus on sama, siis O(( V + E )log V ). Samoin tilavaativuus on sama kuin Dijkstralla, O( V ). 4. Todistetaan väite vastaoletuksella. Olkoon meillä kaksi erilaista pienintä virittävää puuta T ja T, sekä e painavin kaari joukossa (T T )\(T T ). Vaihtamalla T ja T tarvittaessa keskenään voidaan olettaa, että e T \T. Kun e poistetaan puusta T, jakautuu se kahteen komponenttiin. Nämä komponentit voidaan yhdistää jollain kaarella e T \T, joka on kaaren e valinnan perusteella välttämättä kevyempi. Täten puun T = (T {e })\{e} paino on aidosti pienempi kuin puun T, joten T ei voi olla pienin virittävä puu. Tämä on ristiriita.
Joitakin todistusyritelmiä ja mikä niissä menee pieleen: Seuraavanlainen outo vastaoletus oli melko yleinen: oletetaan, että verkossa on monta kaarta samalla painolla ja sitten näytetään vastaesimerkillä, että pienin virittävä puu ei ole välttämättä yksikäsitteinen. Tästä todettiin sitten seuraavan pienimmän virittävän puun yksikäsitteisyys tapauksessa, jossa kaarien painot ovat erilliset, missä ei tietenkään ole mitään järkeä. Toinen yleinen moka oli tarkastella vain tapausta missä pienimmät virittävät puut T ja T eroavat vain yhdellä kaarella. Tässä tapauksessa on toki selvää, että toinen puista on painoltaan aidosti pienempi, mutta tämä ratkaisutapa ei ota ollenkaan huomioon sitä, että pienimmät virittävät puut voisivat erota useammallakin kuin vain yhdellä kaarella. Kolmas kohtuullisen yleinen tapa oli väittää, että jokaisen solmuparin välinen lyhin polku sisältyy aina pienimpään virittävään puuhun, ja käyttää tätä todistaessa pienimmän virittävän puun yksikäsitteisyyttä. Tämä väite ei pidä paikkaansa, esimerkiksi verkon 4 a b d c pienin virittävä puu on a b d c eikä solmujen a ja b välinen lyhin polku näin sisälly siihen. ARVOSTELUPERUSTEET Koska tehtävänannossa oli erikseen huomautus, ettei riitä ainoastaan tarkastella, miten Kruskalin (tai Primin) algoritmi rakentaa pienimmän virittävän puun, sai tällaisesta ratkaisusta yleensä 0 pistettä. Pisteitä saattoi saada, jos todistuksessa oli muutakin tämän lisäksi. Tehtävästä sai yhden pisteen pienestä ideasta, vähän suuremmasta ideasta sai jo kaksi pistettä. Kolmeen pisteeseen vaadittiin lähes oikeaa todistusta ja neljä pistettä saadakseen piti todistuksen olla oikein.