10. Painotetut graafit Esiintyy monesti sovelluksia, joita on kätevä esittää graafeina. Tällaisia ovat esim. tietoverkko tai maantieverkko. Näihin liittyy erinäisiä tekijöitä. Tietoverkkoja käytettäessä on monesti hyödyllistä tietää nopein tietoliikenneyhteys eri pisteiden välillä tämähän riippuu mm., mitä teknologiaa käyttäen yhteydet on rakennettu. Maantieverkossa merkitsevät konkreettiset etäisyydet. Näitä, kuten lukemattomia muitakin, voidaan esittää painotettuina graafeina (weighted graph), joista on lentoreittiesimerkki kuvassa 10.1. Jokaiseen kaareen e = (u,v) liitetään painoarvo (tässä kokonaisluku) w(e). Tässä luvussa pohditaan painotettuihin graafeihin liittyviä lyhimmän polun ongelmaa ja minimivirittävien puiden ongelmaa. 10. luku 507
2704 740 187 JFK 1235 DFW 1121 Kuva 10.1. Esimerkki painotetusta graafista, jossa solmut ovat lentokenttiä ja kaaret on painotettu niiden välisillä etäisyyksillä maileissa. 10. luku 508
10.1. Lyhimmät polut Olkoon G = (V,E) painotettu graafi, missä V on solmujen joukko ja E kaarien joukko. Määritellään G:ssä polun P = ( (v 1, v 2 ), (v 2, v 3 ), (v 3, v 4 ),, (v k 1, v k )) pituus tai paino tai kustannus (length, weight, cost) polun kaarien painojen summana: k 1 = i+ i= 1 w( P) w(( v i, v 1)). Määritellään etäisyys (distance) d(v,u) graafin G solmusta v solmuun u näiden välisen lyhimmän polun pituutena edellyttäen, että sellainen polku on olemassa (jos ei ole, voidaan merkitä äärettömäksi). Yleensä painot ilmaistaan ei negatiivisina lukuina. Negatiivisiakin voidaan erikoismerkityksissä joskus soveltaa, mutta ne aiheuttavat omat rajoituksensa eikä tässä käsitellä niitä. 10. luku 509
Ahne menetelmä Esitetään ahne lähestymistapa yhden lähtösolmun lyhimmän polun (single source shortest path) ongelmaan, jossa etsitään lyhin polku lähtien aloitusolmusta v jokaiseen muuhun graafin solmuun. Esitettävä menetelmä on tavallaan painotetun graafin leveyshaku. Ahne menetelmä kasvattaa iteratiivisesti solmujen pilveä lähtien aloitussolmusta. Kullakin iteraatiolla valitaan seuraava pilven ulkopuolinen, lähin solmu, joka lisätään pilveen. Tätä toistetaan, kunnes kaikki solmut on lisätty, jolloin on olemassa polku aloitussolmusta kaikkiin muihin. Lähestymistapa perustuu ahneeseen (greedy method) algoritmien suunnittelumenetelmään. Se soveltuu yleisesti tilanteessa, jossa yritetään optimoida jotakin funktiota yli kohteiden kokoelman tässä funktio on lyhimmän polun etäisyys. Perusajatuksena on lisätä yksi kerrallaan valiten se niin, että saadaan funktion optimiarvoa. 10. luku 510
Yhden lähtösolmun lyhimmän polun ongelman tapauksessa ahne menetelmä tuottaa täysin oikein (optimiarvon antavan) toimivan algoritmin, nk. Dijkstran algoritmin (Dijkstra s algorithm). Sovellettaessa ahnetta menetelmää johonkin toiseen ongelmaan se ei välttämättä anna optimaalista tulosta, vaan lähes parhaan. Tällainen on mm. kauppamatkustajan ongelma (travelling salesman problem), jossa etsitään lyhintä polkua, kun pitää käydä kaikki graafin solmut tarkalleen kerran. Dijkstran algoritmin toteutus Käytetään eräässä mielessä etäisyysfunktion approksimaatiota, josta lopulta päästään todelliseen etäisyyteen. Määritellään jokaiselle V:n solmulle u D[u] approksimoiduksi etäisyydeksi solmusta v. D[u] käsittää kulloinkin parhaimman löydetyn polun eli kyseisellä hetkellä lyhimmän. Aluksi jokaiselle solmulle u v määritellään D[v] = 0 ja D[u] = +. Määritellään solmujen joukko C, pilvi, aluksi tyhjäksi. Ensimmäisenä iteraationa asetetaan aloitussolmu v joukkoon C. 10. luku 511
Seuraavilla iteraatioilla valitaan pienin D[u], kun u ei ole vielä joukossa, ja siirretään u joukkoon C. Tämän yhteydessä päivitetään kaikkien sellaisten solmujen z etäisyydet, jotka solmut ovat u:n vierekkäisiä ja eivät ole vielä joukossa C. Tämä kuvaa sitä, että voi olla uusi lyhyempi polku solmuun z kuin tähän asti tunnettu lyhin. Tämä päivitysoperaatio on relaksaatio (relaxation), sillä se tarkastaa, voidaanko vanhaa estimaattia parantaa lähemmäksi todellista arvoaan (minimietäisyyttä). Kaaren relaksaatio operaatio on nyt: if D[u] + w((u,z)) < D[z] then D[z] D[u] + w((u,z)) Pseudokoodi Dijkstran algoritmia varten on koodina 10.1., ja sitä havainnollistetaan kuvassa 10.2. 10. luku 512
Algorithm ShortestPath(G,v): Input: Painotettu graafi G ja tästä aloitussolmu v. Output: Jokaiselle solmulle u arvo D[u], missä D[u] on lyhimmän polun pituus solmusta v solmuun u graafissa. alustetaan arvot D[v] = 0 ja D[u] = + jokaiselle solmulle u v olkoon Q prioriteettijono, joka käsittää kaikki graafin G solmut ja jossa avaimina ovat arvot D while Q do {valitaan u pilveen C} u Q.removeMinElement() Koodi 10.1. (alku) Dijkstran algoritmin pseudokoodi. Prioriteettijonossa Q ovat ne solmut, jotka eivät ole vielä pilvessä C. 10. luku 513
for jokaiselle u:n vierekkäiselle solmulle z prioriteettijonosta Q do {suoritetaan relaksaatio operaatio kaarelle (u,z)} if D[u] + w((u,z)) < D[z] then D[z] D[u] + w((u,z)) vaihdetaan z:n avainarvo Q:sta uudeksi arvoksi D[z] return jokaisen solmun u arvo D[u] Koodi 10.1. (loppu) Dijkstran algoritmin pseudokoodi. Prioriteettijonossa Q ovat ne solmut, jotka eivät ole vielä pilvessä C. 10. luku 514
1235 DFW 2704 1391 1121 (a) 621 740 0 849 184 187 JFK 144 PVD Kuva 10.2. (alku) Dijkstran algoritmin suoritus. Solmun viereinen arvo on D[v] (suorakaiteessa). 10. luku 515
2704 1235 DFW 1121 621 1391 (b) 621 740 0 849 184 JFK 144 184 Kuva 10.2. (jatkoa) Löydetty lyhin polku on piirretty paksulla nuolella. Pilven ulkopuoliset, viereiset solmut osoitetaan paksulla kaarella, kun näille on löydetty nykyinen (paras) kaari. 10. luku 516
2704 621 849 1391 0 1235 DFW 1121 1575 (c) 371 JFK 144 328 184 Kuva 10.2. (jatkoa) 10. luku 517
2704 621 849 1391 0 1235 DFW 1121 1575 (d) 371 JFK 144 328 184 Kuva 10.2. (jatkoa) 10. luku 518
3075 2704 621 849 1391 0 1235 DFW 1121 1575 (e) 371 JFK 144 328 184 Kuva 10.2. (jatkoa) 10. luku 519
2467 2704 621 849 1391 0 1235 DFW 1121 1423 (f) 371 JFK 144 328 184 Kuva 10.2. (jatkoa) 10. luku 520
2467 3288 2704 621 849 1391 0 1235 DFW 1121 1423 (g) 371 JFK 144 328 184 Kuva 10.2. (jatkoa) 10. luku 521
2467 2658 2704 621 849 1391 0 1235 DFW 1121 1423 (h) 371 JFK 144 328 184 Kuva 10.2. (jatkoa) 10. luku 522
2467 2658 2704 621 849 1391 0 1235 DFW 1121 1423 (i) 371 JFK 144 328 184 Kuva 10.2. (jatkoa) 10. luku 523
2467 2658 2704 621 849 1391 0 1235 DFW 1121 1423 (j) 371 JFK 144 328 184 Kuva 10.2. (loppu) 10. luku 524
Miksi Dijkstran algoritmi toimii halutulla tavalla? Perusteena on se, että solmuun u liitetty arvo D[u] päivitetään asianmukaisesti ja valittaessa juuri solmu u pilveen C mukaan se poistetaan samalla prioriteettijonosta Q. Seuraava lause, jonka perustelu sivuutetaan, kiteyttää olennaisimman. Lause 10.1. Otettaessa solmu u mukaan pilveen C arvo D[u] on yhtä suuri kuin d(v,u), lyhin polku solmusta v solmuun u. Tämä pitää paikkansa jokaiselle graafin G solmulle u. Dijkstran algoritmi kuvattiin melko abstraktilla tasolla. Täten sen suoritusaika riippuu määrätyn yksityiskohdan, prioriteettijono, toteutuksesta. Graafi G on mielekästä toteuttaa vierekkyyslistana, koska tällöin päästään nopeasti vierekkäisiin solmuihin (suoraan suhteessa näiden määrään) relaksaatio operaation aikana. 10. luku 525
Dijkstran algoritmin suoritusaikojen käsittely on hieman pitkällinen, joten todetaan vain yhteenvedonomaisesti seuraavat seikat. Jos prioriteettijono toteutetaan eräin erikoisominaisuuksin (ei selosteta tällä kurssilla) varustettuna kekona (heap), saadaan pahimman tapauksen suoritusajaksi O((n + m) log n), missä n on solmujen ja m kaarien määrä. Tulos voidaan esittää pelkästään solmujen määrän n funktiona muodossa O(n 2 log n). Toteutettaessa prioriteettijono järjestämättömänä sekvenssinä eräät sen operaatiot mahdollistavat niiden nopean käytön, jolloin suoritusaika on O(n 2 + m). Tämä voidaan muokata muotoon O(n 2 ). Huolimatta näistä tuloksista keko on monesti parempi, jos kaarien lukumäärä verrattuna solmujen määrään ei ole suuri. Myös keskimääräisen suoritusajan tilanteessa keko on monesti käytännössä parempi. 10. luku 526
10.2. Minimivirittävät puut Olkoon esimerkkinä rakennuksen tietokoneiden yhdistäminen kaapelein käyttäen kaapelia minimimäärän. Tätä voidaan mallintaa painotetun graafin ongelmana laatimalla graafi G, jossa jokainen solmu vastaa tietokonetta ja määrätään paino w((v,u)) kaarelle (v,u). Paino vastaa tarvittua kaapelia yhdistämään solmu v solmuun u. Nyt ei ole kysymys lyhimmän polun laskemisesta jostakin tietystä solmusta lähtien, vaan halutaan muodostaa puu T, joka sisältää kaikki G:n solmut ja jolla on pienin painojen summa w( T ) = ( v, u) T w(( v, u)) kaikkien mahdollisten tällaisten puiden joukosta. Puu, joka käsittää kaikki G:n solmut, on virittävä puu (spanning tree), kuten aiemmin on mainittu. Pienimmän painojen summan käsittävä virittävä puu on minimivirittävä puu (minimum spanning tree, MST) 10. luku 527
Esitetään kaksi ahnetta algoritmia minimivirittävän puun klassista ongelmaa varten. Ennen näitä tarkastellaan kuitenkin oheista lausetta ja siihen liittyvää kuvaa 10.3. Lause 10.2. Olkoon G = (V, E) painotettu yhdistetty graafi. Olkoot V 1 ja V 2 kaksi erillistä, ei tyhjää joukkoa sekä lisäksi V =V 1 V 2. Olkoon vielä eg:n kaari, kun tämä on yksi sellaisista, joiden toinen pää on joukossa V 1 ja toinen joukossa V 2. Silloin on minimivirittävä puu T, jolla on e yhtenä kaaristaan ( siltakaari ). Perustelu sivuutetaan. 10. luku 528
e minimipainoinen siltakaari Kuva 10.3. Askel minimivirittävän puun muodostamisesta. 10. luku 529
Kruskalin algoritmi Edellistä lausetta käytetään perustana minimivirittävän puun muodostamisessa. Kruskalin algoritmissa sellainen muodostetaan klustereista eli rypäistä. Aluksi kukin solmu muodostaa oman erillisen klusterinsa. Algoritmi käy kaaret läpi kasvavassa painojärjestyksessä. Jos kaari e yhdistää kaksi erillistä klusteria, e lisätään minimivirittävää puuta muodostavaan kaarien joukkoon ja kyseiset klusterit yhdistetään e:llä yhdeksi klusteriksi. Jos toisaalta e yhdistää kaksi jo samassa klusterissa olevaa solmua, e hylätään. Kun kaaria on lisätty tarpeeksi, jotta minimivirittävä puu on syntynyt, algoritmin suoritus päättyy. Kruskalin algoritmi on koodina 10.2. Kuva 10.4. esittää algoritmin toimintaa. 10. luku 530
Algorithm Kruskal(G): Input: yhdistetty painotettu graafi G, jossa on n solmua ja m kaarta Output: graafin G minimivirittävä puu T for jokaiselle G:n solmulle v do määritellään klusteri C(v) {v} alustetaan prioriteettijono Q käsittämään kaikki G:n kaaret käyttäen painoja avaimina T {lopuksi T sisältää minimivirittävän puun kaaret} while Q do poistetaan Q:sta kaari (v,u), jonka paino on pienin olkoon C(v) solmun v sisältävä klusteri ja olkoon C(u) solmun u sisältävä klusteri if C(v) C(u) then lisätään kaari (v,u) puuhun T yhdistetään C(v) ja C(u) yhdeksi klusteriksi eli näiden unioniksi return puu T Koodi 10.2. Kruskalin algoritmi. 10. luku 531
2704 1391 849 JFK 144 1235 DFW 1121 (a) Kuva 10.4. (alku) Kruskalin algoritmin toiminnasta esimerkki: (a) alkutilanteen klusterit on merkitty taustavärillä. 10. luku 532
2704 1391 849 JFK 144 1235 DFW 1121 (b) Kuva 10.4. (jatkoa) Puuhun käytetty kaari on merkitty vahvennettuna. 10. luku 533
2704 1391 849 JFK 144 1235 DFW 1121 (c) Kuva 10.4. (jatkoa) 10. luku 534
2704 1391 849 JFK 144 1235 DFW 1121 (d) Kuva 10.4. (jatkoa) 10. luku 535
2704 1391 849 JFK 144 1235 DFW 1121 (e) Kuva 10.4. (jatkoa) 10. luku 536
2704 1391 849 JFK 144 1235 DFW 1121 (f) Kuva 10.4. (jatkoa) 10. luku 537
2704 1391 849 JFK 144 1235 DFW 1121 (g) Kuva 10.4. (jatkoa) Hylätyt kaaret esitetään katkoviivoina. 10. luku 538
2704 1391 849 JFK 144 1235 DFW 1121 (h) Kuva 10.4. (jatkoa) 10. luku 539
2704 1391 849 JFK 144 1235 DFW 1121 (i) Kuva 10.4. (jatkoa) 10. luku 540
2704 1391 849 JFK 144 1235 DFW 1121 (j) Kuva 10.4. (jatkoa) 10. luku 541
2704 1391 849 JFK 144 1235 DFW 1121 (k) Kuva 10.4. (jatkoa) 10. luku 542
2704 1391 849 JFK 144 1235 DFW 1121 (l) Kuva 10.4. (jatkoa) 10. luku 543
2704 1391 849 JFK 144 1235 DFW 1121 (m) Kuva 10.4. (jatkoa) 10. luku 544
2704 1391 849 JFK 144 1235 DFW 1121 (n) Kuva 10.4. (loppu) 10. luku 545
Ei perehdytä Kruskalin algoritmin perusteisiin tätä enempää, mutta oletetaan prioriteettijonon toteutetun kekona. Tällöin suoritusaika on seuraavan lauseen mukainen. Lause 10.3. Annetun yhdistetyn painotetun graafin G sisältäessä n solmua ja m kaarta Kruskalin algoritmi etsii siitä minimivirittävän puun ajassa O(m log n). Prim Jarnik algoritmi Tässä algoritmissa, josta käytetään useimmin nimitystä Primin algoritmi, minimivirittävää puuta kasvatetaan yhden solmun klusterista, joka käsittää aluksi juurisolmun v. Sen idea muistuttaa Dijkstran algoritmia. Aloitetaan solmusta v. Tätä C:tä kasvatetaan määräämällä jokaisella iteraatiolla kaari e = (v,u), jonka paino on pienin sekä jonka solmu v on C:ssä ja solmu u tämän ulkopuolella. Solmu u otetaan C:hen mukaan. Prosessia toistetaan, kunnes minimivirittävä puu on muodostunut. Taas ylläpidetään solmua varten arvoa D[u], joka käsittää ko. hetkellä parhaimman kaaren painon liitettäessä solmua u C:hen. 10. luku 546
Algoritmi esitetään pseudokoodina (koodi 10.3) ja toiminta kuvana 10.5. Algorithm PrimJarnik(G): Input: painotettu yhdistetty graafi G, jossa on n solmua ja m kaarta Output: G:n minimivirittävä puu T Otetaan jokin solmu vg:stä D[v] 0 for jokaiselle solmulle u v do D[u] + alustetaan T alustetaan prioriteettijono Q, jonka avaimet ovat D:n arvot ja alkiot solmu kaari parit lisätään pari (v, ) Q:hun prioriteetilla D[v] Koodi 10.3. (alku) Prim Jarnik algoritmi minimivirittävän puun etsimiseksi. 10. luku 547
while Q do (u,e) Q.removeMinElement() lisätään solmu u ja kaari e puuhun T for jokaiselle solmun u vierekkäiselle solmulle z, joka on Q:ssa do {suoritetaan relaksaatio operaatio kaarelle (u,z)} if w((u,z)) < D[z] then D[z] w((u,z)) vaihdetaan z:aan liittyvä kaari Q:sta kaareksi (u,z) return puu T Koodi 10.3. (loppu) Prim Jarnik algoritmi minimivirittävän puun etsimiseksi. 10. luku 548
2704 1391 849 JFK 144 1235 DFW 1121 (a) Kuva 10.5. (alku) Prim Jarnik algoritmin toiminnasta esimerkki. 10. luku 549
2704 1391 849 JFK 144 1235 DFW 1121 (b) Kuva 10.5. (jatkoa) 10. luku 550
2704 1391 849 JFK 144 1235 DFW 1121 (c) Kuva 10.5. (jatkoa) 10. luku 551
2704 1391 849 JFK 144 1235 DFW 1121 (d) Kuva 10.5. (jatkoa) 10. luku 552
2704 1391 849 JFK 144 1235 DFW 1121 (e) Kuva 10.5. (jatkoa) 10. luku 553
2704 1391 849 JFK 144 1235 DFW 1121 (f) Kuva 10.5. (jatkoa) 10. luku 554
2704 1391 849 JFK 144 1235 DFW 1121 (g) Kuva 10.5. (jatkoa) 10. luku 555
2704 1391 849 JFK 144 1235 DFW 1121 (h) Kuva 10.5. (jatkoa) 10. luku 556
2704 1391 849 JFK 144 1235 DFW 1121 (i) Kuva 10.5. (jatkoa) 10. luku 557
2704 1391 849 JFK 144 1235 DFW 1121 (j) Kuva 10.5. (loppu) 10. luku 558
Algoritmin suoritusaika on samaa luokkaa kuin Kruskalin, mikä mainitaan perusteluitta. Tämä edellyttää prioriteetijonon toteutusta kekona. Lause 10.4. Olkoot yhdistetyssä painotetussa graafissa G solmujen määrä n ja kaarien määrä m. Prim Jarnik algoritmi löytää minimivirittävän puun G:lle ajassa O(m log n). Algoritmia on mahdollista edelleen tehostaa prioriteettijonoa tehostamalla, jolloin se toimii ajassa O(n log n + m) (kaarien määrä on siis yleensä olennaisesti suurempi kuin solmujen; vrt. lause 9.3.). 10. luku 559