ratkaisut
Transcription
ratkaisut
58131 Tietorakenteet ja algoritmit (syksy 2015) Harjoitus 9, malliratkaisut 1. Solmut ovat V = {1, . . . , n} ja Adj[u] on solmun u vieruslista. Verkko voi tässä olla suunnattu tai suuntaamaton. Mahdolliset kaaripainot jätetään huomiotta. Seuraavat ovat keskenään yhtäpitäviä: • Verkossa on kaari (u, v) • v ∈ Adj[u] • A[u, v] = 1, missä A on verkkoa vastaava vierusmatriisi. (a) Seuraava algoritmi muodostaa solmujen V vieruslistoja vastaavan vierusmatriisin A. ListastaMatriisi(V, A) for i = 1 to n for j = 1 to n A[i, j] = 0 for i = 1 to n for jokaiselle j ∈ Adj[i] A[i, j] = 1 Algoritmin alustuksen aikavaativuus on O(|V |2 ) ja listojen läpikäynnin aikavaativuus on O(|V | + |E|), koska jokainen solmu käydään läpi ja jokaista kaarta kohden tehdään yksi sijoitusoperaatio matriisiin. Näin ollen aikavaativuus yhteensä on O(|V |2 ). Apumuuttujia on vakiomäärä, joten tilavaativuus on O(1). (b) Seuraava algoritmi muodostaa solmujen V vierusmatriisia A vastaavat vieruslistat. MatriisistaLista(A, V ) Alustetaan jokaiselle solmulle u uusi vieruslista Adj[u] for i = 1 to n for j = 1 to n if A[i, j] == 1 insert(Adj[i], j) // Listan insert Tämän algoritmin aikavaativuus on O(|V |2 ), koska matriisin jokainen alkio on käytävä läpi. Vakiolukumääräisten apumuuttujien lisäksi operaatiot insert luovat uudet listasolmut, ja niitä on O(|E|) kappaletta. Kun alustetaan uudet vieruslistat Adj jokaiselle solmulle, tilavaativuus on yhteensä O(|V | + |E|). (c) Käydään läpi verkon G kunkin solmun u vieruslistat ja lisätään jokaista läytyvää solmua v kohden kaarijoukkoon E T kaari (v, u). Transpose(G) Alustetaan jokaiselle solmulle u uusi vieruslista AdjT [u] for jokaiselle u ∈ V for jokaiselle v ∈ Adj[u] insert(AdjT [v], u) 1 Algoritmin aikavaativuus on O(|V | + |E|), koska jokaista solmua ja kaarta käydään läpi. Tilavaativuus on samoin O(|V | + |E|), koska luomme uudet vieruslistat AdjT , jokaiselle solmulle lista, jossa on vierussolmut. Toki voidaan myös ensin muuttaa vieruslistat vierusmatriisiksi (kohta a), muodostaa matriisin transpoosi ja sitten muuttaa vierusmatriisi vieruslistoiksi (kohta b). Aikavaativuus ja tilavaativuus on tässä tapauksessa O(|V |2 ). 2. (a) Oikein. Tehdään vastaoletus, ettei verkossa ole sykliä. Silloin verkon jokainen yhtenäinen komponentti on syklitön suuntaamaton yhtenäinen verkko, eli (määritelmän mukaan) puu. Puille pätee, että kaaria on yksi vähemmän kuin solmuja, joten kullekin komponentille (Vi , Ei ) on |Ei | = |Vi |−1, joten kun komponentteja Pk P on k kpl, |E| = i=1 |Ei | = ki=1 (|Vi | − 1) = |V | − k < |V |. Ristiriita. (b) Väärin. Väite voidaan osoittaa epätodeksi esimerkiksi oheisen vastaesimerkin avulla. Väite nimittäin pätee vain jos verkko on yhtenäinen. A C B D (c) Oikein. Tehdään vastaoletus, että väite ei päde, eli verkossa on vähintään kaksi erillistä komponenttia. Olkoon komponentti K ⊂ V verkon pienin komponentti ja u ∈ K mielivaltainen komponentin solmu. Nyt u:sta voi lähteä korkeintaan |V |/2 − 1 kaarta (muihin komponentin solmuihin), mutta jokaisen solmun aste oli vähintään |V |/2. Ristiriita. Väite voidaan myös todistaa helposti todeksi osoittamalla itseasiassa huomattavasti vahvempi tulos, että kahden mielivaltaisen solmun u ja v välisen lyhimmän polun pituus on korkeintaan 2. • Jos solmu u on v:n vieruslistassa (yhtäpitävästi v on u:n vieruslistassa), väite pätee triviaalisti. • Vaikka solmut u ja v eivät olisikaan suoraan yhdellä kaarella yhteydessä toisiinsa, kumpikin on suoraan yhdellä kaarella yhteydessä ainakin |V |/2 muuhun solmuun. Jos solmuilla on yhteinen naapuri, niiden välillä on polku jonka pituus on 2. Yhteinen naapuri on pakko olla olemassa, sillä muuten solmujen kokonaismäärän olisi oltava ainakin |V |/2 + |V |/2 + 2 = |V | + 2. Ristiriita. Koska mielivaltaisten kahden solmun välillä on polku, verkko on yhtenäinen. 3. Oletetaan, että vieruslistat käsitellään aakkosjärjestyksessä. Leveyssuuntaisessa läpikäynnissä solmut käsitellään järjestyksessä b, a, f, c, d, e ja syvyyssuuntaisessa läpikäynnissä järjestyksessä b, a, c, d, e, f. 4. Verkon kaksijakoisuuden tarkistamiseksi riittää yksinkertainen algoritmi, joka yrittää värittää verkon solmut. Algoritmi aloittaa värittämällä ensimmäisen solmun valkoiseksi. Tämän jälkeen kaikki edellisen solmun naapurit väritetään mustaksi. Näiden solmujen naapurit väritetään taas valkoiseksi jne. Jos väritettäessä jotain solmua 2 havaitaan solmun olevan jo valmiiksi väritetty eri värillä, verkko ei voi olla kaksijakoinen. Solmujen värien tallettamiseksi pidetään yllä totuusarvotaulukkoa valkoinen. Pseudokoodissa on käytetty merkintää ”¬v”, totuusarvon negaatiolle. Siis, jos v == FALSE, niin ¬v == TRUE ja kääntäen vastaavasti. Algoritmi vie selvästi ajan O(|V | + |E|), sillä algoritmi vastaa verkon syvyyssuuntaista läpikäyntiä. Kaksijakoinen(G = (V, E), s) 1 for jokaiselle solmulle u ∈ V 2 visited[u] = FALSE 3 valkoinen[u] = TRUE 4 return Väritä(s, FALSE) Väritä(s,väri) 1 if visited[s] return color[s] == väri 2 visited[s] = TRUE 3 valkoinen[s] = väri 4 for v ∈ vierus[s] 5 if not Väritä(v, ¬väri) 6 return FALSE 7 return TRUE Algoritmissa oletetaan, että käsiteltävä verkko on yhtenäinen. Jos näin ei ole, niin algoritmi käsittelee ainoastaan verkon solmun s sisältävän komponentin. Algoritmi yleistetään epäyhtenäisille verkoille tismalleen samalla tavalla kuin luentokalvojenkin syvyyssuuntainen läpikäynti. Ongelman ratkaisemiseksi kelpaisi myös verkon leveyssuuntainen läpikäynti. 5. Helpoin ratkaisu on luoda uusi verkko G0 : jokaista verkon G solmua s kohti luomme kaksi uutta solmua sp ja ss . Jos alkuperäisessä verkossa on punainen kaari (s, t), lisäämme uuteen verkkoon kaaren (ss , tp ), toisaalta jos verkossa on sininen kaari (s, t), lisäämme verkkoon G0 kaaren (sp , ts ). Uusissa solmuissa on siis tavallaan lisäinformaationa minkä väristä kaarta pitkin solmuun on tultu. Tämän verkon polut vastaavat täysin sellaisia alkuperäisen verkon polkuja, joissa kaarien värit vuorottelevat. Nyt lyhin sellainen reitti alkuperäisen verkon solmusta s solmuun t, jossa kaarien värit vuorottelevat voidaan selvittää kahdella leveyshaulla, toisessa lähtösolmuna sp ja toisessa ss . Lyhin reitti saadaan leveyshausta normaalilla tavalla. Algoritmin aika- ja tilavaativus on selvästi O(|V | + |E|), luomme uuden verkon, jossa kaarien ja solmujen määrä on enintään kaksinkertainen alkuperäiseen solmuun verrattuna ja suoritamme siinä kaksi (tai vain 1) leveyshakua. Esimerkiksi seuraava algoritmi toteuttaa oleellisesti ylhäällä esitetyn idean. Käsitellään värit kokonaislukuina, punainen=0 ja sininen=1: 3 RedBlue-BFS(G,s,t) G=(V,E) dist[1..|V|, 0..1] //oletuksena kaikkialle alustetaan ääretön prev[1..|V|, 0..1] dist[s, 0]=dist[s, 1]=0 Queue Q enqueue(Q,s) while Q not empty n=dequeue(Q) for each m in vierus[n] if dist[m, (n,m).color] == infinite dist[m, (n,m).color]=dist[n][1 - (n,m).color] + 1 prev[m, (n,m).color]=n enqueue(Q,m) // generoidaan vielä eräs lyhin polku Stack S push(S,t) n=t if dist[t,0] < dist[t,1] c=0 else c=1 while prev[n,c] != NIL push(S,prev[n,c]) n=prev[n,c] c=1-c return path Ylläolevassa pseudokoodissa oletetaan, että ääretön+1=ääretön. Erillistä visitedtaulukkoa ei tarvita, solmussa ollaan käyty silloin kun sen etäisyys on jotain muuta kuin alussa asetettu ääretön. Lyhin reitti palautuu pinomuodossa. 6. Ongelma voidaan ratkaista soveltamalla leveyssuuntaista läpikäyntiä. Tehdään leveyssuuntainen läpikäynti käyttämällä jonoja Pun ja Sin. 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 v viitteet v.pun ja v.sin pitämään yllä tietoa, mistä solmusta solmuun v tultiin. Ohessa on algoritmin esitys pseudokoodina. Aluksi verkko käydään kokonaisuudessaan läpi (läpikäynti voitaisiin lopettaa myös välittömästi kohdattaessa t). Jokaiseen solmuun v kerätään viitteisiin v.pun ja v.sin edeltävä solmu lyhimmällä polulla s ; v, joka koostuu punaisista ja sinisistä kaarista halutulla tavalla. Oikeanlaisen polun muodostamisessa on kriittistä, että kun polulla on esiintynyt ensimmäisen kerran sininen kaari, ei enää sisällytetä polkuun punaisia kaaria. Algoritmissa tämä ehto 4 on ratkaistu käyttämällä kahta jonoa. Viitteisiin v.pun ja v.sin ei kirjoiteta mitään, mikäli solmussa v ollaan jo vierailtu aikaisemmin, sillä tällöin tunnetaan jo ennestään lyhyempi polku s ; v. Tämän jälkeen polku s ; t kerätään pinoon kulkemalla viitteitä s.pun ja s.sin pitkin. Koska viitteet eivät sisällä ”turhia” arvoja, niin riittää valita aina se viite, jonka arvo on eri kuin NIL. Algoritmi palauttaa lopulta lyhimmän polun sisältävän pinon. Sinipunainen_polku(G, s, t) 1 Alusta jonot Pun ja Sin 2 Alusta v.sin = v.pun = NIL kaikilla v ∈ V 3 s.sin = s.pun = s 4 enqueue(Pun,s) 5 while not empty(Pun) or not empty(Sin) 6 Alusta jonot UPun ja USin 7 while not empty(Pun) 8 u = dequeue(Pun) 9 for v ∈ vierus[u] ja v.pun == NIL ja v.sin == NIL 10 if kaari u → v on punainen 11 enqueue(UPun, v) 12 v.pun = u 13 else 14 enqueue(USin, v) 15 v.sin = u 16 while not empty(Sin) 17 u = dequeue(Sin) 18 for v ∈ vierus[u] ja v.sin == NIL ja v.pun == NIL 19 if kaari u → v on sininen 20 enqueue(USin, v) 21 v.sin = u 22 Pun = UPun 23 Sin = USin 24 return Kerää_polku(s, t) Kerää_polku(s, t) 1 Alusta pino P 2 x=t 3 while x 6= s 4 push(P, x) 5 if x.pun 6= NIL 6 x = x.pun 7 else x = x.sin 8 push(P, s) 9 return P Algoritmin aikavaativuus ei eroa tavallisesta leveyssuuntaisesta läpikäynnistä, sillä jokaista verkon kaarta kohden lisätään yhteensä jonoihin Pun ja Sin korkeintaan yksi solmu. Lopussa suoritettava polun kerääminen ei myöskään kasvata aika- tai 5 tilavaativuutta, koska polun s ; t pituus on korkeintaan |V |. Aikavaativuus on siis O(|V | + |E|) ja tilavaativuus on O(|V |), koska jonoihin on pahimmillaan talletettu O(|V |) alkiota. Toinen tapa ratkaista tehtävä: Suoritetaan ensin leveyssuuntainen läpikäynti solmusta s vain punaisia kaaria pitkin, sitten leveyssuuntainen läpikäynti transpoosiverkossa solmusta t vain sinisiä kaaria pitkin. Nyt joka solmulla on etäisyysarvo punaisia pitkin s:stä (ääretön jos ei saavuteta) ja etäisyysarvo sinisiä pitkin t:hen (ääretön jos ei saavuteta). Valitaan tästä minimi. Läpikäyntien aikavaativuudet ovat O(|V | + |E|) ja minimi solmujen yli löydetään ajassa O(|V |). Polun tulostaminen vie aikaa, kuten edellä todettu, O(|V |). Aikavaativuus on siis O(|V | + |E|). 7. Olkoon G yhtenäinen verkko ja u sen solmu. Muodostetaan verkon syvyyssuuntainen puu D, sekä leveyssuuntainen puu B siten, että niiden juurena on solmu u. Pitää osoittaa, että jos nämä puut ovat samanmuotoiset, niin alkuperäinen verkko on muodostettujen puiden muotoinen. Todistamme vastaväitetodistuksena, että kun B ja D ovat samat ja verkko G ei ole puu, vaan siinä on muita kaaria kuin mitä näihin puihin kuuluvat, joudutaan ristiriitaan. Yhtenäinen verkko on puu, jos se ei sisällä syklejä. Koska nyt G sisältää muita kaaria kuin ne jotka ovat puissa B ja D, sieltä löytyy sykli. Olkoon s tähän sykliin kuuluva solmu, joka on puussa B lähimpänä juurta. Koska solmu s on osa sykliä, sillä on ainakin puussa B kaksi lasta t ja t0 , jotka molemmat kuuluvat tähän sykliin. Koska B ja D ovat samanmuotoiset vastaoletuksen nojalla, syvyyssuuntainen läpikäynti on kohdannut ensiksi solmun s. Nyt D ei voi kuitenkaan sisältää puun B tavoin molempia kaaria s → t ja s → t0 , sillä molemmat kuuluvat samaan sykliin ja syvyyssuuntainen läpikäynti tulisi toiseen solmuun eri kautta. Joten puut B ja D eivät voi olla samanmuotoiset jos verkko G ei ole puunmuotoinen. Allaoleva kuva esittää osan puusta B, joka ei voi olla samanlainen puun D kanssa. Saatiin siis osoitettua, että jos puut B ja D ovat samanmuotoiset, niin verkko G on puun muotoinen. Koska läpikäyntien puuhun ei voi kuulua verkkoon kuulumattomia kaaria ja niissä on yhtä paljon kaaria kuin verkossa G on näiden puiden oltava tismalleen samanmuotoisia kuin verkko. 6