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