Tietorakenteet, laskuharjoitus 11, ratkaisuja 1. Leveyssuuntaisen läpikäynnin voi toteuttaa rekursiivisesti käsittelemällä jokaisella rekursiivisella kutsulla kaikki tietyllä tasolla olevat solmut. Rekursiivinen aliohjelma saa parametrinaan listan käsiteltävistä solmuista ja se välittää eteenpäin uuden listan niiden naapureista, joita ei ole vielä käsitelty. Syvyyssuuntaisen läpikäynnin voi toteuttaa iteratiivisesti korvaamalla rekursion pinolla. Tällöin koodi on samanlainen kuin leveyssuuntaisessa läpikäynnissä, paitsi että jonon sijasta käytetään pinoa. Leveyssuuntaisessa läpikäynnissä rekursiosta ei ole aitoa hyötyä, minkä vuoksi algoritmin luontevampi toteutus on iteratiivinen. Syvyyssuuntaisessa läpikäynnissä molemmat toteutukset (iteratiivinen ja rekursiivinen) ovat tavallisia. 2. Seuraavassa kuvassa on verkon solmujen topologinen järjestys: x t v q z u s y w r Kuva 1: Tehtävän 2 solmut järjestettynä topologisesti. 3. Seuraavassa kuvassa ovat verkon vahvasti yhtenäiset komponentit: a b c d e f g h i Kuva 2: Tehtävän 3 vahvasti yhtenäiset komponentit. Tarkastelemalla verkon vahvasti yhtenäisistä komponenteista (kuva 3) muodostettua verkkoa on helppo nähdä, että verkkoon tarvitsee lisätä ainakin kaksi kaarta, jotta siitä saadaan vahvasti yhtenäinen. 1
Kuva 3: Tehtävän 3 vahvasti yhtenäisten komponenttien verkko. Yksi kaari ei riitä verkon yhtenäistämiseksi, koska a:n ja h:n komponenteista ei pääse muihin komponentteihin ilman, että verkkoon lisätään molempiin komponentteihin uusi lähtevä kaari. Yksi mahdollisuus on lisätä kaaret b:stä e:hen ja h:sta g:hen, jolloin alkuperäiset komponentit muodostavat ketjun ja kuuluvat siis kaikki samaan komponenttiin. 4. Ratkaisutapa 1: Muutetaan vieruslistaesitystä niin, että listojen sijasta käytetään taulukkoja. Tämä ei muuta tilankäyttöä, joka on edelleen O( E + V ). Lisäksi pidetään huolta siitä, että taulukot ovat järjestyksessä, jolloin kaaren etsiminen onnistuu binäärihaulla ajassa O(log V ). Ratkaisun huonona puolena on, että jos verkko muuttuu (kaaria täytyy lisätä tai poistaa), taulukoiden päivitys on hidasta. Ratkaisutapa 2: Tallennetaan kaikki verkon kaaret yhteen taulukkoon, joka on järjestetty ensisijaisesti lähtösolmun ja toissijaisesti kohdesolmun mukaan. Tilaa kuluu vain O( E ), koska muistissa ovat vain kaaret. Kaaren etsiminen tapahtuu jälleen binäärihaulla ajassa O(log E ) = O(log( V 2 )) = O(log V ). Tämänkin ratkaisun ongelmana on, että taulukon muuttaminen on hidasta. Molemmissa ratkaisutavoissa voi käyttää myös tasapainotettua binääripuuta (esim. AVL-puuta) taulukon sijaan. Tällöin myös verkon muuttaminen onnistuu ajassa O(log V ), mutta toteutus on huomattavasti monimutkaisempi. 5. Yksinkertaisin tapa tutkia onko verkko haavoittuva on kokeilla poistaa jokainen solmu vuorollaan, ja sen jälkeen tarkastaa verkon läpikäynnillä onko se yhtenäinen. Menetelmä vie aikaa O( V 2 + V E )). Algoritmi on esitetty ohessa vielä pseudokoodilla. Algoritmissa käytetään aputaulukkoa K merkitsemään onko solmussa jo vierailtu. 2
Haavoittuva (G) 1 for jokaiselle solmulle v V 2 Alusta False jokaiseksi aputaulukon K arvoksi 3 K[v] = True / Poistetaan solmu 4 l = DFS(x), missä x V ja x v 5 if l < V 1 / Kaikissa solmuissa ei vierailtu läpikäynnissä 6 return True 7 return False DFS(v) 1 if K[v] = = True 2 return 0 3 K[v] = True / Merkitään solmu vierailluksi. 4 l = 1 / Solmuja vierailtu tämän läpikäynnin aikana. 5 for jokaiselle v:n naapurille x 6 l = l + DFS(x) 7 return l Ongelman ratkaisuun on olemassa myös yksittäiseen syvyyssuuntaiseen läpikäyntiin pohjautuva tapa, joka vie ajan O( V + E ). Algoritmi tarkastelee syvyyssuuntaisen läpikäynnin muodostamaa puuta. Jokaiseen puun solmuun liitetään tieto solmusta, joka on lähimpänä puun juurta ja johon päästään tarkasteltavasta solmusta. Kutsutaan tätä solmua alisolmuksi. Jos jonkun solmun alisolmu on sen vanhempi syvyyssuuntaisen läpikäynnin muodostamassa puussa, niin tämän vanhemman poisto rikkoisi verkon yhtenäisyyden. Ohessa on esitetty algoritmi pseudokoodilla. Algoritmi käyttää apunaan aputaulukkoja K, A ja S. Taulukkoon K merkitään onko solmussa jo vierailtu. Taulukossa A pidetään yllä tietoa solmujen alisolmuista. Taulukkoon S merkitään kunkin solmun syvyys läpikäynnin muodostamassa puussa. Haavoittuva (G) 1 Jollekin v V 2 S[v] = 1 3 K[v] = True 4 for solmun v naapureille x 5 Vieraile(x, 2) 3
Vieraile(v, s) 1 A[v] = s 1 / Solmun vanhempi on toistaiseksi Alisolmu. 2 S[v] = s / Talletetaan syvyys. 3 K[v] = True / Merkitään solmu vierailluksi. 4 for solmun v naapureille x 5 if K[x] = = True 6 if S[x] < A[v] 7 A[v] = S[x] / Naapuri, joka on lähempänä juurta. 8 else 9 l = Vieraile(x, s + 1) 10 if l = = S[v] 11 Solmu on lapsensa alisolmu eli verkko on haavoittuva. 12 else if l < A[v] 13 A[v] = l / Myös tästä solmusta pääsee lapsen alisolmuun. 14 return A[v] 6. Ongelma voidaan ratkaista soveltamalla leveyssuuntaista läpikäyntiä. Tehdään leveyssuuntainen läpikäynti käyttämällä jonoja P ja S. Toiseen jonoon lisätään sellaiset solmut, joihin päästiin punaisella kaarella ja toiseen jonoon lisätään solmut, joihin päästiin sinisellä kaarella. Talletetaan lisäksi jokaiseen solmuun s viitteet s. pun ja s. sin pitämään yllä tietoa, mistä solmusta solmuun s tultiin. Ohessa on algoritmin esitys pseudokoodina. Aluksi verkko käydään kokonaisuudessaan läpi (läpikäynti voitaisiin lopettaa myös välittomästi kohdattaessa t). Tämän jälkeen polku s t kerätään pinoon kulkemalla viitteitä s.pun ja s. sin pitkin valitsemalla aina s. pun kun mahdollista. Lopuksi pinon sisältö tulostetaan. TulostaPolku (s, t) 1 alusta pino S 2 x = t 3 while x s 4 työnnä x pinoon S 5 if x.pun Nil 6 x = x.pun 7 else 8 x = x.sin 9 työnnä s pinoon S 10 tulosta pinon S sisältö pinojärjestyksessä 4
SiniPunaPolku (G, s, t) 1 s.pun = s.sin = s 2 for solmun s naapureille x 3 if kaari s x on punainen 4 lisää solmu x jonoon P 5 x.pun = s 6 else 7 lisää solmu x jonoon S 8 x.sin = s 9 while P ja S 10 ota solmu v jonon P päästä 11 for solmun v naapureille x 12 if kaari v x on punainen ja v.pun = = Nil 13 lisää solmu x jonoon P 14 x.pun = s 15 else if v.sin = = Nil 16 lisää solmu x jonoon S 17 x.sin = s 18 ota solmu v jonon S päästä 19 for solmun v naapureille x 20 if kaari v x on sininen ja v.sin = = Nil 21 lisää solmu x jonoon S 22 x.sin = s 23 TulostaPolku (s, t) 7. Tiedosto Riippuvuus.java sisältää Java-kielisen ohjelman, joka etsii kelvollisen riippuvuuksien järjestyksen, jos sellainen on olemassa. Kyseessä on syklittömyyden tarkastamisen ja topologisen järjestämisen sovellus. 5