Požrešna metoda
Transcription
Požrešna metoda
RAČUNALNIŠTVO IN INFORMACIJSKE TEHNOLOGIJE OSNOVE ALGORITMOV NIKOLA GUID Fakulteta za elektrotehniko, računalništvo in informatiko Maribor, 2011 Kazalo 3 Požrešna metoda 3.1 Splošna metoda . . . . . . . . 3.2 Preprosti problem nahrbtnika 3.3 Primov algoritem . . . . . . . 3.4 Dijkstrin algoritem . . . . . . 3.5 Bellman-Fordov algoritem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-1 3-1 3-2 3-4 3-9 3-18 Poglavje 3 Požrešna metoda Požrešna metoda (greedy method ) je gotovo najbolj neposredna metoda načrtovanja, ki jo obravnavamo. Večina problemov, ki jih rešujemo s to metodo, ima n vhodov in zahteva od nas, da določimo podmnožico, ki izpolnjuje določene omejitve. Kakršnikoli podmnožici, ki izpolnjuje določene omejitve, pravimo dopustna rešitev (feasible solution). Zahtevamo, da poiščemo tako dopustno rešitev, ki bodisi minimizira bodisi maksimizira dano kriterijsko funkcijo (objective function). Dopustna rešitev, ki optimizira kriterijsko funkcijo, je optimalna rešitev (optimal solution). 3.1 Splošna metoda Pri strategiji požrešna metoda rešitev gradimo postopoma. Na tekočem koraku poiščemo element, ki prinese največ h kriterijski funkciji. Sprejmemo ga samo, če s tem elementom razširjena množica ostane dopustna. Požrešno metodo lahko opišemo z naslednjim psevdokodom: POZRESNA(n, a, resitev) 1 resitev ← 0 2 for i ← 1 to n 3 do x ← izberi(n, a, resitev) % Izberi naslednji element, ki še ni % v rešitvi in pride na vrsto % po kriteriju optimalnosti. 4 if dopustna(x, resitev) 5 then resitev ← resitev ∪ x % Če je tekoči element dopusten x, ga vključimo % v celotno rešitev. Oblika procedur izberi in dopustna je odvisna od zgleda. 3-2 3.2 Preprosti problem nahrbtnika 3.2 Preprosti problem nahrbtnika Na razpolago imamo n predmetov. Za vsak predmet i (i = 1, 2, . . . , n) poznamo njegovo prostornino v[i] (0 < v[i] ≤ V ). Poznamo tudi vrednost predmeta c[i] (c[i] > 0). Predpostavimo, da predmete lahko poljubno režemo. x[i] naj predstavlja del predmeta i, ki ga odrežemo in damo v nahrbtnik (0 ≤ x[i] ≤ 1). V nahrbtnik s prostornino V želimo vstaviti deleže predmetov, tako da je izraz n X c[i]x[i] (3.1) v[i]x[i] ≤ V. (3.2) i=1 maksimalen pri pogojih n X i=1 Dopustna rešitev je kakršnakoli množica (x[1], . . . , x[n]), ki izpolnjuje pogoj 3.2. Optimalna rešitev je dopustna rešitev, pri kateri ima izraz 3.1 maksimalno vrednost. Nahrbtnik bo dosegel maksimalno vrednost, če bomo v nahrbtnik vlagali najprej predmete, ki imajo največjo vrednost glede na prostornino. To pomeni, da moramo urediti predmete po relativni vrednosti c[i]/v[i], tako da velja: c[i + 1] c[i] ≥ , v[i] v[i + 1] i = 1, 2, . . . , n − 1 (3.3) V začetku v nahrbtnik vlagamo cele predmete. Praviloma moramo odrezati samo zadnji predmet, ki ga še damo v nahrbtnik. Delovanje procedure kaže naslednji psevdokod: PREPROSTI-NAHRBTNIK(V, n,v,c,x) 1 for i ← 1 to n 2 do x[i] ← 0 % inicializiraj vrednosti x[i] 3 y←V % y je prostor, ki je še na voljo 4 for i ← 1 to n 5 do if v[i] > y % ali je rešitev dopustna? 6 then exit(for) 7 else x[i] ← 1 8 y ← y − v[i] 9 if i ≤ n 10 then x[i] ← y/v[i] % delež predmeta, ki napolni nahrbtnik Iz procedure vidimo, da rešitev x gradimo postopoma. Najprej določimo vrednost x[1], nato x[2], itd. 3.2 Preprosti problem nahrbtnika 3-3 Zgled 3.1. Imejmo tri predmete z naslednjimi vrednostmi: c = [10 14 20] in v = [4 7 5]. Prostornina nahrbtnika naj bo V = 8. Procedura PREPROSTI-NAHRBTNIK zahteva ureditev predmetov glede na vrednost na prostorsko enoto. Izračunajmo c/v = [2.5 2 4]. To pomeni, da tretji predmet postane prvi, prvi drugi in drugi tretji. Preuredimo vektor c in v: c = [20 10 14] in v = [5 4 7]. Opišimo delovanje procedure PREPROSTI-NAHRBTNIK: 1. Vrstici 1–2: x[1] = 0, x[2] = 0, x[3] = 0. 2. Vrstica 3: y = 8. 3. Vrstice 4–8: 1. iteracija zanke for: Ker je v[1] < y (5<8), dobimo x[1] = 1 in y = 8 − 5 = 3. 4. Vrstice 4–8: 2. iteracija zanke for: Ker je v[2] > y (4<3), izstopimo iz zanke for. 5. Vrstica 9: Ker je i ≤ n (2 ≤ 3), izračunamo delež drugega predmeta, ki ga postavimo v nahrbtnik: x[2] = 3/4 = 0.75. To pomeni, da je rešitev problema vektor x = [1 0.75 0]. ♦ Časovna zahtevnost procedure PREPROSTI-NAHRBTNIK Procedura PREPROSTI-NAHRBTNIK največ dela opravi z drugo zanko for, ki se izvede v najslabšem primeru n-krat. Zato je časovna zahtevnost v najslabšem primeru: T (n) = O(n). 3-4 3.3 Primov algoritem 3.3 Primov algoritem Imejmo n kontaktov, ki jih moramo povezati med sabo, tako da uporabimo n − 1 žičk. Vsaka žička povezuje dva kontakta. Naš cilj je, da porabimo čim manj žice. Vemo, da električna napetost pride do kontakta, če je le-ta povezan z eno žičko. V problemu ožičenja kontaktov lahko uporabimo model neusmerjenega grafa G = (V, E), kjer je V množica vozlišč (vertices) in E množica povezav (edges). Vozlišča ustrezajo kontaktom, žičke pa povezavam. 0 0 0 V danem grafu želimo najti aciklični podgraf G = (V, E ), tako da je E ⊆ E, 0 ki povezuje vsa vozlišča grafa G. Graf G imenujemo vpeto drevo (spanning tree). V danem grafu (slika 3.1a) je možno določiti veliko vpetih dreves. Slike 3.1b, 3.1c in 3.1d kažejo samo tri primere vpetega drevesa, teh pa je še več. Slika 3.1: a) Neusmerjeni graf, b) Vpeto drevo 1, c) Vpeto drevo 2, d) Vpeto drevo 3 Uvedimo tak graf G, v katerem je za vsako povezavo (u, v) ∈ E, pri čemer sta u in v vozlišči iz grafa G (u, v ∈ V ), dana vrednost c(u, v), ki predstavlja strošek (cost) povezave. Sedaj lahko določimo strošek vseh povezav v vpetem drevesu T : X c(u, v). (3.4) c(T ) = (u,v)∈T Nekateri avtorji [Cormen et al., 2007] uporabljajo izraz utež (weight) in oznako w(u, v). Drevo z minimalnim stroškom povezav c(T ) imenujemo minimalno vpeto drevo (minimum-spanning-tree), ki ga je mogoče določiti z različnimi algoritmi. Najprej bomo spoznali Primov algoritem. Minimalno vpeto drevo bomo gradili postopoma. Najprej bomo vključili v rešitev eno vejo drevesa, zatem drugo, itd. Tekoča podatkovna struktura je venomer drevo. Na vsakem koraku bomo našli eno vejo končnega minimalnega vpetega drevesa. Najprej izberemo povezavo z minimalno vrednostjo v grafu G. Označimo jo s (k, l), pri čemer sta k in l vozlišči grafa G. 3.3 Primov algoritem 3-5 K vsakemu vozlišču j, ki še ni vključeno v minimalno vpeto drevo, priredimo vrednost r[j], ki predstavlja indeks najbližjega vozlišča, ki je že v minimalnem vpetem drevesu. V drevo vključimo takšno vozlišče j, katerega povezava s poljubnim že vključenim vozliščem je minimalna (torej c(j, r[j])=min). Brž ko je novo vozlišče j vključeno v rešitev (oz. v drevo), postane vrednost indeksa r[j] = 0. To pomeni, da moramo pri vključitvi novega vozlišča j izpolniti dva pogoja: 1. r[j] 6= 0, kar pomeni, da vozlišče j še ni vključeno v drevo, in 2. c(j, r[j])=min, izbrati tako nevključeno vozlišče j, ki minimizira ta izraz. Zapišimo proceduro Primovega algoritma: PRIM(G, C, vr, T ) 1 izberi (k, l) kot povezavo, ki ima najmanjšo ceno c(u, v) (u, v ∈ V ) 2 (k, l) ∈ T % vključi povezavo (k, l) v rešitev 3 vr ← c(k, l) % izračunaj vrednost vpetega drevesa 4 for i ← 1 to n % zanka za določitev indeksov r[i] 5 do if c(i, l) < c(i, k) 6 then r[i] ← l 7 else r[i] ← k 8 r[k] = r[l] ← 0 % vozlišči k in l sta že vključeni v drevo T 9 for i ← 2 to n − 1 10 do poišči j, tak da je r[j] 6= 0 in c(j, r[j])=min 11 (j, r[j]) ∈ T % vključi povezavo (j, r[j]) v drevo T 12 vr ← vr + c(j, r[j]) % osveži vrednost vr vpetega drevesa 13 r[j] = 0 % spremeni indeks vključenosti za vozlišče j 14 for h ← 1 to n % zanka za osvežitev indeksov r[j] 15 do if r[h] 6= 0 and c(h, r[h]) > c(h, j) % stara povezava ima strošek večji od nove 16 then r[h] ← j Procedura PRIM(G, C, vr, T ) najde vse povezave minimalnega vpetega drevesa in vrednost drevesa. 3-6 3.3 Primov algoritem Zgled 3.2. Poglejmo kako deluje procedura PRIM na primeru grafa na sliki 3.2. 1 30 15 25 2 4 3 6 14 4 20 12 5 Slika 3.2: Primer neusmerjenega grafa Iz grafa na sliki 3.2 lahko zapišemo naslednjo 0 30 15 6 30 0 25 ∞ C= 15 25 0 14 6 ∞ 14 0 ∞ 4 20 12 matriko povezav: ∞ 4 20 12 0 1. Vrstica 1: Izberi povezavo (2, 5) kot povezavo, ki ima najmanjšo ceno c(i, j). 2. Vrstica 2: Vključi povezavo (2, 5) v drevo T (slika 3.3a). 3. Vrstica 3: Izračunaj vrednost vpetega drevesa (vr = 4). 4. Vrstice 4–7: Vsem vozliščem izračunaj indeks najbližjega vozlišča, ki je že v minimalnem vpetem drevesu. Ti so: r[1] = 2, r[2] = 2, r[3] = 5, r[4] = 5, r[5] = 5. 5. Vrstica 8: Vozliščema 2 in 5 postavimo indeks r na nič, saj sta obe vozlišči že vključeni v drevo T (r[2] = 0, r[5] = 0). 6. Vrstica 9: Vstop v prvo iteracijo druge zanke for. 7. Vrstica 10: Določi vozlišče j, ki ga vključimo v drevo T . Vključili bomo vozlišče 4, saj ima le-to c(j, r[j])=min. 8. Vrstica 11: Vključi povezavo (4, 5) v drevo T (slika 3.3b). 9. Vrstica 12: Osveži vrednost vr vpetega drevesa. Nova vrednost je: vr = 4 + 12 = 16. 3-7 3.3 Primov algoritem 2 2 4 4 4 12 5 5 a) b) 1 1 6 2 6 4 4 12 3 2 4 14 4 12 5 5 c) d) Slika 3.3: Gradnja minimalnega vpetega drevesa T : a) po izvršeni prvi zanki for, b) po izvršeni prvi iteraciji druge zanke for, c) po izvršeni drugi iteraciji druge zanke for, d) po izvršeni tretji iteraciji druge zanke for 10. Vrstica 13: Spremeni indeks vključenosti za vozlišče 4 (r[4] = 0). 11. Vrstice 14–16: Vstop v tretjo zanko for, ki osveži vrednosti nevključenim vozliščem. V našem primeru se spremeni r[1] na 4 (saj je c[1, 2] > c[1, 4] oz. 30 > 6) in r[3] na 4 (saj je c[3, 5] > c[3, 4] oz. 20 > 14). 12. Vrstica 9: Vstop v drugo iteracijo druge zanke for. 13. Vrstica 10: Določi vozlišče j, ki ga vključimo v rešitev. Od še nevključenih vozlišč ima vozlišče 1 minimalno vrednost izraza c(j, r[j]). 14. Vrstica 11: Vključi povezavo (1, 4) v drevo T (slika 3.3c). 15. Vrstica 12: Osveži vrednost vpetega drevesa. Nova vrednost je: vr = 16 + 6 = 22. 16. Vrstica 13: Spremeni indeks vključenosti za vozlišče 1 (r[1] = 0). 3-8 3.3 Primov algoritem 17. Vrstice 14–16: Vstop v tretjo zanko for, ki osveži vrednosti nevključenim vozliščem. V našem primeru se r[3] ne spremeni (saj je c[3, 4] < c[3, 1] oz. 14 < 15). 18. Vrstica 9: Vstop v tretjo (zadnjo) iteracijo druge zanke for. 19. Vrstica 10: Določi vozlišče j, ki ga vključimo v rešitev. Vozlišče 3 ima c(j, r[j])=min. Sicer pa je to vozlišče zadnjo nevključeno vozlišče. 20. Vrstica 11: Vključi povezavo (3, 4) v drevo T (slika 3.3d). To je že končno minimalno vpeto drevo. 21. Vrstica 12: Osveži vrednost vpetega drevesa. Nova vrednost je: vr = 22 + 14 = 36. 22. Vrstica 13: Spremeni indeks vključenosti za vozlišče 3 (r[3] = 0). 23. Vrstice 14–16: Vstop v tretjo zanko for, ki osveži vrednosti nevključenim vozliščem. V našem primeru zanka ne osvežuje več, saj ni več izpolnjen pogoj r[j] 6= 0. 24. Preglednica 3.1 povzema delovanje algoritma PRIM. ♦ Preglednica 3.1: Rezultati delovanja procedure PRIM zap. št. vključene povezave 1 2 3 4 j vključena povezava 4 1 3 (2,5) (4,5) (1,4) (3,4) vrednost drevesa vr 4 16 22 36 r[h] 12345 20550 4 40 0 0 Časovna zahtevnost procedure PRIM Procedura PRIM ima tri zanke for. Prva zanka for se izvede n-krat, druga (n − 2)krat in tretja n-krat. Tretja zanka for je vgnezdena v drugo zanko. Vse zanke se brezpogojno izvedejo do konca, zato je skupna časovna zahtevnost: T (n) = Θ(n) + Θ(n2 ) = Θ(n2 ), kar pomeni, da so zgornja, spodnja in poprečna časovna zahtevnost enake. (3.5) 3-9 3.4 Dijkstrin algoritem 3.4 Dijkstrin algoritem V tem razdelku bomo spoznali problem najkrajše poti iz enega vozlišča do vseh preostalih vozlišč v grafu. Preden se lotimo obravnave algoritma razložimo nekaj novih pojmov. V problemih najkrajše poti je dan utežen usmerjen graf G = (V, E) z utežno funkcijo w : E → R, ki preslika povezave v uteži (realna števila). Definicija 3.1. Utež poti p = hv0 , v1 , . . . , vk i je vsota uteži njenih povezav: w(p) = k X w(vi−1 , vi ). (3.6) i=1 Definicija 3.2. Utež najkrajše poti ( shortest-path weight) je definirana kot: ½ min{w(p) : u → v}, ˇce je pot od u do v, δ(u, v) = (3.7) ∞, drugaˇce. Najkrajša pot iz vozlišča u do vozlišča v je definirana kot katerakoli pot p z utežjo w(p) = δ(u, v). Uteži predstavljajo razdalje (problem zemljevida), čas, stroške, kazni ipd. Algoritem iskanja z razvijanjem v širino je algoritem najkrajših poti na neuteženih grafih, kjer ima vsaka povezava enotsko utež. Precej konceptov iz tega algoritma se uporablja tudi v uteženih grafih. V tem razdelku se bomo osredotočili na problem najkrajših poti iz enega vozlišča: za dani graf G = (V, E) želimo določiti najkrajše poti iz izhodišča s ∈ V k vsakemu vozlišču v ∈ V . V nekaterih problemih najkrajših poti iz enega vozlišča lahko imamo povezave z negativnimi utežmi. Če graf G = (V, E) ne vsebuje ciklov z negativno utežjo, dosegljivih iz izhodišča s, potem so najkrajše poti δ(s, v) do vozlišč v ∈ V dobro definirane, čeprav imajo celo negativno utež. Če obstaja cikel z negativnimi utežmi, dosegljiv iz s, potem dobi najkrajša pot vrednost −∞ (δ(s, v) = −∞). Slika 3.4 kaže učinek negativnih uteži na uteži najkrajših poti. Iz s do a vodi samo ena pot (pot hs, ai), zato je δ(s, a) = w(s, a) = 6. Podobno velja za pot iz s do b (pot hs, a, bi): δ(s, b) = w(s, a) + w(a, b) = 6 + (−2) = 4. Na drugi strani pa imamo iz s do c neskončno mnogo poti: hs, ci, hs, c, d, ci, hs, c, d, c, d, ci itd. Ker ima cikel hc, d, ci utež 5 + (−4) = 1 > 0, je najkrajša pot iz s v c hs, ci z utežjo δ(s, c) = 3. Podobno je najkrajša pot iz s v d hs, c, di z utežjo δ(s, d) = w(s, c) + w(c, d) = 3 + 5 = 8. Analogno imamo neskončno poti iz s v e: hs, ei, hs, e, f, ei. hs, e, f, e, f, ei itd. Ker ima cikel he, f, ei utež 2 + (−5) = −3 < 0, ne obstaja najkrajša pot iz s v e. Če se sprehodimo po ciklu z negativno utežjo he, f, ei poljubno krat, lahko 3-10 3.4 Dijkstrin algoritem najdemo poti iz s v e s poljubno velikimi negativnimi utežmi, zato je δ(s, e) = −∞. Podobno velja za δ(s, f ) = −∞. Ker je g dosegljivo iz f , lahko najdemo poti s poljubni velikimi negativnimi utežmi iz s do g in δ(s, g) = −∞. 6 a -2 4 b 10 6 3 0 s 3 c 5 8 d 11 g -¥ -4 7 6 2 e -¥ -5 f -¥ Slika 3.4: Negativne uteži v usmerjenem grafu. Ob vsakem vozlišču je prikazana njegova najkrajša pot iz izhodišča. Da bi razumeli algoritme najkrajše poti iz enega vozlišča, je koristno poznati tehnike, ki jih ti algoritmi uporabljajo, in lastnosti najkrajših poti, ki jih le-ti izkoriščajo. Glavna tehnika, ki jo uporabljajo algoritmi, je relaksacija (relaxation), tj. metoda, ki ponavljajoče zmanjšuje zgornjo mejo uteži najkrajše poti, dokler ne postane enaka uteži najkrajše poti. Algoritmi najkrajših poti izkoriščajo lastnost, da najkrajša pot med dvema vozliščema vsebuje druge najkrajše poti znotraj te poti. To je princip optimalnosti, ki je značilen tako za požrešno metodo kot dinamično programiranje. Principu optimalnosti pravijo nekateri tudi lastnost optimalne podstrukture (optimal-substructure property). Izrek 3.1. Dan je utežni usmerjen graf G = (V, E) z utežno funkcijo w : E → R. Bodi p = hv1 , v2 , . . . , vk i najkrajša pot iz vozlišča v1 do vozlišča vk in za poljubni i in j (1 ≤ i ≤ j ≤ k) bodi pij = hvi , vi+1 , . . . , vj i delna pot ( subpath) iz vozlišča vi do vozlišča vj . Potem je pij najkrajša pot iz vi do vj . Izrek 3.2. Dan je utežni usmerjen graf G = (V, E) z utežno funkcijo w : E → R. Predpostavljamo, da lahko najkrajšo pot p iz izhodišča s do vozlišča v razcepimo 0 v pot p od s do u in pot od u direktno v vozlišče v (torej med vozliščema u in v obstaja neposredna povezava). Potem je utež najkrajše poti iz s do u enaka δ(s, v) = δ(s, u) + w(u, v). 3-11 3.4 Dijkstrin algoritem Izrek 3.3. Dan je utežni usmerjen graf G = (V, E) z utežno funkcijo w : E → R. Za vse povezave (u, v) ∈ E velja: δ(s, v) ≤ δ(s, u) + w(u, v). V izreku 3.2 je vozlišče u vključeno v najkrajšo pot med s in v, v izreku 3.3 pa vozlišče u ni nujno vključeno v najkrajšo pot med s in v. Za vsako vozlišče v ∈ V vzdržujemo atribut d[v], ki je zgornja meja uteži najkrajše poti od izhodišča s do v. d[v] imenujemo ocena najkrajše poti (shortestpath estimate). Ocene najkrajše poti in prednike inicializiramo z naslednjo proceduro: INICIALIZACIJA(G, s) 1 for vsako vozlišče v ∈ V 2 do d[v] ← ∞ 3 oce[v] ← NIL 4 d[s] ← 0 Proces relaksacije povezave (u, v) sestoji iz testiranja, če lahko izboljšamo najkrajšo pot do v, tako da gremo skozi u in če nam to uspe, osvežimo d[v] in oce[v]. Relaksacijo izvedemo z naslednjo kodo: RELAKSACIJA(u, v, w) 1 if d[v] > d[u] + w(u, v) 2 then d[v] ← d[u] + w(u, v) 3 oce[v] ← u Slika 3.5 prikazuje dva primera relaksacije povezave. V primeru na sliki 3.5a se ocena najkrajše poti zmanjša, v primeru na sliki 3.5b pa se ocena ne spremeni. 3-12 3.4 Dijkstrin algoritem 10 u 15 v 3 10 u RELAKSACIJA(u, v, w) 10 u 3 13 v 11 v 3 RELAKSACIJA(u, v, w) 10 u a) 3 11 v b) Slika 3.5: Relaksacija povezave (u, v). Ocena najkrajše povezave d je označena ob vozlišču. a) Ker je pred relaksacijo d[v] > d[u]+w(u, v), se vrednost d[v] zmanjša. b) Ker je pred relaksacijo d[v] ≤ d[u] + w(u, v), se d[v] ne spremeni. Izrek 3.4. Dan je utežni usmerjen graf G = (V, E) z utežno funkcijo w : E → R in bodi poljubna povezava (u, v) ∈ E. Po relaksaciji povezave (u, v) s proceduro RELAKSACIJA(u, v, w) velja d[v] ≤ d[u] + w(u, v). Dijkstrin algoritem rešuje problem najkrajše poti iz enega vozlišča na utežnem usmerjenem grafu, ko so vse uteži nenegativne. Algoritem vzdržuje množico S vozlišč, ki imajo že določeno najkrajšo pot iz izhodišča s. Za vsa vozlišča v ∈ S velja d[v] = δ(s, v). Algoritem izbere tako vozlišče u (u ∈ V −S), ki ima minimalno oceno najkrajše poti, vstavi u v S in relaksira vse povezave, ki izhajajo iz u. V pričujoči aplikaciji vzdržujemo prednostno vrsto Q, ki vsebuje vsa vozlišča (iz V − S), ki hranijo v ključih vrednost d. Implementacija predpostavlja, da je graf podan s seznami sosedov. DIJKSTRA(G, w, s) 1 INICIALIZACIJA(G, s) 2 S←∅ 3 Q←V 4 while Q 6= 0 5 do u ← IZLOCI-MINIMUM(Q) 6 S ← S ∪ {u} 7 for za vsako vozlišče v ∈ Adj[u] 8 do RELAKSACIJA(u, v, w) 3.4 Dijkstrin algoritem 3-13 V vrstici 1 postavimo začetne vrednosti za d[v] in oce[v] za vsako vozlišče v iz G. Vrstica 2 napravi množico S prazno. V vrstici 3 postavimo v prednostno vrsto Q vsa vozlišča V . V iteraciji zanke while v vrsticah od 4–8 se izloči vozlišče u, ki ima najmanjšo oceno najkrajše poti v V − S, in ga vloži v množico S ( V prvi iteraciji se izloči izhodišče s.). V vrsticah od 7–8 se relaksira vsaka povezava (u, v), ki zapušča u, in se osvežijo ocene d[v] in oce[v]. Zgled 3.3. Delovanje Dijkstrinega algoritma na usmerjenem grafu kaže slika 3.6. Ocene najkrajših poti so zapisane ob krogcih, ki predstavljajo vozlišča. S pomočjo poudarjene povezave lahko določimo očeta ustreznega vozlišča (oče je vozlišče pri repu puščice). Črna vozlišča so v množici S, bela pa v prednostni vrsti Q = V − S. Vozlišče, ki je pobarvano sivo, bo izbrano v naslednji iteraciji zanke while. Bodi izhodišče vozlišče 1. Podrobno ponazorimo delovanje algoritma: 1. Vrstica 1: Po izvršitvi procedure INICIALIZACIJA(G, 1) imamo d[2] = d[3] = d[4] = d[5] = ∞, d[1] = 0 in oce[1] = oce[2] = oce[3] = oce[4] = oce[5] = NIL. 2. Vrstica 2: S = {}. 3. Vrstica 3: Q = {1, 2, 3, 4, 5}. V prvi iteraciji while bo izbrano vozlišče 1, ki ima najmanjšo vrednost d izmed vseh elementov v vrsti Q. Zato vozlišče 1 pobarvamo sivo. Stanje po tej izbiri kaže slika 3.6a. 4. Vrstice 4–8: 1. iteracija while: Prednostna vrsta Q ni prazna. Iz nje izločimo vozlišče 1, ki ima najmanjšo vrednost d (novo stanje je Q = {2, 3, 4, 5}). V množico S vključimo vozlišče 1 (S = {1}) in ga označimo s črno barvo. 1. iteracija for: 1. sosed od 1 je v = 2 in izvedemo RELAKSACIJA(1, 2, w). Ker je d[2] > d[1] + w(1, 2) (∞ > 0 + 9), postavimo d[2] = d[1] + w(1, 2) = 0 + 9 = 9 in oce[2] = 1. 2. iteracija for: 2. sosed od 1 je v = 5, izvedemo RELAKSACIJA(1, 5, w). Ker je d[5] > d[1]+w(1, 5) (∞ > 0+4), postavimo d[5] = d[1]+w(1, 5) = 0+4 = 4 in oce[5] = 1. Stanje po zaključku 1. iteracije while kaže slika 3.6b, kjer je označeno vozlišče 1 kot črno (je tudi že razvito v celoti). Vozlišče 5 je pobarvano sivo, saj ima trenutno najmanjšo vrednost d v vrsti Q. 5. Vrstice 4–8: 2. iteracija while: Prednostna vrsta Q ni prazna. Iz nje izločimo vozlišče 5, ki ima najmanjšo vrednost d (novo stanje je Q = {2, 3, 4}). V množico S vključimo vozlišče 5 (S = {1, 5}) in ga pobarvamo črno. 1. iteracija for: Sosed od 5 je v = 2 in izvedemo RELAKSACIJA(5, 2, w). Ker je d[2] > d[5]+w(5, 2) (9 > 4+1), postavimo d[2] = d[5]+w(5, 2) = 4+1 = 5 3.4 Dijkstrin algoritem 3-14 in oce[2] = 5. Vozlišče 2 dobi novega očeta, tj. 5. Stanje po zaključku 2. iteracije while kaže slika 3.6c. Na sliki je vozlišče 5 pobarvano črno (je tudi že razvito) in vozlišče 2 kot sivo, saj ima trenutno najmanjšo vrednost d v vrsti Q. 6. Vrstice 4–8: 3. iteracija while: Prednostna vrsta Q ni prazna. Iz nje izločimo vozlišče 2, ki ima najmanjšo vrednost d (novo stanje je Q = {3, 4}). V množico S vključimo vozlišče 2 (S = {1, 5, 2}) in ga pobarvamo črno. 1. iteracija for: 1. sosed od 2 je v = 3 in izvedemo RELAKSACIJA(2, 3, w). Ker je d[3] > d[2] + w(2, 3) ( ∞ > 5 + 5), postavimo d[3] = d[2] + w(2, 3) = 5 + 5 = 10 in oce[3] = 2. 2. iteracija for: 2. sosed od 2 je v = 4 in izvedemo RELAKSACIJA(2, 4, w). Ker je d[4] > d[2] + w(2, 4) (∞ > 5 + 4), postavimo d[4] = d[2] + w(2, 4) = 5 + 4 = 9 in oce[4] = 2. Stanje po zaključku 3. iteracije while kaže slika 3.6d. Na sliki je vozlišče 2 kot črno (je že razvito) in vozlišče 4 kot sivo, saj ima trenutno najmanjšo vrednost d v vrsti Q. 7. Vrstice 4–8: 4. iteracija while: Prednostna vrsta Q ni prazna. Iz nje izločimo vozlišče 4, ki ima najmanjšo vrednost d (novo stanje je Q = {3}). V množico S vključimo vozlišče 4 (S = {1, 5, 2, 4}) in ga pobarvamo črno. 1. iteracija for: 1. sosed od 4 je v = 1 in izvedemo RELAKSACIJA(4, 1, w). Ker ni d[1] > d[4] + w(4, 1) (ne velja 0 > 9 + 6), zaključimo obravnavo tega soseda. 2. iteracija for: 2. sosed od 4 je v = 5, izvedemo RELAKSACIJA(4, 5, w). Ker ni d[5] > d[4] + w(4, 5) (ne velja 4 > 9 + 7), zaključimo obravnavo tega soseda. Stanje po zaključku 4. iteracije while kaže slika 3.6e. Na sliki je označeno kot črno vozlišče 4 in kot sivo vozlišče 3, saj ima trenutno najmanjšo vrednost d v vrsti Q. 8. Vrstice 4–8: 5. iteracija while: Prednostna vrsta Q ni prazna. Iz nje izločimo vozlišče 3, ki ima najmanjšo vrednost d (novo stanje je Q = {}). V množico S vključimo vozlišče 3 (S = {1, 5, 2, 4, 3}) in ga pobarvamo črno. 1. iteracija for: 1. sosed od 3 je v = 2 in izvedemo RELAKSACIJA(3, 2, w). Ker ni d[2] > d[3] + w(3, 2) (ne velja 5 > 10 + 3), zaključimo obravnavo tega soseda. 2. iteracija for: 2. sosed od 3 je v = 4 in izvedemo RELAKSACIJA(3, 4, w). Ker ni d[4] > d[3] + w(3, 4) (ne velja 9 > 10 + 2), zaključimo obravnavo tega soseda. Stanje po zaključku 5. iteracije while kaže slika 3.6f. Na sliki so vsa vozlišča označena črno. Ker je množica Q izčrpana (prazna), zaključimo proceduro. 3-15 3.4 Dijkstrin algoritem Preglednica 3.2: Rezultati delovanja procedure DIJKSTRA zap. št. iteracije vrstica 3 1 2 3 4 5 izbrano vozlišče 1 5 2 4 3 oče v drevesu NIL 1 5 2 2 najkrajša pot 1 1, 5 1, 5, 2 1, 5, 2, 4 1, 5, 2, 3 d[1] 0! - d[2] ∞ 9 5! - d[3] ∞ ∞ ∞ 10 10! - d[4] ∞ ∞ ∞ 9! - d[5] ∞ 4! - Če narišemo samo poudarjene povezave v grafu na sliki 3.6f, dobimo drevo najkrajših poti iz vozlišča 1 do vseh ostalih vozlišč v grafu (slika 3.7). Zunaj ob vozlišču je označena dolžina najkrajše poti iz vozlišča 1. ♦ Časovna zahtevnost Procedura IZLOCI-MINIMUM zahteva O(|V |) časa, ker pa jo izvedemo |V |-krat, je celoten čas za IZLOCI-MINIMUM velikosti O(|V |2 ). Zanka for v vrsticah 7–8 se izvede tolikokrat, kolikor imamo povezav, tj. |E|-krat, medtem ko vsaka iteracija zahteva O(1) časa. Skupni čas algoritma je torej T (n) = O(|V |2 + |E|) = O(|V |2 ), saj velja, da je v polnem usmerjenem grafu |E| = |V |(|V | − 1) povezav in je |V |2 > |E|. 3-16 3.4 Dijkstrin algoritem 0 1 0 1 4 4 9 1 ¥ 5 6 4 7 4 ¥ 2 ¥ 5 0 1 4 4 2 5 5 1 4 5 4 9 d) 0 1 0 1 4 9 1 6 4 2 e) 2 5 5 3 10 3 2 5 5 1 6 4 9 3 9 4 5 7 3 3 10 2 c) 4 4 5 4 7 3 ¥ 2 5 9 6 3 2 9 3 ¥ 2 0 1 6 4 9 4 ¥ b) 1 4 ¥ 4 7 9 4 5 7 6 3 a) 4 7 1 4 5 3 ¥ 2 9 4 2 2 5 5 3 3 10 f) Slika 3.6: Delovanje Dijkstrinega algoritma na usmerjenem grafu: a) Stanje po izvršitvi vrstice 3, b) Stanje po izvršitvi 1. iteracije while, c) Stanje po izvršitvi 2. iteracije while, d) Stanje po izvršitvi 3. iteracije while, e) Stanje po izvršitvi 4. iteracije while, f) Stanje po izvršitvi 5. (zadnje) iteracije while. 3.4 Dijkstrin algoritem 3-17 Slika 3.7: Drevo najkrajših poti iz vozlišča 1 do vseh ostalih vozlišč v grafu na sliki 3.6a 3.5 Bellman-Fordov algoritem 3.5 3-18 Bellman-Fordov algoritem Bellman-Fordov algoritem rešuje problem najkrajših poti iz enega vozlišča bolj splošno, torej tudi v primeru negativnih uteži za povezave. Za dani uteženi usmerjeni graf G = (V, E) z izhodiščem s in utežno funkcijo w : E → R vrne Bellman-Fordov algoritem Boolovo vrednost, ki označuje, ali je ali ni cikla z negativno utežjo, dosegljivega iz izhodišča. Če obstaja tak cikel, algoritem naznani, da ni rešitve. Če tak cikel ne obstaja, algoritem proizvede najkrajše poti in njihove uteži. Podobno kot Dijkstrin algoritem tudi Bellman-Fordov algoritem uporablja metodo relaksacije. Bellman-Fordov algoritem vrne TRUE, če in samo če graf ne vsebuje ciklov z negativnimi utežmi, dosegljivih iz izhodišča. BELLMAN-FORD(G, w, s) 1 INICIALIZACIJA(G, s) 2 for i ← 1 to |V | − 1 3 do for za vsako povezavo (u, v) ∈ E 4 do RELAKSACIJA(u, v, w) 5 for za vsako povezavo (u, v) ∈ E 6 do if d[v] > d[u] + w(u, v) 7 then return FALSE % eden od ciklov je negativen 8 return TRUE % vsi cikli so pozitivni V vrstici 1 postavimo začetne vrednosti za d[v] in oce[v] za vsako vozlišče v iz G. V vrsticah 2–4 se izvede |V | − 1 iteracij preko povezav grafa. Vsaka iteracija relaksira vsako povezavo grafa samo enkrat. Vrstice 5–8 preverjajo cikel z negativno utežjo in vrnejo ustrezno Boolovo vrednost. Zgled 3.4. Delovanje Bellman-Fordovega algoritma na usmerjenem grafu kaže slika 3.8. Izhodišče s naj bo vozlišče 5. Ocene najkrajših poti so zapisane zunaj ob krogcih, ki predstavljajo vozlišča. S pomočjo poudarjene povezave lahko določimo očeta ustreznega vozlišča (oče je vozlišče pri repu puščice). V tem zgledu vsaka iteracija prve zanke for relaksira povezave po naslednjem vrstnem redu: (1, 2), (1, 3), (1, 4), (2, 1), (3, 2), (3, 4), (4, 2), (4, 5), (5, 1), (5, 3). Vsi cikli imajo pozitivno vrednost, zato nam algoritem vrne vrednost TRUE. Podrobno ponazorimo delovanje algoritma: 1. Vrstica 1: Po izvršitvi procedure INICIALIZACIJA(G, 5) imamo d[1] = d[2] = d[3] = d[4] = ∞, d[5] = 0 in oce[1] = oce[2] = oce[3] = oce[4] = oce[5] = NIL. Stanje po tej izvršitvi kaže slika 3.8a. 2. Vrstice 2–4: 1. iteracija 1. zanke for: Prvo relaksacijo dobimo šele pri povezavi (5, 1) (0 + 6 < ∞). Tedaj se določita nova razdalja in novi oče pri 3.5 Bellman-Fordov algoritem 3-19 vozlišču 1, tj. d[1] = 6 in oce[1] = 5. Druga relaksacija se zgodi pri povezavi (5, 3) (0 + 7 < ∞), ko se izračunata nova razdalja in novi oče pri vozlišču 3, tj. d[3] = 7 in oce[3] = 5. Stanje po zaključku 1. iteracije 1. zanke for kaže slika 3.8b. 3. Vrstice 2–4: 2. iteracija 1. zanke for: Prva relaksacija se izvede že pri povezavi (1, 2) (6 + 5 < ∞). Tedaj se določita nova razdalja in novi oče pri vozlišču 2, tj. d[2] = 11 in oce[2] = 1. Druga relaksacija se zgodi pri pri povezavi (1, 4) (6 − 4 < ∞), ko dobimo novo razdaljo in novega očeta pri vozlišču 4, tj. d[4] = 2 in oce[4] = 1. Tretja relaksacija se izvede pri povezavi (3, 2) (7 − 3 < 11), ko dobimo ponovno novo razdaljo in novega očeta pri vozlišču 2, tj. d[2] = 4 in oce[2] = 3. Stanje po zaključku 2. iteracije 1. zanke for kaže slika 3.8c. 4. Vrstice 2–4: 3. iteracija 1. zanke for: V tej iteraciji se izvede samo ena relaksacija in to pri povezavi (2, 1) (4 − 2 < 6). Tedaj se določita nova razdalja in novi oče pri vozlišču 1, tj. d[1] = 2 in oce[1] = 2. Stanje po zaključku 3. iteracije 1. zanke for kaže slika 3.8d. 5. Vrstice 2–4: 4. iteracija 1. zanke for: Izvede se samo ena relaksacija in to pri povezavi (1, 4) (2−4 < 2). Tedaj se določi nova razdalja d[4] = −2, medtem ko oče od vozlišča 4 ostane nespremenjen. Stanje po zaključku 4. iteracije 1. zanke for kaže slika 3.8e. 6. Vrstice 5–7: V tretji zanki for ne velja pri nobeni povezavi (u, v) neenačba d[v] > d[u] + w(u, v). V danem grafu imamo štiri cikle, kjer se pojavijo tudi povezave z negativnimi utežmi. Cikel h1, 2, 1i ima utež 5 + (−2) = 3, cikel h1, 4, 2, 1i utež (−4) + 7 + (−2) = 1, cikel h1, 4, 5, 1i utež (−4) + 2 + 6 = 4 in cikel h1, 3, 2, 1i ima utež 8 + (−3) + (−2) = 3. 7. Vrstica 8: Bellman-Fordov algoritem nam vrne TRUE, ker ni ciklov z negativnimi vrednostmi. 3.5 Bellman-Fordov algoritem 3-20 Slika 3.8: Delovanje Bellman-Fordovega algoritma na usmerjenem grafu: a) Stanje po izvršitvi vrstice 1, b) Stanje po izvršitvi 1. iteracije 1. zanke for, c) Stanje po izvršitvi 2. iteracije 1. zanke for, d) Stanje po izvršitvi 3. iteracije 1. zanke for, e) Stanje po izvršitvi 4. iteracije 1. zanke for 3-21 3.5 Bellman-Fordov algoritem Preglednica 3.3: Rezultati delovanja procedure BELLMAN-FORD zap. št. iteracije pred 1. it. 1 2 3 4 oce[1] NIL 5 5 2 2 oce[2] NIL NIL 3 3 3 oce[3] NIL 5 5 5 5 oce[4] NIL NIL 1 1 1 oce[5] NIL NIL NIL NIL NIL d[1] ∞ 6 6 2 2 d[2] ∞ ∞ 4 4 4 d[3] ∞ 7 7 7 7 d[4] ∞ ∞ 2 2 -2 d[5] 0 0 0 0 0 Če narišemo samo poudarjene povezave v grafu na sliki 3.8e, dobimo drevo najkrajših poti iz vozlišča 5 do vseh ostalih vozlišč v grafu (slika 3.9). Zunaj ob vozlišču je označena dolžina najkrajše poti iz vozlišča 5. ♦ Slika 3.9: Drevo najkrajših poti iz vozlišča 5 do vseh ostalih vozlišč v grafu na sliki 3.8a 3.5 Bellman-Fordov algoritem 3-22 Časovna zahtevnost Inicializacija v vrstici 1 zahteva O(|V |) časa. Prva zanka for se izvede (|V | − 1)krat. V vsaki od iteracij te zanke se pa izvede še druga zanka for |E|-krat. Skupaj torej prva zanka for zahteva O(|V ||E|) časa. Tretja zanka for zahteva O(|E|) časa. Skupna časovna zahtevnost Bellman-Fordovega algoritma je: T (n) = O(|V |) + O(|V ||E|) + O(|E|) = O(|V ||E|). Literatura Aho, A. V., Hopcroft, J. E., and Ullman, J. D. (1974). The Design and Analysis of Computer Algorithms. Addison-Wesley, Reading. Cormen, T. H., Leiserson, C. E., and Rivest, R. L. (2007). Introduction to Algorithms. Druga izdaja, MIT Press, Cambridge. Horowitz, E., Sahni, S., and Rajasekaran, S. (1998). Computer Algorithms. Computer Science Press, New York. Kleinberg, J. and Tardos, E. (2006). Algorithm Design. Parson Education, Inc., New York. Kononenko, I. (1996). Načrtovanje podatkovnih struktur in algoritmov. Fakulteta za računalništvo in informatiko, Ljubljana. Kozak, J. (1997). Podatkovne strukture in algoritmi. Društvo matematikov, fizikov in astronomov Slovenije, Ljubljana. Levitin, A. (2007). The Design and Analysis of Algorithms. Druga izdaja, Pearson Education, Inc., Boston. Manber, U. (1989). Introduction to Algorithms. A Creative Approach. AddisonWesley, Reading. Nilsson, N. J. (1980). Principles of Artificial Intelligence. Tioga. Sedgewick, R. (2003). Boston. Algorithms in Java. Third Edition. Addison-Wesley, Vilfan, B. (1998). Osnovni algoritmi. Fakulteta za računalništvo in informatiko, Ljubljana.