58131 Tietorakenteet Erilliskoe 11.11.2008, ratkaisuja (Jyrki Kivinen) 1. (a) Koska halutaan DELETEMAX mahdollisimman nopeaksi, käytetään järjestettyä linkitettyä listaa, jossa suurin alkio on listan kärjessä. Muutuja Q osoittaa listan ensimmäistä alkiota. DELETEMAX(Q) value key[q] Q next[q] return value INSERT(Q, k, x) Lisätään data x avaimella k p new node key[p] k data[p] x if Q = NIL or k key[q] then next[p] Q Q p else r Q s next[r] while s NIL and k < key[s] next[r] p next[p] s INCREASEKEY(Q, k, v) Kasvata avain k arvoon v if Q NIL and v > k then r NIL s Q while s NIL and key[s] > v p r solmu jonka jälkeen v tulee while s NIL and key[s] > k if s NIL and key[s] = k then Avain k löytyi key[s] v if r NIL and key[r] < v then next[r] next[s] if p = NIL then next[s] Q Q s else next[s] next[p] next[p] s Selvästi DELETEMAX toimii vakioajassa. Proseduurit INSERT ja INCREASEKEY käyvät pahimmmassa tapauksessa koko listan läpi, joten aikavaativuudet ovat O(n), missä n on alkioiden lukumäärä. (b) Jos avaimet lisätän järjestyksessä pienimmästä suurimpaan, niin lisäys kohdistuu aina listan kärkeen, jolloin aikavaativuus on O(1) per operaatio. Koko operaatiojonon aikavaativuus on siis O(n).
(c) Jos avaimet lisätään järjestyksessä suurimmasta pienimpään, niin lisäykset kohdistuvat aina listan häntään, joten aikavaativuus lisäystä kohti on O(k), missä k on listan alkioiden lukumäärä lisäyshetkellä. Aikavaativuudeksi saadaan siis O(1 + 2 + 3 +... + n) = O(n 2 ). Arvostelu: Aikavaativuustarkasteluista yhteensä on saanut maksimissaan 4 pistettä ja pseudokoodeista 6 pistettä. Jos toteutuksessa on käytetty apuproseduureja (esim. DELETE listoille) kuvaamatta niitä, on vähennetty 2 pistettä. Jos prioriteettijonon operaatioita on selvästi tulkittu väärin, on vähennetty 2 3 pistettä. 2. (a) 20 5 10 15 20 25 30 35 45 50 55 65 70 75 85 90 95 100 (b) Lisätään 12: 12 20 5 10 12 15 20 25 30 35 45 50 55 65 70 75 85 90 95 100 Lisätään 92: 12 20 92 5 10 12 15 20 25 30 35 45 50 55 65 70 75 85 90 92 95 100 2
Lisätään 52: 12 20 52 92 5 10 12 15 20 25 30 35 65 70 75 85 90 92 95 100 45 50 52 55 (c) Poistetaan avaimet 15, 20, 35,, 55,, 30 ja 50. Ensimmäiset kuusi poistoa eivät aiheuta puun rakenteeseen muutoksia: 20 5 10 25 30 45 50 65 70 75 85 90 95 100 Viides poisto (30) aiheuttaa kahden lehden yhdistämisen: 5 10 25 45 50 65 70 75 85 90 95 100 Kuudes poisto (50) jättäisi yhdelle sisäsolmulle vain yhden lapsen: 5 10 25 45 65 70 75 85 90 95 100 3
Sisäsolmujen yhdistäminen jättäisi juurelle vain yhden lapsen, joten se poistetaan tarpeettomana: 5 10 25 45 65 70 75 85 90 95 100 Pisteytys: Kohta (a) 4 pistettä, kohdat (b) ja (c) kumpikin 3 pistettä. Selvästi yleisin virhe oli muistaa B + -puun määritelmä väärin siten, että sisäsolmujen viitta-arvojen suurimmaksi lukumääräksi oli asetettu 4, kun sen pitää siis tässä olla suurin lasten lukumäärä. Tällainen virhetulkinta teki (a)-kohdan turhan helpoksi ja tuhosi kokonaan (c)-kohdan, joten siitä on vähennetty yhteensä 4 pistettä. Toinen yleinen virhe oli laittaa sisäsolmuihin yksi viitta-arvo liikaa. 3. Pikajärjestäminen on selitetty kevään 2008 luentokalvoilla. 3 363. Pisteytys: Algoritmin perusajatuksen selittämisestä on voinut saada 5 pistettä. Keskeiset kohdat ovat jakoalkion rooli ja taulukon jakaminen osataulukoiksi, jotka järjestetään rekursiivisesti. Aika- ja tilavaativuuden esittämisestä O-merkinnällä on saanut 3 pistettä, mihin on edellytetty aikavaativuudesta sekä pahin että keskimääräinen tapaus. Loput 2 pistettä on saanut esittämällä pari muuta huomiota, esim. pienet vakiokertoimet, mikä on pahin tapaus. 4. Tehtävänä on siis löytää suunnatusta syklittömästä verkosta G = (V, E) pisin polku annetusta lähtösolmusta u annettuun maalisolmuun t. Tämä on samantapainen ongelma kuin luentomateriaalissa esitetty kriittiset työvaiheet (s. 431 434). Lasketaan siis jokaiselle solmulle v V lukuarvo pituus[v], joka on pisimmän polun pituus solmusta v solmuun t. Lisäksi talletetaan muuttujaan polku[v] kyseisen polun ensimmäinen solmu. Jos polkua v t ei ole, merkitään pituus[v] = 1. Algoritmi perustuu havaintoon pituus[u] = max {pituus[v] + 1 (u,v) E } (paitsi jos polkua ei ole). Koska verkko on syklitön, pituus-arvot voidaan laskea tämän mukaan noudattaen verkon käänteistä topologista järjestystä, eikä kehämääritelmiä synny. Tämä voidaan helposti toteuttaa syvyyssuuntaisen läpikäynnin yhteydessä: PISINPOLKU(V, E, s, t) for v V do pituus[v] 1 vierailtu[v] FALSE pituus[t] 0 polku[t] NIL vierailtu[t] TRUE VIERAILE(s) v s while v NIL do PRINT(v) v polku[v] VIERAILE(u) vierailtu[u] TRUE for v L[u] do if not vierailtu[v] then VIERAILE(v) if pituus[v] 1 and pituus[u] < pituus[v] + 1 then pituus[u] pituus[v] + 1 polku[u] v 4
Arvostelu: Tehtävä oli tarkoitettukin hieman vaikeammaksi soveltavaksi tehtäväksi. Pari pistettä on voinut saada esittämällä asiaan liittyviä huomioita tai jollain tapaa järjellisiä ratkaisuyrityksiä, mutta enempiin pisteisiin on vaadittu oikeilla jäljillä oleva ratkaisuidea. Jos ratkaisu noudattaa edellä esitettyä perusideaa, mutta toteutus perustuu tehottomaan rekursioon, on vähennetty 2 pistettä. (Edellä esitetyn palautuskaavan evaluoiminen naiivilla rekursiolla johtaa pahimmillaan eksponentiaaliseen aikavaativuuteen, koska samaan solmuun voidaan päästä useita eri reittejä pitkin.) 5. Kurssilla on käsitelty Kruskalin algoritmi (kevään 2008 luennot, s. 509 alkaen) ja Primin algoritmi (s. 518). Esimerkin lisäksi toivottiin suunnilleen seuraavan tasoista sanallista kuvausta: Kruskal: Algorimin idea on käydä kaaret läpi painon mukaan kasvavassa järjestyksessä. Vuorossa oleva kaari lisätään virittävään puuhun, paitsi jos tämä loisi syklin. Algoritmi pysähtyy, kun puussa on V 1 kaarta. Aputietorakenteeksi tarvitaan ensinnäkin keko, johon ensin viedään kaikki kaaret ja josta sitten kaaria poimitaan operaatiolla DELETE-MIN yksi kerrallaan. Vaihtoehtoisesti, ja hieman tehottomammin, voidaan ensin järjestää kaikki kaaret aputaulukkoon. Lisäksi pitää voida testata, aiheuttaisiko kaaren (u, v) lisääminen syklin. Tätä varten pidetään yllä tietorakennetta, jossa on tähän mennessä muodostetun virittävän metsän yhtenäiset komponentit. Operaatiot ovat seuraavat: FIND(u) palauttaa sen komponentin nimen, johon solmu u kuuluu. Komponentin nimenä on jokin tietty siihen kuuluva solmu. Jos FIND(u) = FIND(v), kaarta (u, v) ei saa lisätä, koska tästä aiheutuisi sykli. Jos kaari (u,v) lisätään, yhdistetää solmujen u ja v komponentit suorittamalla UNION(x,y), missä x = FIND(u) ja y = FIND(v). Prim: Virittävää puuta rakennetaan mielivaltaisesta lähtösolmusta alkaen liittämällä puuhun aina se solmu, joka voidaan liittää pienimmällä kustannuksella. Aputietorakenteeksi otetaan taas keko, jossa tällä kertaa kuitenkin on solmuja, ei kaaria. Solmun u avaimena on pienin sellaisen kaaren (u,v) paino, jolla v on jo liitetty puuhun. Liittämisvuorossa oleva solmu saadaan operaatiolla DELETE-MIN. Lisäksi solmua u liitettäessä pitää pienentää kaikkia sellaisia solmujen v avaimia, jotka ovat enemmän kuin w(u, v). Tämä tapahtuu operaatiolla DECREASE-KEY(v, w(u, v)). Edellä esitetyn mukaisista ratkaisuista on saanut 9 pistettä algoritmin idean kuvauksesta ja 6 pistettä tietorakenteiden kuvauksesta. Tietorakenteilla tarkoitetaan tässä oleellisesti hahmotelmaa siitä, miten algoritmisesti voidaan toteuttaa sellaiset asiat, jotka esimerkissä näkee suoraan kuvasta (syklin toteaminen jne.). 5