6 Verkkoalgoritmeja. 6.1 Verkkojen esitystapoja

Samankaltaiset tiedostot
811312A Tietorakenteet ja algoritmit V Verkkojen algoritmeja Osa1 : Leveys- ja syvyyshaku

5 Verkkoalgoritmeja. 5.1 Verkkojen esitystapoja

811312A Tietorakenteet ja algoritmit V Verkkojen algoritmeja Osa 2 : Kruskalin ja Dijkstran algoritmit

811312A Tietorakenteet ja algoritmit, , Harjoitus 6, Ratkaisu

811312A Tietorakenteet ja algoritmit, , Harjoitus 6, Ratkaisu

13 Lyhimmät painotetut polut

Algoritmi on periaatteellisella tasolla seuraava:

811312A Tietorakenteet ja algoritmit Kertausta jälkiosasta

811312A Tietorakenteet ja algoritmit Kertausta jälkiosasta

58131 Tietorakenteet ja algoritmit (kevät 2014) Uusinta- ja erilliskoe, , vastauksia

10. Painotetut graafit

Olkoon seuraavaksi G 2 sellainen tasan n solmua sisältävä suunnattu verkko,

Algoritmit 1. Luento 9 Ti Timo Männikkö

verkkojen G ja H välinen isomorfismi. Nyt kuvaus f on bijektio, joka säilyttää kyseisissä verkoissa esiintyvät särmät, joten pari

Algoritmit 1. Luento 8 Ke Timo Männikkö

Pienin virittävä puu (minimum spanning tree)

= 5! 2 2!3! = = 10. Edelleen tästä joukosta voidaan valita kolme särmää yhteensä = 10! 3 3!7! = = 120

Oikeasta tosi-epätosi -väittämästä saa pisteen, ja hyvästä perustelusta toisen.

TIE Tietorakenteet ja algoritmit 261

v 8 v 9 v 5 C v 3 v 4

10. Painotetut graafit

V. V. Vazirani: Approximation Algorithms, luvut 3-4 Matti Kääriäinen

Algoritmit 1. Luento 13 Ma Timo Männikkö

58131 Tietorakenteet (kevät 2009) Harjoitus 11, ratkaisuja (Topi Musto)

Algoritmit 1. Luento 13 Ti Timo Männikkö

58131 Tietorakenteet ja algoritmit (kevät 2013) Kurssikoe 2, , vastauksia

TKT20001 Tietorakenteet ja algoritmit Erilliskoe , malliratkaisut (Jyrki Kivinen)

isomeerejä yhteensä yhdeksän kappaletta.

Valitaan alkio x 1 A B ja merkitään A 1 = A { x 1 }. Perinnöllisyyden nojalla A 1 I.

Algoritmit 2. Luento 11 Ti Timo Männikkö

Eräs keskeinen algoritmien suunnittelutekniikka on. Palauta ongelma johonkin tunnettuun verkko-ongelmaan.

Tarkennamme geneeristä painamiskorotusalgoritmia

A ja B pelaavat sarjan pelejä. Sarjan voittaja on se, joka ensin voittaa n peliä.

Graafit ja verkot. Joukko solmuja ja joukko järjestämättömiä solmupareja. eli haaroja. Joukko solmuja ja joukko järjestettyjä solmupareja eli kaaria

Näytetään nyt relaatioon liittyvien ekvivalenssiluokkien olevan verkon G lohkojen särmäjoukkoja. Olkoon siis f verkon G jokin särmä.

Algoritmit 2. Luento 2 To Timo Männikkö

Luku 7. Verkkoalgoritmit. 7.1 Määritelmiä

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.

Johdatus graafiteoriaan

Tehtävä 4 : 2. b a+1 (mod 3)

Johdatus graafiteoriaan

b) Olkoon G vähintään kaksi solmua sisältävä puu. Sallitaan verkon G olevan

811312A Tietorakenteet ja algoritmit , Harjoitus 2 ratkaisu

Algoritmit 2. Luento 2 Ke Timo Männikkö

A TIETORAKENTEET JA ALGORITMIT

Kurssikoe on maanantaina Muista ilmoittautua kokeeseen viimeistään 10 päivää ennen koetta! Ilmoittautumisohjeet löytyvät kurssin kotisivuilla.

Sekalaiset tehtävät, 11. syyskuuta 2005, sivu 1 / 13. Tehtäviä

1. (a) Seuraava algoritmi tutkii, onko jokin luku taulukossa monta kertaa:

58131 Tietorakenteet ja algoritmit Uusinta- ja erilliskoe ratkaisuja (Jyrki Kivinen)

Johdatus verkkoteoriaan 4. luento

Algoritmit 2. Luento 7 Ti Timo Männikkö

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

ja λ 2 = 2x 1r 0 x 2 + 2x 1r 0 x 2

Miten osoitetaan joukot samoiksi?

Tietorakenteet ja algoritmit. Verkot. Ari Korhonen

58131 Tietorakenteet ja algoritmit (syksy 2015) Toinen välikoe, malliratkaisut

811312A Tietorakenteet ja algoritmit, , Harjoitus 7, ratkaisu

Näin ollen saadaan tulos rad(g) diam(g). Toisaalta huomataan, että verkon G kaikilla solmuilla x ja y pätee kolmioepäyhtälön nojalla havainto

Tehtävä 10 : 1. Tehtävä 10 : 2

Tietorakenteet, laskuharjoitus 7, ratkaisuja

14. Luennon sisältö. Kuljetustehtävä. Verkkoteoria ja optimointi. esimerkki. verkkoteorian optimointitehtäviä verkon virittävä puu lyhimmät polut

Approbatur 3, demo 1, ratkaisut A sanoo: Vähintään yksi meistä on retku. Tehtävänä on päätellä, mitä tyyppiä A ja B ovat.

Algoritmit 2. Luento 9 Ti Timo Männikkö

Tehtävä 8 : 1. Tehtävä 8 : 2

4. Joukkojen käsittely

Lyhin kahden solmun välinen polku

58131 Tietorakenteet Erilliskoe , ratkaisuja (Jyrki Kivinen)

T : Max-flow / min-cut -ongelmat

Johdatus matemaattiseen päättelyyn

Algoritmit 1. Luento 7 Ti Timo Männikkö

Suunnatun verkon syklittömyyden testaaminen

Algoritmit 2. Luento 14 Ke Timo Männikkö

Demo 1: Simplex-menetelmä

Konsensusongelma hajautetuissa järjestelmissä. Niko Välimäki Hajautetut algoritmit -seminaari

1 sup- ja inf-esimerkkejä

Ratkaisu. Tulkitaan de Bruijnin jonon etsimiseksi aakkostossa S := {0, 1} sanapituudelle n = 4. Neljän pituisia sanoja on N = 2 n = 16 kpl.

missä on myös käytetty monisteen kaavaa 12. Pistä perustelut kohdilleen!

Algoritmit 2. Luento 13 Ti Timo Männikkö

Luonnollisen päättelyn luotettavuus

811312A Tietorakenteet ja algoritmit II Perustietorakenteet

1 + b t (i, j). Olkoon b t (i, j) todennäköisyys, että B t (i, j) = 1. Siis operaation access(j) odotusarvoinen kustannus ajanhetkellä t olisi.

Miten käydä läpi puun alkiot (traversal)?

A TIETORAKENTEET JA ALGORITMIT

Algoritmit 1. Luento 5 Ti Timo Männikkö

AVL-puut. eräs tapa tasapainottaa binäärihakupuu siten, että korkeus on O(log n) kun puussa on n avainta

Silmukkaoptimoinnista

Esimerkkejä polynomisista ja ei-polynomisista ongelmista

Lineaarikombinaatio, lineaarinen riippuvuus/riippumattomuus

Harjoitus 3 ( )

Harjoitus 3 ( )

1 Lineaariavaruus eli Vektoriavaruus

Algoritmit 2. Luento 12 Ke Timo Männikkö

Fibonacci-kasoilla voidaan toteuttaa samat operaatiot kuin binomikasoilla.

j(j 1) = n(n2 1) 3 + (k + 1)k = (k + 1)(k2 k + 3k) 3 = (k + 1)(k2 + 2k + 1 1)

Johdatus lukuteoriaan Harjoitus 2 syksy 2008 Eemeli Blåsten. Ratkaisuehdotelma

Datatähti 2019 loppu

Diskreetin matematiikan perusteet Laskuharjoitus 2 / vko 9

Algoritmit 2. Luento 13 Ti Timo Männikkö

Alaraja vertailuihin perustuvalle järjestämiselle

Injektio. Funktiota sanotaan injektioksi, mikäli lähtöjoukon eri alkiot kuvautuvat maalijoukon eri alkioille. Esim.

Transkriptio:

6 Verkkoalgoritmeja Tässä kappaleessa esitellään verkkoalgoritmien perusidea. Verkkoalgoritmit ovat yleisimpiä algoritmityyppejä. Yksi neuvo algoritmia suunniteltaessa onkin muuntaa tarkasteltava ongelma verkkojen ongelmaksi, jolloin voidaan päästä soveltamaan jotakin tunnettua verkkoalgoritmia ongelman ratkaisuun. Aluksi esitellään hieman verkon esittämistapoja, minkä jälkeen esitellään leveys-ja syvyyshaku verkossa. Tämän jälkeen esitellään edistyneitä verkkojen algoritmeja, kuten Dijkstran algoritmi lyhimmän polun löytämiseksi sekä Kruskalin algoritmi. Pääasiallinen lähde on Cormenin kirjan [Cor] luvut 23 ja 24. 6.1 Verkkojen esitystapoja Palautetaan aluksi mieleen, että verkko (graafi, graph) on pari G = (V, E), missä V on solmujen (vertices) epätyhjä joukko ja E on välien (edges) joukko, joka on relaatio solmujen joukossa. Joukko E siis määrittelee, mitkä solmut ovat verkossa kytketty toisiinsa. Mikäli verkon väleille annetaan painoarvoja, saadaan painotettu verkko. Tällöin joukon E jokaiseen väliin liitetään painoarvo. Verkon esittämiseen käytetään tavallisesti joko matriisi- tai vieruslistaesitystä. Matriisiesityksessä oletetaan solmujen joukon olevan numeroitu arvoilla 1, 2,..., N, missä N on solmujen lukumäärä. Verkkoa G = (V, E) esittävä matriisi on NxN- matriisi A, jonka paikassa (i, j) on arvo 1, jos (i, j) on verkon väli, muuten paikassa (i, j) on arvo 0 eli { 1 jos (i, j) E, a ij = 0 muutoin. Esimerkiksi verkon 1 2 5 3 4 Kuva 6.1: Esimerkki suunnatusta verkosta matriisiesitys on 0 1 0 0 0 0 0 0 1 1 0 1 0 0 1 0 0 1 0 0 0 0 0 1 0 1

Mikäli halutaan esittää painotettua verkkoa, arvon 1 paikalla matriisissa on välin painoarvo Olkoon edelleen N verkon G = (V, E) solmujen lukumäärä. Vieruslistaesityksessä käytetään taulukkoa Adj, jossa on N linkitettyä listaa. Kun u on solmu, listassa Adj[u] ovat solmun vierustoverit verkossa (ts. solmu v on listassa jos ja vain jos (u, v) on verkon väli). Edellä annetun verkon vieruslistaesitys on esitetty kuvassa 6.2. 1 2 / 2 4 5 / 3 2 5 / 4 3 / 5 4 / Kuva 6.2: Kuvan 6.1 suunnatun verkon vieruslistaesitys Myös vieruslistalla voidaan esittää painotettuja verkkoja liittämällä listaan välin painoarvo. Käyttötarkoituksesta riippuu, kumpaa esitystapaa kannattaa tietokoneohjelmassa käyttää. 6.2 Leveyshaku Leveyshaku on yksinkertaisimpia menetelmiä käydä verkon solmut systemaattisesti läpi. Leveyshakua voidaan käyttää perustana monenlaisille verkkoalgoritmeille. Olkoon G = (V, E) verkko. Leveyshaussa valitaan jokin solmu s ja käydään välejä systemaattisesti läpi, kunnes kohdataan kaikki solmut, joihin solmusta s voidaan päästä. Samalla lasketaan kunkin solmun etäisyys (etäisyys on tässä pienin mahdollinen määrä välejä) solmusta s. Leveyshaun nimi johtuu siitä, että algoritmi laajentaa löydettävien solmujen rintamaa tasaisesti koko rintaman leveydeltä. Toisin sanoen ensin löydetään kaikki etäisyydellä 1 olevat solmut, sitten etäisyydellä 2 olevat solmut jne. Algoritmissa käytetään hyväksi (FIFO-tyyppistä) jonoa Q. Tyhjää jonoa merkitää symbolilla EMPTY, solmun u jonoon lisääminen tapahtuu funktion EN QU EU E(Q, u) kutsulla ja funktion DEQU EU E(Q) kutsu poistaa jonosta solmun ja palauttaa sen. Symboli INF tarkoittaa ääretöntä lukua, joka on suurempi kuin mikä tahansa positiivinen luku. Käytännössä INF voi olla esimerkiksi -1. Algoritmissa päivitetään kahta taulukkoa d ja π, joissa kummassakin on paikka jokaista verkon solmua kohti. Taulukkoon d päivitetään kunkin solmun etäisyys annetusta syötesolmusta ja taulukkoon π solmun edeltäjä jossakin syötesolmusta lähtevästä lyhimmässä polussa. Näin ollen algoritmin suorituksen jälkeen voidaan taulukosta lukea yksi (mahdollisesti monista) lyhimmistä poluista mihin tahansa solmuun. Apuna käytetään lisäksi taulukkoa color, johon solmut merkitään valkoisiksi, harmaiksi tai mustiksi (WHITE,GRAY, BLACK). Jos solmu on valkea, sitä ei ole vielä löydetty. Mikäli solmu on musta, sen kaikki naapurit on jo löydetty ja ovat joko mustia tai harmaita. Sen sijaan harmaa solmu on löydetty, mutta sillä voi olla löytämättömiä naapureita (jotka ovat siis valkoisia). Näin ollen harmaat solmut esittävät löydettyjen ja löytämättömien solmujen rajapintaa. Alla olevassa algoritmissa oletetaan käytettävän vieruslistaesitystä verkolle, mutta se on helposti muutettavissa käyttämään matriisiesitystä. 2

Input: Verkko G = (V, E) ja sen solmu s. Verkolle oletetaan käytettävän vieruslistaesitystä. Output: Olkoon N solmujen lukumäärä. N-paikkaiset taulukot π,d ja color indeksoidaan verkon solmuilla. Taulukkoon d lasketaan solmun etäisyys syötesolmusta s. Arvo INF tarkoittaa, ettei solmuun ole polkua. Taulukkoon π lasketaan solmun edeltäjäsolmu jossakin lyhimmässä polussa syötesolmusta s. Arvo NIL tarkoittaa, ettei polkua ole. BFS(G,s) 1. for each u in V do 2. color[u] = WHITE 3. d[u] = INF 4. π[u] = NIL 5. d[s] = 0 6. color[s] = GRAY 7. π[s] = NIL 8. Q = EMPTY 9. ENQUEUE(Q,s) 10. while Q!= EMPTY do 11. u = DEQUEUE(Q) 12. for each v in Adj[u] do 13. if color[v] == WHITE then 14. color[v] = GRAY 15. d[v] = d[u]+1 16. π[v] = u 17. ENQUEUE(Q,v) 18. color[u] = BLACK Analysoidaan algoritmia hieman. Huomaa, että riveillä 1-4 päivitetään taulukot niin, että jokainen solmu on valkoinen, etäisyystaulukon kaikki arvot ovat äärettömiä ja edeltäjätaulukon arvot tyhjiä. Sitten syötesolmun etäisyys päivitetään nollaksi ja solmu väritetään harmaaksi, koska se on nyt löydetty (rivit 5-6). Tämän jälkeen luodaan tyhjä jono ja sijoitetaan syötesolmu jonoon (rivit 8-9). While-silmukassa otetaan jonon keulasta solmu ja tutkitaan sen naapurit: valkoiset naapurit löydetään nyt: ne väritetään harmaiksi, päivitetään niitä vastaavat etäisyys- ja edeltäjämatriisin paikat sekä sijoitetaan ne jonon häntään (rivit 14-17). Rivillä 18 väritetään jonosta otettu solmu mustaksi: sen kaikki naapurit on jo käsitelty. Jonoon sijoitetaan siis ainoastaan harmaita solmuja eli jonossa on vain löydettyjen ja löytämättömien solmujen välissä sijaitsevia solmuja. Tutkitaan algoritmin suoritusaikaa: Oletetaan, että verkossa on N solmua ja M väliä. Rivien 1-4 for-silmukka käy kaikki solmut kerran läpi, joten silmukan suoritusaika on lineaarinen solmujen lukumäärän N suhteen (tarvitaan operaatiota). Koska yksi solmu voidaan sijoittaa jonoon ainoastaan kerran (alunperin valkea solmu väritetään harmaaksi ja laitetaan jonoon; muun värisiä solmuja ei jonoon laiteta), rivejä 9 ja 17 toistetaan korkeintaan N kertaa. Näin ollen niiden suoritusaika on korkeintaan 2N. Lisäksi for- silmukassa riveillä 12-17 käydään algoritmin suorituksen aikana korkeintaan kerran läpi kukin verkon väli, joten näiden rivien suorittamiseen kuluu korkeintaan 6M operaatiota. Näin saadaan koko algoritmin maksimaaliselle suoritusajalle yläraja 5N + 6M, mikä on vähemmän kuin 6(N + M). Näin saadaan algoritmin kompleksisuusluokaksi O(N + M). Verkon rakenteesta riippuu, kuinka monta operaatiota vähintään joudutaan suorittamaan. Jos esimerkiksi lähtösolmu s ei ole yhteydessä mihinkään muuhun solmuun, while-silmukkaa tarvitsee suorittaa vain kerran. Näin ollen algoritmin kompleksisuusluokka ilmoitetaan ylärajana O-merkinnällä. Seuraava esimerkki havainnollistaa algoritmin toimintaa. 3

Kuva 6.3: Leveyshakualgoritmin toiminta suuntaamattomassa verkossa, kun lähtösolmuna on b. Solmuun päivitetään etäisyys d[b] algoritmin edetessä. Q on jono while-silmukan alussa. Leveyshaun oikeellisuus: Perustellaan, että algoritmi toimii oikein. Pitäisi osoittaa, että algoritmin suorituksen jälkeen kaikki solmusta s saavutettavat solmut löydetään ja taulukon d arvo d[v] on solmun v etäisyys solmusta s (merkitään δ(s, v)). Lisäksi, jos v s, ja v voidaan saavuttaa solmusta s, niin jokin lyhimmistä poluista solmusta s solmuun v on polku solmusta s solmuun π[v] ja väli (π[v], v). Todetaan ensin, että kun v on verkon solmu, millä tahansa hetkellä algoritmin suoritusta pätee d[v] δ(s, v). Aluksi nimittäin d[s] = 0 = δ(s, s) ja kaikilla muilla solmuilla v arvo d[v] =INF δ(s, v). Jokaisella while-silmukan kierroksella otetaan jonosta solmu u. Jos tälle solmulle pätee d[u] δ(s, u), niin sen vielä käsittelemättömille naapureille v saadaan d[v] = d[u] + 1 δ(s, u) + 1 δ(s, v), koska (u, v) on väli ja siten solmusta u päästään solmuun v yhdellä askeleella. Koska väite on alussa voimassa jonon ainoalle solmulle s ja koska se pysyy voimassa jokaisella kierroksella (ja siten kaikilla jonoon sijoitettavilla solmuilla), niin se on voimassa koko algoritmin suorituksen ajan. Seuraavaksi näytetään, että algoritmin suorituksen jälkeen itse asiassa d[v] = δ(s, v). Oletetaan, että on olemassa solmuja, joille d[v] δ(s, v). Valitaan solmuksi v niistä 4

se, jonka etäisyys solmusta s (siis δ(s, v)) on pienin. Koska d[v] δ(s, v), on oltava d[v] > δ(s, v). On siis ainakin olemassa polku solmusta s solmuun v, sillä muuten δ(s, v) = INF d[v]. Olkoon nyt u solmu, joka edeltää solmua v lyhimmässä polussa solmusta s solmuun v. Silloin δ(s, v) = δ(s, u) + 1 ja koska δ(s, u) < δ(s, v), on solmun v valinnan takia d[u] = δ(s, u). Tällöin olisi d[v] > δ(s, v) = δ(s, u) + 1 = d[u] + 1. (6.1) Tutkitaan nyt,mitä tapahtuu, kun solmu u otetaan algoritmissa jonosta. Tällöin solmu v voi olla valkea, musta tai harmaa. Jos solmu olisi valkea, taulukon d arvoksi d[v] tulisi d[u] + 1, mikä on ristiriidassa epäyhtälön (6.1) kanssa. Jos v olisi musta, se on otettu jonosta ennen solmua u ja d[v] < d[u], mikä ei myöskään ole mahdollista epäyhtälön (6.1) takia. Siis v on harmaa ja se on väritetty harmaaksi otettaessa jonosta jotakin solmua w. Siksi d[v] = d[w] + 1 ja koska w on otettu jonosta ennen solmua u, on myös d[w] d[u], joten d[v] d[u] + 1, mikä sekään ei käy epäyhtälön (6.1) vuoksi. Siksi kaikille solmuille v on oltava d[v] = δ(s, v). Tämä osoittaa myös, että kaikki solmusta s saavutettavat solmut löydetään, sillä muuten jollekin solmulle v olisi d[v] = INF, vaikka δ(s, v) on äärellinen. Todetaan vielä, että kun v on jokin saavutettava solmu ja π[v] = u, niin d[v] = d[u] + 1. Siksi lyhin polku solmusta s solmuun v saadaan ottamalla lyhin polku solmusta s solmuun u ja lisäksi väli (u, v). Edellä perusteltiin, että algoritmi toimii. Seuraavaksi tutustutaan sen käyttötapoihin. Kaikkein luonnollisin sovellus on hakea verkosta G lyhin polku kahden solmun välillä. Olkoot nämä solmut s ja v. Tällöin ajetaan ensin algoritmi BF S(G, s) ja käytetään algoritmin edeltäjätaulukkoa π hyväksi seuraavassa algoritmissa: Input: Verkko G = (V, E) ja sen solmut s ja v. Algoritmissa oletetaan ensin ajettavan BF S(G, s), jonka tuottamaa taulukkoa π käytetään. Input: Lyhin polku solmusta s solmuun v tai ilmoitus siitä, että polkua ei ole. Huomaa, että verkossa voi olla monia lyhimpiä polkuja. Algoritmi tulostaa jonkin niistä. BFS_POLKU(G,s,v) 1. if v==s then 2. tulosta s 3. else 4. if π[v] == NIL then 5. tulosta *Ei polkua solmusta s solmuun v* 6. else 7. BFS_POLKU(G,s,π[v]) 8. tulosta v Algoritmi toimii polun pituuden suhteen lineaarisessa ajassa, koska yksi kutsu tekee yhden tulostusoperaation ja kutsuu algoritmia itseään yhtä askelta lyhemmällä polulla. 6.2.1 Esimerkki: Lyhimmät polut Haetaan kuvan 6.3 esimerkkiverkossa lyhin polku solmusta b solmuun d. Tutkitaan, miten algoritmi täyttää edeltäjätaulukon π. Aluksi asetetaan π[a] = π[f] = b. Tämän jälkeen käsitellään solmu a, jolla ei ole valkeita naapurisolmuja ja edeltäjätaulukko ei muutu. Sitten käsitellään solmu f, jolla on kolme valkeaa naapuria e, c ja g. Näin asetetaan π[c] = π[e] = π[g] = f. Solmun e käsittely ei muuta edeltäjätaulukkoa, sen sijaan solmulla c on valkea naapuri d, joten asetetaan π[d] = c. Solmun g käsittely 5

ei taaskaan enää muuta edeltäjätaulukkoa. Nyt voidaan lukea polku: kun kutsutaan BF S P OLKU(G, b, d) saadaan kutsupino BFS_POLKU(G,b,d) BFS_POLKU(G,b,π[d]=c) BFS_POLKU(G,b,π[c]=f) BFS_POLKU(G,b,π[f]=b) Tulostetaan b Tulostetaan f Tulostetaan c Tulostetaan d Algoritmi siis tulostaa polun b, f, c, d, mikä onkin ainoa kolmen askeleen mittainen polku solmusta b solmuun d. Leveyshakualgoritmin avulla voidaan myös tutkia suuntaamattoman verkon yhtenäisyyttä. Suuntaamatonta verkkoa sanotaan yhtenäiseksi, jos mistä tahansa solmusta on polku mihin tahansa toiseen solmuun. Leveyshaulla voidaan helposti havaita, onko verkko yhtenäinen: Valitaan solmu s sattumanvaraisesti ja suoritetaan leveyshakualgoritmi. Jos algoritmin suorituksen jälkeen jokin solmu on yhä valkoinen, siihen ei ole polkua solmusta s ja verkko ei ole yhtenäinen. Muussa tapauksessa solmusta s pääsee kaikkiin solmuihin ja verkko on yhtenäinen. Jos verkko ei ole yhtenäinen, voidaan lähteä jostakin valkoiseksi jääneestä solmusta t ja tutkia, mitkä solmut eivät nyt jää valkoisiksi. Näin löydetään kaikki solmut, joihin on polku solmusta t. Mikäli vielä jää valkoisia solmuja, voidaan algoritmin suoritusta edelleen jatkaa valitsemalla jokin valkoinen solmu, kunnes valkeita solmuja ei enää ole jäljellä. Näin löydetään lopulta ns. verkon yhtenäiset komponentit (connected components) eli sellaiset mahdollisimman suuret solmujoukot, joiden sisällä kahden solmun välillä on verkossa polku. Kahden eri yhtenäisen komponentin solmun välillä ei koskaan voi olla polkua. Jos verkko on yhtenäinen, se koostuu yhdestä yhtenäisestä komponentista. 6.3 Syvyyshaku Tarkastellaan seuraavaksi toisenlaista hakustrategiaa verkossa. Leveyshaku keskittyi tutkimaan valitun solmun kaikki naapurit. Syvyyshaussa mennäänkin verkossa aina aluksi niin syvälle kuin päästään, ts. tarkistetaan, onko tutkittavalla solmulla s vielä tutkimattomia siitä lähteviä välejä. Kun kaikki välit on tutkittu, algoritmi perääntyy solmuun, josta solmu s löydettiin ja tutkii siitä lähtevät välit. Tätä jatketaan, kunnes lähtösolmusta saavutettavat solmut on kaikki tutkittu. Mikäli tutkimattomia solmuja vielä on, valitaan uusi lähtösolmu ja suoritetaan taas syvyyshaku. Näin saadaan lopulta kaikki verkon solmut läpikäytyä. Syvyyshaussa käytetään samankaltaista solmujen värittämistä ja edeltäjätaulukkoa kuin leveyshaussa, mutta tämän lisäksi pidetään yllä globaalia askelmuuttujaa, joka ilmaisee kunkin solmun löytämis- ja tarkastelun lopettamisajan. Nämä ajat tallennetaan aikaleimoina kahteen taulukkoon ja niiden avulla saadaan verkon rakenteesta tarkempaa tietoa kuin leveyshaulla. Tästä syystä syvyyshaulla on enemmän sovellustapoja, mutta niiden konstruoiminen vaatii myös hieman enemmän luovuutta ohjelmoijalta. Seuraavassa on syvyyshaun algoritmi. Rekursiivisena se käyttää implisiittisesti pinoa; algoritmi voitaisiin myös toteuttaa ilman rekursiota pinorakennetta käyttämällä. Sama algoritmi toimii sekä suunnatuille että suuntaamattomille verkoille. Tosin algoritmin lopputuloksen tulkinta on hieman erilainen. Tässä tarkastellaan enimmäkseen suunnattuja verkkoja. 6

Input: Verkko G = (V, E). Verkolle oletetaan käytettävän vieruslistaesitystä. Output: Olkoon N solmujen lukumäärä. N-paikkaiset taulukot π,d,f ja color indeksoidaan verkon solmuilla. Taulukkoon π lasketaan solmun edeltäjäsolmu syvyyshakupuussa. Taulukkoon d lasketaan solmun havaitsemisaika ja taulukkoon f solmun käsittelyn lopetusaika. DFS(G) 1. for each u in V 2. color[u] = WHITE 3. π [u]= NIL 4. time = 0 5. for each u in V 6. if color[u] == WHITE then 7. DFS_VISIT(u) DFS_VISIT(u) 1. color[u] = GRAY 2. time = time+1 3. d[u] = time 4. for each v in Adj[u] do 5. if color[v]==white then 6. π[v] = u 7. DFS_VISIT(v) 8. color[u] = BLACK 9. time = time+1 10. f[u] = time Samaan tapaan kuin leveyshaun kompleksisuusanalyysissä, huomataan että alkuoperaatiossa käydään solmut läpi silmukassa ja koska algoritmissa DFS VISIT käydään läpi valkoisia solmuja ja heti väritetään solmu harmaaksi, käydään verkon välit läpi tasan kerran. Erotuksena leveyshausta, jokainen solmu käydään läpi, joten myös välit käydään kaikki läpi. Näin ollen algoritmin kompleksisuusluokka on Θ(N + M). Siis myös operaatioiden vähimmäismäärälle saadaan arvio. Perehdytään algoritmin ominaisuuksiin tarkemmin. Jo pintapuolisella tarkastelulla on ilmeistä, että algoritmi käy läpi verkon kaikki solmut tasan kerran. Algoritmin avulla voi kuitenkin luokitella verkon välit ja saada näin tietoa verkon rakenteesta. Algoritmin suorituksen aikana konstruoidaan edeltäjätaulukko π. Taulukko täytetään siten, että solmuille u ja v pätee π[v] = u, jos ja vain jos (u, v) on verkon väli ja v havaitaan siinä vaiheessa, kun u on väriltään harmaa. Kun muodostetaan verkko, jonka välit ovat (π[u], u) kaikille niille solmuille u joilla on edeltäjä, saadaan puiden muodostama verkko. Jos solmu v on samassa puussa solmun u kanssa lisäksi solmusta u lähtevässä alipuussa, sanotaan, että u on solmun v edeltäjä ja v solmun u jälkeläinen. Tämä tapahtuu tarkalleen silloin, kun u on väriltään harmaa havaittaessa solmu v. Mainittua puiden muodostamaa verkkoa sanotaan syvyyssuuntaiseksi virittäväksi metsäksi (depth-first forest). Metsän tarkka rakenne riippuu siitä, missä järjestyksessä solmut on tallennettu vieruslistaan, mutta verkon rakennetta heijastaviin ominaisuuksiin tämä ei vaikuta. 6.3.1 Esimerkki: Syvyyssuuntainen virittävä metsä Kuvan 6.4 syvyyssuuntainen virittävä metsä koostuu kahdesta puusta a b e d ja c f. Verkon solmujen aikaleimoille pätee seuraava ominaisuus: Olkoot u ja v verkon G solmuja ja suoritetaan verkolle syvyyshaku. Silloin jokin seuraavasta kolmesta ehdosta 7

Kuva 6.4: Syvyyshaun eteneminen suunnatussa verkossa. Solmuihin päivitetään käsittelyn alku-ja loppuajan aikaleima. Puuvälit (ks. määritelmä tuonnempana) merkitään punaisella, muut välit merkitään pisteviivalla ja kirjaimella niiden tyypin mukaan: takautuvat välit merkitään kirjaimella B, etenevät välit kirjaimella F ja sivuttaisvälit kirjaimella C. on voimassa: 1. Välit [d[u], f[u]] ja [d[v], f[v]] ovat erilliset eikä u ole solmun v jälkeläinen eikä v solmun u jälkeläinen. 2. Väli [d[u], f[u]] sisältyy kokonaisuudessaan väliin [d[v], f[v]] ja u on solmun v jälkeläinen. 3. Väli [d[v], f[v]] sisältyy kokonaisuudessaan väliin [d[u], f[u]] ja v on solmun u jälkeläinen. Perustellaan väite. Olkoon aluksi d[u] < d[v]. Jos nyt d[v] < f[u], niin v havaitaan ennen kuin u on käsitelty loppuun, joten u on vielä harmaa, kun v löydetään. Näin ollen v on solmun u jälkeläinen. Lisäksi v havaitaan myöhemmin kuin u, joten siitä lähtevät välit tutkitaan ennen kuin solmun u käsittely loppuun. Siten täytyy olla f[v] < f[u]. Tässä tapauksessa siis väli [d[v], f[v]] sisältyy kokonaisuudessaan väliin [d[u], f[u]] ja v on solmun u jälkeläinen, eli ehto 3 on voimassa. Jos taas pätee d[v] > f[u], välit [d[u], f[u]] ja [d[v], f[v]] ovat erilliset. Lisäksi solmu u on jo käsitelty, kun v havaitaan, joten kumpikaan solmu ei voi olla toisen jälkeläinen. Tässä tapauksessa ehto 1 on siis voimassa. Lopuksi, jos d[v] < d[u], päätellään samalla tavoin kuin yllä, että joko ehto 2 tai 1 on voimassa. Päättely on aivan sama, ainoastaan solmujen u ja v osat vaihtuvat. Ylläolevasta voidaan heti päätellä, että solmu v on solmun u jälkeläinen jos ja vain jos d[u] < d[v] < f[v] < f[u]. 8

Kuva 6.5 havainnollistaa edellä esitettyä ominaisuutta kuvan 6.4 verkon tapauksessa. Kuva 6.5: Kuvan 6.4 verkon solmujen käsittelyajat. Puuvälit (ks. seuraava määritelmä) on merkitty nuolilla näkyviin. 6.3.2 Esimerkki: Välien luokittelusta Verkon rakennetta tutkittaessa, jaetaan verkon välit seuraaviin luokkiin: 1. Puuvälit (tree edges) ovat välejä syvyysmetsän puissa. Siten (u, v) on puuväli, jos solmu v havaittiin väliä (u, v) tutkimalla. 2. Takautuvat välit (back edges) ovat sellaisia verkon välejä (u, v), joissa u on solmun v jälkeläinen jossakin syvyysmetsän puussa. 3. Etenevät välit (forward edges) ovat sellaisia verkon välejä (u, v), joissa u on solmun v edeltäjä jossakin syvyysmetsän puussa, ja (u, v) ei ole puuväli. 4. Sivuttaisvälit (cross edges) ovat sellaisia verkon välejä, jotka eivät kuulu mihinkään luokista 1-3. 6.3.3 Esimerkki: Syvyyshaku syklien havainnoinnissa Kuvaan 6.4 on merkitty esimerkkiverkon syvyyshaussa syntyvät välityypit. Välit (a, b), (b, e), (e, d) ja (c, f) ovat puuvälejä. Väli (d, b) on takautuva väli, koska d on solmun b jälkeläinen syvyysmetsän puussa. Väli (a, d) on etenevä väli, koska se ei ole puuväli ja d on solmun a jälkeläinen syvyysmetsän puussa. Lopulta (c, e) on sivuttaisväli, koska se ei kuulu mihinkään kolmesta ensimmäisestä luokasta. Luokittelu on olennainen verkon rakenteen kannalta. Esimerkiksi usein on tutkittava, onko verkossa sykliä, eli polkua joka alkaa solmusta v ja päättyy samaan solmuun. Verkossa G on sykli tarkalleen silloin, kun syvyyshaku tuottaa vähintään yhden takautuvan välin. Jos nimittäin (u, v) on takautuva väli, on verkossa olemassa polku solmusta v solmuun u (solmu u on solmun v jälkeläinen puussa) ja koska nyt (u, v) on väli, saadaan sykli. Oletetaan toisaalta, että verkossa on sykli c = (v 1, v 2,..., v n ) ja että numerointi on valittu siten, että solmu v 1 havaitaan syvyyshaussa ensimmäiseksi. Tällöin, kun v 1 havaitaan, ovat kaikki muut syklin solmut vielä valkoisia. Olkoon v j ensimmäinen syklin solmu, joka ei ole solmun v 1 jälkeläinen. Siten solmu v j 1 on solmun v 1 jälkeläinen (voi myös olla v j 1 = v 1 ) ja silloin d[v 1 ] d[v j 1 ] < f[v j 1 ] f[v 1 ]. Koska (v j 1, v j ) on verkon väli, v j havaitaan ennen kuin v j 1 on käsitelty, ts. 9

d[v j ] < f[v j 1 ]. Kuitenkin d[v 1 ] < d[v j ], joten saadaan d[v 1 ] < d[v j ] < f[v 1 ]. Edellä annettujen kolmen ehdon perusteella on oltava d[v 1 ] < d[v j ] < f[v j ] < f[v 1 ] ja v j on sittenkin solmun v 1 jälkeläinen. Näin ollen kaikki polun solmut, myös v n, ovat solmun v 1 jälkeläisiä. Koska (v n, v 1 ) on verkon väli, se on takautuva väli. Yllä esitetyn perusteella voitaisiin syvyyshakualgoritmia muuttaa niin, että se havaitsee suunnatun verkon syklin. Takautuva väli voidaan nimittäin helposti havaita algoritmin suoritusvaiheessa. Väli (u, v) on takautuva, jos u on solmun v jälkeläinen. Näin ollen väliä ensimmäistä kertaa tutkittaessa solmun v on oltava harmaa, jolloin v on solmun u edeltäjä. Siten tarvitaan ainoastaan pieni muutos algoritmissa havaitsemaan syklin olemassaolo: Input: Verkko G = (V, E). Verkolle oletetaan käytettävän vieruslistaesitystä. Output: Olkoon N solmujen lukumäärä. N-paikkaiset taulukot π,d,f ja color indeksoidaan verkon solmuilla. Taulukkoon π lasketaan solmun edeltäjäsolmu syvyyshakupuussa. Taulukkoon d lasketaan solmun havaitsemisaika ja taulukkoon f solmun käsittelyn lopetusaika. Palauttaa arvon TRUE, jos verkossa on sykli ja arvon FALSE, jos sykliä ei ole. DFS_CYCLE(G) 1. for each u in V do 2. color[u] = WHITE 3. π[u] = NIL 4. time = 0 5. cycle = FALSE 6. for each u in V do 7. if color[u] == WHITE then 8. DFS_VISIT(u) 9. return cycle Syklittömässä suunnatussa verkossa voidaan suorittaa esimerkiksi topologinen järjestäminen. Syvyyshaun avulla voidaan etsiä suunnatun verkon vahvasti yhtenäiset komponentit (strongly connected components), mikä on yksi suunnattujen verkkojen tyypillisiä ongelmia. Vahvasti yhtenäinen komponentti suunnatussa verkossa on sellainen maksimaalinen solmujen joukko, että aina kun u ja v ovat joukon solmuja, on olemassa polku sekä solmusta u solmuun v että solmusta v solmuun u. Esimerkki: Vahvasti yhtenäiset komponentit Kuvan 6.4 esimerkkiverkossa vahvasti yhtenäiset komponentit ovat {a}, {b, d, e}, {c} ja {f}. Kun G on suunnattu verkko, merkitään symbolilla G T verkkoa, joka saadaan verkosta G kääntämällä verkon G välien suunnat. Tämä verkon vieruslistaesitys saadaan verkon G vieruslistaesityksestä helposti ajassa θ(n + M), kun N on verkon solmujen ja M verkon G välien lukumäärä. Vahvasti yhtenäiset komponentit voidaan löytää tekemällä kaksi syvyyssuuntaista hakua seuraavasti: 10

Syöte: Suunnattu verkko G=(V,E). Verkolle oletetaan käytettävän vieruslistaesitystä. Tulostus: Syöteverkon vahvasti yhtenäiset komponentit. SCC(G) 1. Suorita DF S(G), käytetään solmujen käsittelyajan päättymistaulukkoa f. 2. Muodosta G T 3. Suorita DF S(G T ), mutta pääsilmukassa (rivi 6) käsitellään solmut askelessa 1 saatujen f[u]:n arvojen suhteen vähenevässä järjestyksessä. 4. Askelessa 3 syntyvän virittävän metsän jokainen puu on verkon G vahvasti yhtenäinen komponentti. Koska algoritmissa tehdään kahdesti syvyyshaku, niin mikäli verkossa on N solmua ja M väliä, algoritmin suoritusaika on luokkaa θ(n + M). Osoitetaan vielä, että algoritmi toimii. Pitää siis näyttää, että solmut u ja v ovat verkossa G samassa vahvasti yhtenäisessä komponentissa tarkalleen silloin, kun ne ovat algoritmin 4 tuottaman virittävän metsän samassa puussa. Seuraavassa havaitsemisajat d ja lopetusajat f tarkoittavat verkon G (eivätkä verkon G T ) syvyyshaun tuloksia. Tehdään ensin havainto 1: Olkoot u ja v verkon eri solmuja. Jos syvyyshakualgoritmin havaitessa solmun u, solmusta u on solmuun v polku, jonka kaikki solmut ovat valkeita, niin silloin saadaan d[u] < d[v] < f[v] < f[u] (ja v on solmun u jälkeläinen jossain puussa). Jos nimittäin (u, v) on verkon väli, niin solmusta u tulee solmun v edeltäjä (koska v on valkea havaittaessa u) ja d[u] < d[v] < f[v] < f[u]. Oletetaan, että ominaisuus on voimassa kaikille lyhemmille poluille kuin polku u v. Olkoon w solmun v edeltäjä tässä polussa, jolloin oletuksen mukaan d[u] < d[w] < f[w] < f[u]. Koska (w, v) on verkon väli, solmu v löytyy ennen kuin solmun w käsittely loppuu, ts. d[v] < f[w]. Toisaalta tietysti pätee d[u] < d[v], koska v on valkea havaittaessa u. Siten saadaan d[u] < d[v] < f[w] < f[u] ja aiemmin johdettujen ehtojen nojalla on voimassa d[u] < d[v] < f[v] < f[u]. Oletetaan ensin, että u ja v ovat verkossa G samassa vahvasti yhtenäisessä komponentissa. Silloin verkossa G on polku solmusta u solmuun v ja päinvastoin. Siksi myös käännetyssä verkossa G T on polku solmusta u solmuun v ja päinvastoin. Tällöin u ja v päätyvät samaan puuhun. Olkoon nimittäin x polun u v u solmu, joka havaitaan ensin. Silloin havaittaessa x, muut polun solmut ovat valkeita ja havainnon 1 perusteella solmuista u ja v tulee solmun x jälkeläisiä puussa, johon x sijoittuu. Oletetaan seuraavaksi, että solmut u ja v ovat algoritmin SCC tuottaman virittävän metsän samassa puussa ja olkoon tämän puun juuri x. Siten käännetyssä verkossa G T on polku solmusta x solmuihin u ja v ja näin ollen verkossa G on polku solmusta u solmuun x ja solmusta v solmuun x. Siksi riittää näyttää, että verkossa G on polku solmusta x solmuihin u ja v. Tutkitaan ensin solmua u. Jos u = x, niin asia on triviaali. Merkitään verkon G T polkua solmusta x solmuun u seuraavasti: x = v 1 v 2... v n = u. Tällöin verkossa G on polku u = v n... v 2 v 1 = x solmusta u solmuun x. Tässä f[x] > f[v i ], aina, kun i = 2,..., n, koska solmun x käsittely verkossa G T alkaa ennen solmun v i käsittelyä. Tutkitaan nyt solmun u havaitsemisaikaa d[u]. Solmu x ei voi olla musta, kun u havaitaan, koska f[x] > f[u]. Oletetaan, että x on valkoinen, kun u havaitaan. Tällöin mikään v i ei voi olla musta, koska f[x] > f[v i ]. Olkoon jokin solmu v i on harmaa ja polussa sen jälkeen tulevat solmut valkoisia. Tällöin havainnon 1 perusteella seuraa d[v i ] < d[x] < f[x] < f[v i ] mikä ei taaskaan ole mahdollista. Näin ollen solmu x on harmaa havaittaessa u, ja tällöin pätee d[x] < d[u] < f[u] < f[x]. Siten u on solmun x jälkeläinen ja verkossa G on polku 11

solmusta x solmuun u. Samalla tavalla näytetään, että verkossa G on polku solmusta x solmuun v. Näin ollen algoritmi toimii. 6.3.4 Esimerkki: Käännetyn verkon vieruslistaesitys Suoritetaan algoritmi kuvan 6.4 esimerkkiverkolle. Solmujen käsittelyn päättymisajat ovat: f[a] = 8, f[b] = 7, f[c] = 12, f[d] = 5, f[e] = 6, f[f] = 11. Käännetyn verkon G T vieruslistaesitys on a / b d / c / d a e / e c b / f c / Kuva 6.6: Käännetyn verkon vieruslistaesitys. Ensin käsitellään solmua c, jolla ei ole naapureita. Tämän jälkeen siirrytään solmuun f, jonka ainut naapuri on c. Tämä solmu on jo käsitelty, joten tästäkään ei tule edeltäjätaulukkoon merkintää. Sitten siirrytään käsittelemään solmua a, jolla ei myöskään ole naapureita, ei vieläkään merkintöjä edeltäjätaulukkoon. Nyt mennään solmuun b, jonka naapuri on d. Tässä π[d] = b ja sitten siirrytään solmun d naapuriin a, joka on jo musta, ei merkintää edeltäjätaulukkoon. Seuraavaksi käsitellään solmun d naapuri e ja merkitään π[e] = d. Solmun e naapurit c ja b eivät enää ole valkoisia, joten edeltäjätaulukkoon ei tehdä merkintöjä ja algoritmi päättyy. Näin saatiin virittävään metsään neljä puuta a, b d e, c ja f. Siis vahvasti yhtenäiset komponentit ovat {a}, {b, d, e}, {c} ja {f}. Syvyyshaku suuntaamattomissa verkoissa toimii samaan tapaan kuin suunnatuissakin. Välien luokittelua on hieman tulkittava, koska suuntaamattomassa verkossa väli (u, v) ja (v, u) samaistetaan. Näin syvyyshaussa välin luokaksi annetaan se, joka tehdään, kun väliä tarkastellaan ensimmäistä kertaa, olipa kyseessä vali (u, v) tai (v, u). Tällä menetelmällä suunnatun verkon välit ovat kaikki joko puuvälejä tai takautuvia välejä. Suuntaamattomassa verkossa syvyyssuuntaisen virittävän metsän puut antavat verkon yhtenäiset komponentit. 6.4 Kruskalin algoritmi 6.4.1 Pienin virittävä puu Tässä kappaleessa oletetaan, että puu on suuntaamaton verkko, joka on yhtenäinen eli minkä tahansa kahden solmun välillä on polku ja syklitön eli minkä tahansa kahden solmun välillä on korkeintaan yksi yksinkertainen polku. Yhtenäisen suuntaamattoman verkon G = (V, E) virittävä puu on mikä tahansa puu (V, T ), missä T E. Virittävä puu saadaan valitsemalla joukosta E mahdollisimman vähän välejä niin, että verkko pysyy yhtenäisenä. Virittävän puun välien lukumäärä on aina T = V 1. Kuvassa 6.7 on suuntamaton verkko ja kaikki sen virittävät puut. 12

Kuva 6.7: Suuntaamaton verkko ja sen virittävät puut Kuva 6.8: Pienin virittävä puu yhtenäiselle verkolle. Jokaisen välin paino on merkattu välin viereen ja pienin virittävä puu, jonka koko paino on 37, on piirretty paksummalla punaisella viivalla. Kuvassa esitetty pienin puu ei ole ainoa pienin virittävä puu, koska korvaamalla väli (b, c) välillä (a, h) saadaan toinen pienin virittävä puu, jonka paino on 37. Pienin virittävä puu on virittävä puu, jonka paino w(t ) = Σ (u,v) T w(u, v) on pienin mahdollinen eli toisin sanoen minimaalisessa virittävässä puussa välien painojen summa on mahdollisimman pieni. Kuvassa 6.8 esitetään pienin virittävä puu yhtenäiselle verkolle. Seuraava perusalgoritmi löytää pienimmän virittävän puun: GENERIC-MST(G,w) 1. A = 0 2. while A does not form a spanning tree do 3. find an edge (u,v) that is safe for A 4. A = A {(u,v)} 5. return A Algoritmi käsittelee välien joukkoa A, joka on jokaisen while-silmukan kierroksen alussa jonkin pienimmän virittävän puun osajoukko (algoritmin silmukkainvariantti). Turvallinen (safe) väli on väli (u, v), joka säilyttää tämän invariantin s.e. A {(u, v)} on myös aina pienimmän virittävän puun osajoukko. Algoritmin oikeellisuus: Alustus: Rivillä 1, joukko A toteuttaa triviaalisti silmukkainvariantin. Ylläpito: Riveillä 2-4, invariantti säilyy, koska välien joukkoon lisätään vain turvallisia (safe) välejä. Lopetus: Kaikki välit, jotka on lisätty joukkoon A ovat pienimmässä virittävässä puussa, joten joukon A, joka palautetaan rivillä 5, on oltava pienin virittävä puu. 13

Voidaan osoittaa, että turvallinen väli voidaan aina löytää. Suuntaamattoman verkon G = (V, E) joukon V ositusta (S, V S) kutsutaan termillä leikkaus (cut). Sanomme, että väli (u, v) E ylittää (crosses) leikkauksen (S, V S), jos toinen sen päätepisteistä kuuluu joukkoon S ja toinen joukkoon V S. Lisäksi, leikkaus kunnioittaa (respects) välien joukkoa A, jos mikään joukon A väli ei ylitä leikkausta. Leikkausta ylittävä väli on kevyt väli (light edge), jos sen paino on kaikkien leikkauksen ylittävien välien painoista pienin. Kuvassa 6.9 esitellään leikkaus. Lause ([Cor]23.1): Olkoon G = (V, E) yhtenäinen, suuntaamaton graafi, jolla on reaaliarvoinen painofunktio w välien joukossa. Olkoon A joukon E osajoukko jonkin pienimmän virittävän puun osajoukko. Olkoon (S, V S) graafin G mikä tahansa leikkaus, joka kunnioittaa joukkoa A. Olkoon (u, v) leikkauksen (S, V S) kevyt väli. Tällöin, väli (u, v) on turvallinen joukolle A. Kuva 6.9: Kaksi erilaista tapaa kuvata leikkaus (S, V S) kuvan 6.8 graafille. Vasen kuva: Joukkoon S kuuluvat solmut on merkattu sinisellä ja joukkoon V S kuuluvat solmut on merkittu valkoisella. Välit, jotka ylittävät leikkauksen yhdistävät siniset ja valkoiset solmut. Väli (d, c) on ns. kevyt väli. Osajoukon A välit on merkattu kuviin punaisella paksummalla viivalla. Leikkaus (S, V S) kunnioittaa joukkoa A, koska mikään osajoukon A väli ei ylitä leikkausta. Oikea kuva: Sama graafi, mutta joukon S solmut kuvattu vasemmalla ja joukon V S solmut oikealla. Väli ylittää leikkauksen, jos se yhdistää vasemmalla puolella olevan solmun oikeanpuoleiseen. Alla kuvataan Kruskalin algoritmi pienimmän virittävän puun löytämiseksi. Kruskalin algoritmissa virittävän puun perusalgoritmiin lisätään ahne käsittely: kokeillaan aina painoltaan pienintä jäljellä olevaa väliä. Algoritmissa käytetään hyväksi operaatioita MAKE-SET(x) ja FIND-SET(x), joista ensimmäinen luo uuden joukon, jonka ainoa alkio on x ja jälkimmäinen palauttaa osoittimen joukkoon, joka sisältää alkion x. Operaatio UNION(x, y) yhdistää dynaamiset joukot, merk. S x ja S y, jotka sisältävät alkion x, että alkion y vastaavasti, ja tuhoaa yhdistämisen jälkeen joukot S x ja S y. 14

MST_KRUSKAL 1. A = EMPTY 2. for each vertex v in V do 3. MAKE-SET(v) 4. sort the edges of E into nondecreasing order by weight w 5. for each edge (u,v) in E, taken in nondecreasing order by weight do 6. if FIND-SET(u)!= FIND-SET(v) then 7. A = A {(u,v)} 8. UNION(u,v) Algoritmi etsii turvallisen välin lisättäväksi kasvatettavaan metsään. Turvallinen väli on aina graafin kahden erillisen komponentin yhdistävän pienimmän painon omaava väli. Aikavaatimus Kruskalin algoritmille riippuu siitä, miten operaatiot MAKE-SEToperaatiot, FIND-SET ja UNION on toteutettu. Oletetaan kirjan luvun 23.1. toteutukset, jolloin joukon A alustus vie vakioajan, ja välien järjestäminen suuruusjärjestykseen rivillä 4 vie O(E lg E) ajan. Rivien 5-8 for- silmukka suorittaa O(E) FIND-SET- ja UNION- operaatiota. Yhdessä MAKE-SET operaatioiden kanssa, joita tehdään V kappaletta, saadaan yhteensä O((V +E)α(V )) aikavaatimus, missä α on hyvin hitaasti kasvava funktio (määr. em. operaatioiden yhteydessä kirjan kappaleessa 21.4.). Koska graafi G on yhtenäinen, saadaan E V 1 ja operaatiot vievät ajan O(Eα(V )). Koska α( V ) = O(lg V ) = O(lg E), on Kruskalin algoritmin aikavaatimus luokkaa O(E lg E). Huomataan, että E < V 2, jolloin lg E = O(lg V ) ja siis aikavaatimus saadaan muotoon O(E lg V ). Usein virittävä puu tulee valmiiksi, ennen kuin kaikki välit on käsitelty. Tällöin algoritmia voidaan hieman tehostaa käyttämällä toteutuksessa kekoa sen sijaan, että heti järjestetään kaikki välit. 6.5 Dijkstran algoritmi Vuonna 1956 hollantilainen Edsger Dijkstra kehitteli miettiessään lyhyintä reittiä kahden pisteen välille rautatieverkossa, verkkoalgoritmin, joka ratkaisee keveimmän polun ongelman graafille ja tuottaa pienimmän virittävän puun polun. Algoritmia käytetään nykyään mm. älyliikenteessä, karttapalveluissa, reitittämisongelmien ratkaisuissa (tukiasemien välillä reititetään esim. korkeimman kaistanleveyden perusteella) tai vaikka tarttuvien tautien verkkopohjaisessa mallintamisessa ja leviämisennusteissa (solmut vastaavat ihmisiä ja välit heidän välisiä suhteitaan). Algoritmi on läheistä sukua leveyshaulle, jonka avulla voidaan löytää lyhin polku painottamattomalle (jokaisen välin paino on sama) verkolle. Määritellään ensin tässä kappaleessa käytettävät merkinnät. Polun p = v 0, v 1,..., v k paino on siihen liittyvien välien painojen summa: w(p) = k w(v i 1, v i ). i=1 Joissakin tapauksissa polun välin paino voi olla negatiivinen. Rajoitumme kuitenkin tarkastelemaan vain positiivisen painon omaavia verkkoja. Huomaa, että nyt kevein polku ei voi sisältää syklejä. Paino voi sovelluskentällä vastata esimerkiksi välien yhdistämien solmujen välimatkaa, kustannusta tälle välille, tai vaikka tiedonsiirtonopeutta. 15

Kuva 6.10: Kruskalin algoritmin suoritus graafille 6.8. Punaisella merkityt paksummat välit kuuluvat kasvatettavaan metsään A. Algoritmi käsittelee välit suuruusjärjestyksessä painon suhteen. Nuolella merkataan tarkasteltavaa väliä suorituksen eri vaiheissa. Jos väli yhdistää kaksi erillistä puuta metsässä, lisätään se metsään. Määritellään myös lyhyimmän polun paino solmusta u solmuun v seuraavasti { min{w(p) : u p v} jos on olemassa polku solmusta u solmuun v δ(u, v) = muutoin. (6.2) eli polun paino on polulla olevien välien painojen summa. Kevein polku solmusta u solmuun v on polku p, jonka paino w(p) = δ(u, v). Dijkstrassa etsitään kevein polku 16

yhdestä lähtösolmusta s jokaiseen solmuun v. Variaatioita tähän ongelmaan ovat: Etsitään lyhin polku valittuun päätepisteeseen jokaisesta verkon solmusta. Etsitään lyhin polku kahden valitun solmun välille. Etsitään lyhin polku valitusta solmuparista kaikkiin verkon solmupareihin. Dijkstran algoritmi säilöö joukon S solmuja, joiden lyhyimmän polun painot lähtösolmusta s on jo määritetty. Algoritmissa tallennetaan ensin kaikki solmut prioriteettijonoon, jossa avaimina eli prioriteetteina ovat etäisyysarvot d. Prioriteettijonosta poistetaan toistuvasti solmu u V S, jolla on pienin etäisyysestimaatti solmuun s ja lisätään se joukkoon S. Samalla päivitetään kaikkien sellaisten u:n vierekkäisten solmujen v etäisyydet, jotka eivät ole joukossa S. Tarkoitus on löytää uusi lyhyempi polku solmuun s kuin tähän asti tunnettu lyhin. Päivitysoperaatiosta käytetään Cormenin kirjassa termiä relaxation ja sillä tarkistetaan, voiko vanhaa estimaattia parantaa lähemmäs todellista arvoaan (minimietäisyyttä). 17

DIJKSTRA(G,w,s) 1. for each vertex v in V do 2. d[v] = 3. π[v] = NIL 4. d[s] = 0 5. S = EMPTY 6. Q = V[G] 7. while Q!= 0 do 8. u = EXTRACT-MIN(Q) 9. S = S {u} 10. for each vertex v in Adj[u] do 11. if d[v] > d[u] + w(u,v) then 12. d[v] = d[u] + w(u,v) 13. π[v] = u Riveillä 1-4 alustetaan lyhin polku lähtösolmusta s itseensä nollaksi ja muihin solmuihin äärettömäksi (etäisyysarvot d). Joukko S alustetaan tyhjäksi. Algoritmi säilyttää seuraavan invariantin: jokaisen while-silmukan kutsun alussa Q = V S (rivit 7-13). Rivillä 6 tallennetaan minimiprioriteettijonoon Q kaikki solmut, jotka kuuluvat joukkoon V ; koska tässä vaiheessa S = 0, invariantti on tosi rivin 6 jälkeen. Jokaisella while-silmukan kierroksella (rivit 7-13), solmu u poistetaan joukosta Q = V S ja lisätään joukkoon S. Ensimmäisellä kierroksella u = s. Tällöin invariantti on tosi ensimmäisellä kierroksella ja solmulla u on lyhin polku mihin tahansa solmuun joukossa V S. Algoritmi on ns. ahne algoritmi. Kuva 6.11: Dijkstran algoritmin toiminta. Lähtösolmu on vasemmanpuoleisin solmu. Lyhimmän polun estimaatit on merkattu solmun sisälle. Mustat solmut kuuluvat joukkoon S ja valkoiset solmut kuuluvat minimiprioriteettijonoon Q = V S. (a) Tilanne juuri ennen while-silmukan ensimmäistä iteraatiota. Tummennetulla solmulla on pienin d arvo ja se valitaan solmuksi u rivillä 8. (b)-(f) Tilanne jokaisen while- silmukan suorituksen jälkeen. Tummennettu solmu valitaan solmuksi u rivillä 8 seuraavalle kierrokselle. Arvot muuttujille d ja π saadaan kuvan (f) lopullisista arvoista. Dijkstra-algoritmin oikeellisuus: Teoreema Olkoon G = (V, E) painotettu, suunnattu verkko, jolla on ei-negatiivinen painofunktio w ja lähtösolmu s. Sovellettaessa Dijkstran algoritmia verkkoon G, päättyy ja tällöin d[u] = δ(s, u) kaikille solmuille u V. Todistus: Tarkastellaan seuraavaa silmukkainvarianttia: 18

Jokaisen while-silmukan (rivit 7-13) kierroksen alussa, d[v] = δ(s, v) jokaiselle solmulle v S. Riittää osoittaa, että jokaiselle solmulle u V, pätee d[u] = δ(s, u) sillä hetkellä, kun u on lisätty joukkoon S. Tämän jälkeen on vielä osoitettava, että yhtäsuuruus säilyy senkin jälkeen. Alustus: Aluksi S = 0, joten invariantti on tosi. Ylläpito: Haluamme osoittaa, että jokaisella silmukan kierroksella d[u] = δ(s, u) solmulle, joka on lisätty joukkoon S. Tehdään vastaoletus: Olkoon u ensimmäinen solmu, jolle d[u] δ(s, u), kun se on lisätty joukkoon S. Tarkastellaan sitä kohtaa whilesilmukan kierroksen alussa, jossa u lisätään joukkoon S ja tarkastellaan lyhintä polkua solmusta s solmuun u. Aluksi huomataan, että u s, koska jos u = s, pitäisi olla d[s] = δ(s, s) = 0, mikä olisi ristiriita. Koska u s, on myös S 0 juuri ennenkuin u on lisätty joukkoon S. Tällöin, on oltava joku polku solmujen s ja u välillä, koska muutoin olisi d[u] = δ(s, u) = (no-path property), joka olisi ristiriita, koska oletimme d[u] (s, u). Nyt koska solmujen s ja u välillä on oltava vähintään yksi polku, on näiden solmujen välillä on olemassa lyhin polku, merkitään p. Ennen solmun u lisäämistä joukkoon S, polku p yhdistää solmun (s) joukosta S solmuun (u) joukossa V S. Tarkastellaan ensimmäistä solmua y V S (eli joukon S ulkopuolella) polulla p. Olkoon x joukossa S ja olkoon se solmusta y edellinen solmu polulla p. Voimme siis jakaa polun p osiin seuraavasti: s p1 x y p2 u. Väitämme, että d[y] = δ(s, y), kun solmu u lisätään joukkoon S. On huomattava, että x S. Nyt muistetaan, että u on oletuksen mukaan ensimmäinen solmu, jolle d[u] δ(s, u), kun se lisätään joukkoon S. Tällöin d[x] = δ(s, x), kun x lisättiin joukkoon S. Huom! Dijkstra-algoritmissa esiintyvällä päivitysoperaatiolla relaxation on seuraava ominaisuus (convergence property): Jos s u v on lyhin polku joukossa G, joillekin u, v V ja jos d[u] = δ(s, u) ennenkuin väli (u, v) on päivitetty (relaxed), niin d[v] = δ(s, v) aina päivityksen jälkeen. Koska väli (x, y) päivitettiin (was relaxed), kun se oli lisätty joukkoon S, d[y] = δ(s, y) seuraa edellisen huomautuksen perusteella. Koska y esiintyy ennen solmua u lyhmmällä polulla solmusta s solmuun u ja kaikki välien painot ovat ei-negatiivisia (polulla p 2 ), on voimassa δ(s, y) δ(s, u), joten d[y] = δ(s, y) (6.3) δ(s, u) (6.4) d[u] (6.5) Viimeinen rivi on voimassa, koska aina d[v] δ(s, v) kaikille solmuille v V ja kun d[v] saavuttaa arvon δ(s, v), se ei enää muutu. Kun u valittiin prioriteettijonosta, olivat molemmat solmut u ja v joukossa V S, joten d[u] d[y]. Siten ym. yhtälöiden epäsuuruusmerkeistä voi poimia yhtäsuuruuden ja saadaan d[y] = δ(s, y) = δ(s, u) = d[u]. Täten, d[u] = δ(s, u), joka on ristiriidassa oletuksemme kanssa ja d[u] = δ(s, u), kun u lisättiin joukkoon S ja yhtäsuuruus on voimassa kaikilla silmukan iteraatioilla. Lopetus: Suorituksen lopussa Q = EM P T Y ja koska Q = V S, tarkoittaa se, että V = S. Täten, d[u] = δ(s, u) kaikille solmuille u V. 19

Voimme siis todeta, että algoritmi toimii oikein. Dijkstra-algoritmin suoritusajasta: Algoritmin suoritusaikaan vaikuttaa olennaisesti se, miten käytettävä minimiprioriteettijono on toteutettu. Suorituskyvyn suhteen avainasemassa ovat funktio EXTRACT-MIN ja sijoitus d[v] = d[u] + w(u, v). Mikäli EXT RACT MIN- funktio toteutetaan yksinkertaisella listan läpikäymisellä (vaativuus O(V )) saadaan algoritmin kokonaisvaativuudeksi O(V 2 ). Algoritmi voidaan toteuttaa kekoa käyttäen siten että sen kokonaisvaativuudeksi saadaan O((E + V )lg(v )). Vieläkin suorituskykyisempi toteutus saadaan käyttäen Fibonacci-kekoa jolloin vaativuudeksi saadaan O(V lg(v ) + E). Kummassakin tapauksessa kekoa täytyy päivittää aina kun tehdään sijoitus d[v] = d[u] + w(u, v). LÄHTEET: Cormen, T.H., Leiserson, C.E., Rivest, R.L., Stein, C. Introduction to Algorithms, 2nd edition, The MIT Press 2001. Dijkstra, E. W. (1959) A Note on Two Problems in Connection with Graphs. Numerische Mathematik, 1. 269-271. 20