Dinamično programiranje
Transcription
Dinamično programiranje
RAČUNALNIŠTVO IN INFORMACIJSKE TEHNOLOGIJE OSNOVE ALGORITMOV NIKOLA GUID Fakulteta za elektrotehniko, računalništvo in informatiko Maribor, 2011 Kazalo 4 Dinamično programiranje 4.1 Floyd-Warshallov algoritem . . . . . . . . . . . . . . . . . . . . . . 4.2 Problem trgovskega potnika . . . . . . . . . . . . . . . . . . . . . . 4.3 Optimalna dvojiška iskalna drevesa . . . . . . . . . . . . . . . . . . 4-1 4-2 4-9 4-12 Poglavje 4 Dinamično programiranje Dinamično programiranje (dynamic programming) je iznašel ameriški matematik Richard Bellman v 50-tih letih prejšnjega stoletja kot splošno metodo za optimiziranje večstopenjskih odločitvenih procesov. Beseda “programiranje” stoji tu namesto besede “planiranje” in nima zveze z računalniškim programiranjem. Potem ko se je metoda dokazala kot uspešno orodje v uporabni matematiki, so jo prevzeli tudi v računalništvu kot strategijo pri načrtovanju algoritmov. Dinamično programiranje je metoda za reševanje problemov s podproblemi, ki se prekrivajo. Rešitev problema dobimo iz množice rešenih podproblemov. Torej, reševanje vodi rekurzivna enačba, ki ji pravimo Bellmanova enačba. Osnovna lastnost dinamičnega programiranja je, da temelji na pravilu optimalnosti (principle of optimality). Pravilo pove, da je optimalna rešitev problema sestavljena iz optimalnih rešitev podproblemov in obrnjeno. Pri formuliranju rekurzivnih izrazov dinamičnega programiranja lahko uporabljamo dva različna pristopa: • pristop naprej (forward approach) in • pristop nazaj (backward approach). Naj bodo x1 , x2 , . . . , xn spremenljivke, ki nastopajo v problemu. V pristopu naprej se formulacija za spremenljivko xi izvede kot funkcija f (xi+1 , xi+2 , . . . , xn ), kar pomeni, da rešujemo nazaj (od xn do x1 ). V pristopu nazaj se formulacija za spremenljivko xi izvede kot funkcija f (x1 , x2 , . . . , xi−1 ), kar pomeni, da rešujemo naprej (od x1 do xn ). 4.1 Floyd-Warshallov algoritem 4.1 4-2 Floyd-Warshallov algoritem V tem razdelku obravnavamo problem iskanja najkrajših poti med vsemi pari vozlišč v grafu. Dan je utežen usmerjen graf G = (V, E) z utežno funkcijo w : E → R, ki preslika povezave v uteži (realna števila). Za vsak par vozlišč u, v ∈ V želimo poiskati najkrajšo (z najmanjšo utežjo) pot iz u v v. Dani problem lahko rešimo, tako da poženemo algoritem za iskanje najkrajših poti iz enega vozlišča |V |-krat, enkrat za vsako od vozlišč. Če so vse uteži nenegativne, lahko uporabimo Dijkstrin algoritem. Če za prednostno vrsto uporabimo implementacijo z linearnim poljem, je časovna zahtevnost O(|V |3 + |V ||E|) = O(|V |3 ). Implementacija prednostne vrste z dvojiško kopico nam da časovno zahtevnost O(|V ||E| ln |V |), kar pomeni izboljšanje, če je graf redek. Če dovoljujemo negativne uteži, moramo uporabiti počasnejši Bellman-Fordov algoritem, enkrat za vsako vozlišče. Časovna zahtevnost je sedaj O(|V |2 |E|), kar pomeni pri gostem grafu O(|V |4 ). V tem razdelku ne bomo izhajali iz algoritmov za iskanje najkrajših poti iz enega vozlišča, temveč bomo predstavili poseben algoritem, imenovan FloydWarshallov algoritem, ki uporablja predstavitev z matriko sosednosti. Element wij matrike uteži W ima naslednje vrednosti: ˇce i = j 0, uteˇz usmerjene povezave (i, j), ˇce i 6= j in (i, j) ∈ E wij = (4.1) ∞, ˇce i 6= j in (i, j) 6∈ E Floyd-Warshallov algoritem prikazuje rezultate v matriki D = (dij ), kjer predstavlja dij utež najkrajše poti iz vozlišča i do vozlišča j. Za označevanje iteracij (m) (k) bomo uporabili sufiks: D(m) = (dij ). dij označuje utež najkrajše poti iz vozlišča i do vozlišča j preko vozlišč, katerih indeks ne presega k. Torej vmesna vozlišča so iz množice {1, 2, 3, . . . , k}. dij izračunamo po naslednji rekurzivni formuli (Bellmanova enačba): ( wij , ³ ˇce k = 0 (k) ´ dij = (4.2) (k−1) (k−1) (k−1) min dij , dik + dkj , ˇce k ≥ 1 ³ ´ (n) (n) Matrika D(n) = dij daje končni odgovor, tj. dij = δ(i, j) za vse i, j ∈ V , ker so vmesna vozlišča v množici {1, 2, 3, . . . , n}. Procedura ima naslednjo obliko: 4-3 4.1 Floyd-Warshallov algoritem FLOYD-WARSHALL(W) 1 n ← število vrst v W 2 D(0) ← W 3 for k ← 1 to n 4 do for i ← 1 to n 5 do for j ← 1 to n ³ ´ (k) (k−1) (k−1) (k−1) 6 do dij ← min dij , dik + dkj 7 return D(n) Časovna zahtevnost procedure FLOYD-WARSHALL(W) Ta procedura izvede vrstico 6 v času O(1), medtem ko se celotni algoritem izvede v času Θ(n3 ). Zgled 4.1. Izračunajmo najkrajše poti med vsemi pari vozlišč v grafu na sliki 4.1 s proceduro FLOYD-WARSHALL(W). Matrika uteži W je: 0 4 11 W = 6 0 2 . 3 ∞ 0 1 6 3 4 2 11 2 3 Slika 4.1: Usmerjen graf. 1. Vrstica 1: n = 3. 2. Vrstica 2: D(0) 0 4 11 = W = 6 0 2 . 3 ∞ 0 3. Vrstice 3–6: 1. iteracija 1. zanke for: k = 1. 4. Vrstice 4–6: 1. iteracija 2. zanke for: i = 1. 4.1 Floyd-Warshallov algoritem 4-4 5. Vrstici 5–6: 1. iteracija 3. zanke for: j = 1, (1) (0) (0) (0) d11 = min(d11 , d11 + d11 ) = min(0, 0 + 0) = 0. 6. Vrstici 5–6: 2. iteracija 3. zanke for: j = 2, (1) (0) (0) (0) d12 = min(d12 , d11 + d12 ) = min(4, 0 + 4) = 4. 7. Vrstici 5–6: 3. iteracija 3. zanke for: j = 3, (1) (0) (0) (0) d13 = min(d13 , d11 + d13 ) = min(11, 0 + 11) = 11. 8. Vrstice 4–6: 2. iteracija 2. zanke for: i = 2. 9. Vrstici 5–6: 1. iteracija 3. zanke for: j = 1, (1) (0) (0) (0) d21 = min(d21 , d21 + d11 ) = min(6, 6 + 0) = 6. 10. Vrstici 5–6: 2. iteracija 3. zanke for: j = 2, (1) (0) (0) (0) d22 = min(d22 , d21 + d12 ) = min(0, 6 + 4) = 0. 11. Vrstici 5–6: 3. iteracija 3. zanke for: j = 3, (1) (0) (0) (0) d23 = min(d23 , d21 + d13 ) = min(2, 6 + 11) = 2. 12. Vrstice 4–6: 3. iteracija 2. zanke for: i = 3. 13. Vrstici 5–6: 1. iteracija 3. zanke for: j = 1, (1) (0) (0) (0) d31 = min(d31 , d31 + d11 ) = min(3, 3 + 0) = 3. 14. Vrstici 5–6: 2. iteracija 3. zanke for: j = 2, (1) (0) (0) (0) d32 = min(d32 , d31 + d12 ) = min(∞, 3 + 4) = 7. 15. Vrstici 5–6: 3. iteracija 3. zanke for: j = 3, (1) (0) (0) (0) d33 = min(d33 , d31 + d13 ) = min(0, 3 + 11) = 0. 0 4 11 Sedaj lahko zapišemo: D(1) = 6 0 2 . 3 7 0 (1) Torej, edina sprememba glede na matriko D(0) se je zgodila na mestu d32 . 16. Vrstice 3–6: 2. iteracija 1. zanke for: k = 2. 17. Vrstice 4–6: 1. iteracija 2. zanke for: i = 1. 18. Vrstici 5–6: 1. iteracija 3. zanke for: j = 1, (2) (1) (1) (1) d11 = min(d11 , d12 + d21 ) = min(0, 4 + 6) = 0. 19. Vrstici 5–6: 2. iteracija 3. zanke for: j = 2, (2) (1) (1) (1) d12 = min(d12 , d12 + d22 ) = min(4, 4 + 0) = 4. 4-5 4.1 Floyd-Warshallov algoritem 20. Vrstici 5–6: 3. iteracija 3. zanke for: j = 3, (2) (1) (1) (1) d13 = min(d13 , d12 + d23 ) = min(11, 4 + 2) = 6. 21. Vrstice 4–6: 2. iteracija 2. zanke for: i = 2. 22. Vrstici 5–6: 1. iteracija 3. zanke for: j = 1, (2) (1) (1) (1) d21 = min(d21 , d22 + d21 ) = min(6, 0 + 6) = 6. 23. Vrstici 5–6: 2. iteracija 3. zanke for: j = 2, (2) (1) (1) (1) d22 = min(d22 , d22 + d22 ) = min(0, 0 + 0) = 0. 24. Vrstici 5–6: 3. iteracija 3. zanke for: j = 3, (2) (1) (1) (1) d23 = min(d23 , d22 + d23 ) = min(2, 0 + 2) = 2. 25. Vrstice 4–6: 3. iteracija 2. zanke for: i = 3. 26. Vrstici 5–6: 1. iteracija 3. zanke for: j = 1, (2) (1) (1) (1) d31 = min(d31 , d32 + d21 ) = min(3, 7 + 6) = 3. 27. Vrstici 5–6: 2. iteracija 3. zanke for: j = 2, (2) (1) (1) (1) d32 = min(d32 , d32 + d22 ) = min(7, 7 + 0) = 7. 28. Vrstici 5–6: 3. iteracija 3. zanke for: j = 3, (2) (1) (1) (1) d33 = min(d33 , d32 + d23 ) = min(0, 7 + 2) = 0. 0 4 6 Sedaj lahko zapišemo: D(2) = 6 0 2 . 3 7 0 (2) Edina sprememba glede na matriko D(1) se je zgodila na mestu d13 . 29. Vrstice 3–6: 3. iteracija 1. zanke for: k = 3. 30. Vrstice 4–6: 1. iteracija 2. zanke for: i = 1. 31. Vrstici 5–6: 1. iteracija 3. zanke for: j = 1, (3) (2) (2) (2) d11 = min(d11 , d13 + d31 ) = min(0, 6 + 3) = 0. 32. Vrstici 5–6: 2. iteracija 3. zanke for: j = 2, (3) (2) (2) (2) d12 = min(d12 , d13 + d32 ) = min(4, 6 + 7) = 4. 33. Vrstici 5–6: 3. iteracija 3. zanke for: j = 3, (3) (2) (2) (2) d13 = min(d13 , d13 + d33 ) = min(6, 6 + 0) = 6. 34. Vrstice 4–6: 2. iteracija 2. zanke for: i = 2. 35. Vrstici 5–6: 1. iteracija 3. zanke for: j = 1, (3) (2) (2) (2) d21 = min(d21 , d23 + d31 ) = min(6, 2 + 3) = 5. 4-6 4.1 Floyd-Warshallov algoritem 36. Vrstici 5–6: 2. iteracija 3. zanke for: j = 2, (3) (1) (2) (2) d22 = min(d22 , d23 + d32 ) = min(0, 2 + 7) = 0. 37. Vrstici 5–6: 3. iteracija 3. zanke for: j = 3, (3) (2) (2) (2) d23 = min(d23 , d23 + d33 ) = min(2, 2 + 0) = 2. 38. Vrstice 4–6: 3. iteracija 2. zanke for: i = 3. 39. Vrstici 5–6: 1. iteracija 3. zanke for: j = 1, (3) (2) (2) (2) d31 = min(d31 , d33 + d31 ) = min(3, 0 + 3) = 3. 40. Vrstici 5–6: 2. iteracija 3. zanke for: j = 2, (3) (2) (2) (2) d32 = min(d32 , d33 + d32 ) = min(7, 0 + 7) = 7. 41. Vrstici 5–6: 3. iteracija 3. zanke for: j = 3, (3) (2) (2) (2) d33 = min(d33 , d33 + d33 ) = min(0, 0 + 0) = 0. 0 4 6 42. Vrstica 7: Procedura nam vrne: D(3) = 5 0 2 . 3 7 0 (3) Torej, edina sprememba glede na matriko D(2) se je zgodila na mestu d21 . ♦ Zgled 4.2. Izračunajmo najkrajše poti med vsemi pari vozlišč v grafu na sliki 4.2 s proceduro FLOYD-WARSHALL(W). Dani graf ima tudi negativne uteži, nima pa nobenega negativnega cikla. Slika 4.2: Usmerjen graf. 4-7 4.1 Floyd-Warshallov algoritem 1. Vrstica 1: n = 5. 2. Vrstica 2: (0) D =W= 0 3 8 ∞ −4 ∞ 0 ∞ 1 7 ∞ 4 0 ∞ ∞ . 2 ∞ −5 0 ∞ ∞ ∞ ∞ 6 0 3. Vrstice 3–6: Na koncu 1. iteracije 1. zanke for, ko je k = 1, dobimo matriko D(1) , ki prikazuje najkrajše poti preko vmesnega vozlišča z indeksom 1: 0 3 8 ∞ −4 ∞ 0 ∞ 1 7 (1) 0 ∞ ∞ D = ∞ 4 . 2 5 −5 0 −2 ∞ ∞ ∞ 6 0 4. Vrstice 3–6: Na koncu 2. iteracije 1. zanke for, ko je k = 2, dobimo matriko D(2) , ki prikazuje najkrajše poti preko vmesnih vozlišč z indeksoma 1 in 2: 0 3 8 4 −4 ∞ 0 ∞ 1 7 (2) 0 5 11 D = ∞ 4 . 2 5 −5 0 −2 ∞ ∞ ∞ 6 0 5. Vrstice 3–6: Na koncu 3. iteracije 1. zanke for, ko je k = 3, dobimo matriko D(3) , ki prikazuje najkrajše poti preko vmesnih vozlišč z indeksi 1, 2 in 3: 0 3 8 4 −4 ∞ 0 ∞ 1 7 (3) . ∞ 4 0 5 11 D = 2 −1 −5 0 −2 ∞ ∞ ∞ 6 0 6. Vrstice 3–6: Na koncu 4. iteracije 1. zanke for, ko je k = 4, dobimo matriko D(4) , ki prikazuje najkrajše poti preko vmesnih vozlišč z indeksi 1, 2, 3 in 4: 0 3 −1 4 −4 3 0 −4 1 −1 (4) 0 5 3 D = . 7 4 2 −1 −5 0 −2 8 5 1 6 0 4.1 Floyd-Warshallov algoritem 4-8 7. Vrstice 3–6: Na koncu 5. iteracije 1. zanke for, ko je k = 5, dobimo matriko D(5) , ki prikazuje najkrajše poti preko vmesnih vozlišč z indeksi 1, 2, 3, 4 in 5: 0 1 −3 2 −4 3 0 −4 1 −1 (5) 0 5 3 D = 7 4 . ♦ 2 −1 −5 0 −2 8 5 1 6 0 Matrika D(5) je tudi matrika – rezultat. 4-9 4.2 Problem trgovskega potnika 4.2 Problem trgovskega potnika Dan je graf G = (V, E) z matriko povezav C. Krožna pot v grafu je pot, ki začne v nekem vozlišču in se tam tudi konča, pri čemer vsako vozlišče obiščemo natanko enkrat. Zanima nas najkrajša krožna pot, tj. Hamiltonov cikel. S tem problemom se sreča trgovski potnik, poštar, voznik dostavnega vozila, itd. Če je graf poln, imamo (n − 1)! možnih poti. Torej vseh poti je reda O(nn ). Problem bomo rešili s strategijo dinamičnega programiranja. Brez izgube splošnosti lahko začnemo in končamo pot v vozlišču 1. Predpostavimo, da gremo iz vozlišča 1 najprej v vozlišče k. Krožna pot razpade v povezavo (1, k) in pot iz vozlišča k do vozlišča 1. Pot iz vozlišča k do vozlišča 1 gre skozi vsako vozlišče iz množice V − {1, k} natanko enkrat. Če je optimalna celotna krožna pot, je optimalna tudi pot iz vozlišča k do 1. Definicija 4.1. Označimo z g(i, S) dolžino najkrajše poti, ki začne v i, gre skozi vsa vozlišča v S natanko enkrat in konča v vozlišču 1. S predstavlja poljubno množico vozlišč danega grafa G, samo vozlišča 1 ne sme vsebovati. Moč množice S lahko zavzame naslednje vrednosti: |S| = 0, 1, . . . , n − 1. Največjo moč (|S| = n − 1) ima množica S, ko je S = V − {1}. Torej g(1, V − {1}) označuje dolžino najkrajše poti, ki jo moramo določiti. Zaradi principa optimalnosti lahko zapišemo: g(1, V − {1}) = min {c[1, k] + g(k, V − {1, k})}. 2≤k≤n (4.3) Enačbo 4.3 lahko posplošimo. Predpostavimo, da smo v vozlišču i in še nismo obiskali vozlišča iz množice S. Naj bo vozlišče j prvo vozlišče preko katerega gremo na poti od i v 1. Izbiro prvega vozlišča j določa naslednja Bellmanova enačba: g(i, S) = min{c[i, j] + g(j, S − {j})}, za 1 < i ≤ n, (4.4) j∈S pri čemer S ni prazna množica. Za prazno množico S (|S| = 0) velja: g(i, 0) = c[i, 1], za 1 < i ≤ n. (4.5) Postopek reševanja je naslednji: najprej uporabimo enačbo 4.5 za določitev g(i, 0), tj. ko je |S| = 0, zatem uporabimo enačbo 4.4 za določitev g(i, S) za vse S velikosti 1, nato določimo g(i, S) za vse S velikosti 2 itd. Postopek nadaljujemo do velikosti |S| = n − 1. Definicija 4.2. Označimo z J(i, S) vrednost za j, ki minimizira desno stran enačbe 4.4. 4-10 4.2 Problem trgovskega potnika V zadnjem koraku najdemo prvo vmesno vozlišče iz vozlišča 1, tj. vozlišče J(1, V − 1). Drugo vmesno vozlišče najdemo iz rezultata predzadnjega koraka: J(J(1, V −1), V −1−J(J(1, V −1)). Na podoben način rekonstruiramo optimalmo krožno pot. Zgled 4.3. Naj bo matrika povezav v 0 3 C= 1 6 polnem grafu s 4 vozlišči: 5 11 4 0 6 2 . 8 0 7 4 9 0 (4.6) 1. Izvedimo 1. korak procedure, ko je |S| = 0. Uporabimo enačbo 4.5: g(2, 0) = c[2, 1] = 3, g(3, 0) = c[3, 1] = 1, g(4, 0) = c[4, 1] = 6. 2. Izvedimo 2. korak procedure, ko je |S| = 1. Uporabimo enačbo 4.4: g(2, {3}) = c[2, 3] + g(3, 0) = 6 + 1 = 7, J = 3, g(2, {4}) = c[2, 4] + g(4, 0) = 2 + 6 = 8, J = 4, g(3, {2}) = c[3, 2] + g(2, 0) = 8 + 3 = 11, J = 2, g(3, {4}) = c[3, 4] + g(4, 0) = 7 + 6 = 13, J = 4, g(4, {2}) = c[4, 2] + g(2, 0) = 4 + 3 = 7, J = 2, g(4, {3}) = c[4, 3] + g(3, 0) = 9 + 1 = 10, J = 3. 3. Izvedimo 3. korak procedure, ko je |S| = 2. Uporabimo enačbo 4.4: g(2, {3, 4}) = = g(3, {2, 4}) = = g(4, {2, 3}) = = min {c[2, 3] + g(3, {4}), c[2, 4] + g(4, {3})} j∈{3,4} min {6 + 13, 2 + 10} = 12, j∈{3,4} J = 4, min {c[3, 2] + g(2, {4}), c[3, 4] + g(4, {2})} j∈{2,4} min {8 + 8, 7 + 7} = 14, j∈{2,4} J = 4, min {c[4, 2] + g(2, {3}), c[4, 3] + g(3, {2})} j∈{2,3} min {4 + 7, 9 + 11} = 11, j∈{2,3} J = 2. 4-11 4.2 Problem trgovskega potnika 4. Izvedimo 4. korak procedure, ko je |S| = 3. Uporabimo enačbo 4.3: g(1, {2, 3, 4}) = min {c[1, 2] + g(2, {3, 4}), c[1, 3] + g(3, {2, 4}), j∈{2,3,4} c[1, 4] + g(4, {2, 3})} = min {5 + 12, 11 + 14, 4 + 11} = 15, j∈{2,3,4} J = 4. Korak 4 nam določi dolžino najkrajše krožne poti v danem grafu (ta je 15) in številko vozlišča, preko katerega začnemo najkrajšo krožno pot, torej J(1, {2, 3, 4}) = 4. Preostalo pot določimo iz poti, ki začne v vozlišču 4 in gre preko vozlišč 2 in 3. Uporabimo rezultat iz koraka 3, ko smo računali minimum g(4, {2, 3}). Ta se zgodi, če gremo najprej preko vozlišča 2 (J(4, {2, 3}) = 2). Preostane nam še obisk vozlišča 3 in vrnitev v vozlišče 1. Najkrajša pot je torej pot: 1 → 4 → 2 → 3 → 1. ♦ Časovna in prostorska zahtevnost Po daljši izpeljavi lahko določimo naslednjo zgornjo mejo časovne zahtevnosti [Kozak, 1997]: T (n) = O(n2 2n ). (4.7) To je sicer bolje kot O(nn ), vendar še vedno ostaja eksponentna zahtevnost. Pri algoritmih s strategijo dinamičnega programiranja ni zanemarljiva niti prostorska zahtevnost: S(n) = O(n2n ). (4.8) 4-12 4.3 Optimalna dvojiška iskalna drevesa 4.3 Optimalna dvojiška iskalna drevesa Če gradimo prevajalnik, gradimo program, ki ga bomo uporabili mnogokrat. Še tako nepomemben prihranek se bo množil z velikim številom uporab. V tem razdelku bomo spoznali tvorbo optimalnega dvojiškega iskalnega drevesa, ki zahteva v povprečju najmanj časa. Kot že vemo, so simboli v dvojiškem iskalnem drevesu urejeni: a[1] < a[2] < . . . < a[n]. Podajmo naslednje vrednosti: • p[i], i = 1, 2, . . . , n, je verjetnost, da je iskani element x enak i-ti elementu v drevesu (x = a[i]), • q[i], i = 0, 1, 2, . . . , n, je verjetnost, da iskanega elementa x ni v drevesu in da smo ga iskali med dvema elementoma (a[i] < x < a[i + 1]), • a[0] = −∞, spodnja straža in • a[n + 1] = ∞, zgornja straža. Ker lahko element x zavzame vse vrednosti v intervalu [−∞, ∞], velja po teoriji verjetnosti: n n X X p[i] + q[i] = 1. (4.9) i=1 i=0 Verjetnosti p[i] in q[i] morajo biti znane. Naša naloga je, da na osnovi teh podatkov zgradimo iskalno dvojiško drevo, ki zahteva za iskanje v povprečju najmanj časa. Iz podatkovnih struktur že vemo, če iščemo element a[i], ki se nahaja na nivoju nivo(a[i]), potrebujemo nivo(a[i]) + 1 primerjav. V povprečju je strošek iskanja za vozlišče a[i] enak: p[i](nivo(a[i]) + 1). (4.10) Neuspešna iskanja se končajo na vozliščih z[i] (i = 0, 1, 2, . . . , n), ki jih predstavimo s kvadratki v dvojiškem iskalnem drevesu kot liste drevesa (slika 4.3). z[i] predstavlja vse simbole x, za katere velja a[i] < x < a[i + 1], (i = 0, 1, 2, . . . , n). Če je z[i] na nivoju nivo(z[i]), potrebujemo samo nivo(z[i]) primerjav. V povprečju je strošek iskanja za vozlišče z[i] enak: q[i]nivo(z[i]). (4.11) Strošek dvojiškega iskalnega drevesa z n vozlišči a[i] je: c[0, n] = n X i=1 p[i](nivo(a[i]) + 1) + n X i=0 q[i]nivo(z[i]). (4.12) 4.3 Optimalna dvojiška iskalna drevesa 4-13 Slika 4.3: Dvojiško iskalno drevo. Okrogla vozlišča opisujejo elemente a[i], kvadratna vozlišča pa vozlišča z[i]. Če strošek c[0, n] uspemo minimizirati, dobimo optimalno dvojiško iskalno drevo (optimal binary search tree). Zgled 4.4. Možna dvojiška drevesa za množico simbolov a = [a[1] a[2] a[3]] = [f or if while] so prikazana na sliki 4.4. 4.3 Optimalna dvojiška iskalna drevesa Slika 4.4: Možne oblike dvojiškega iskalnega drevesa. 4-14 4-15 4.3 Optimalna dvojiška iskalna drevesa · · ¸ ¸ 1 1 1 1 1 1 1 in q = 7 7 7 7 7 7 7 so stroški c[0, 3], ki jih izračunamo po enačbi 4.12, opisani v preglednici 4.1. Najmanjši strošek ima drevo na sliki 4.4b. Če so vsi simboli a[i] enako verjetni, tj. p = Preglednica 4.1: Stroški c[0, 3] posameznih dreves s slike 4.4. drevo c[0, 3] a 15 7 b 13 7 c 15 7 d 15 7 e 15 7 Če so verjetnosti p = [0.5 0.1 0.05] in q = [0.15 0.1 0.05 0.05], dobimo stroške drevesa, ki jih lahko vidimo v preglednici 4.2. Najmanjši strošek ima drevo na sliki 4.4c. ♦ Preglednica 4.2: Stroški c[0, 3] posameznih dreves s slike 4.4. drevo c[0, 3] a 2.65 b 1.9 c 1.5 d 2.05 e 1.6 S Tij (0 ≤ i < j ≤ n) označimo drevo z minimalnim stroškom za podmnožico elementov a[i + 1], a[i + 2], . . . , a[j]. S c[i, j] označimo njegov strošek, z r[i, j] pa indeks korena drevesa. Drevo Tij ima težo (weight) w[i, j], ki je definirana kot: w[i, j] = q[i] + (p[i + 1] + q[i + 1]) + . . . + (p[j] + q[j]). (4.13) Optimalno drevo Tij sestoji iz korena a[k] in iz dveh optimalnih poddreves, levega poddrevesa Ti,k−1 in desnega poddrevesa Tkj (slika 4.5). Levo optimalno drevo Ti,k−1 ima elemente a[i + 1], a[i + 2], . . . , a[k − 1], desno optimalno drevo Tkj pa elemente a[k + 1], a[k + 2], . . . , a[j]. Če je i = k − 1, ni levega poddrevesa, če je k = j, ni desnega poddrevesa. Torej, Tii je prazno podrevo. Teža praznega drevesa je: w[i, i] = q[i]. (4.14) Strošek praznega drevesa je (po enačbi 4.12): c[i, i] = 0. (4.15) Strošek drevesa Tij je gotovo odvisen od stroškov obeh poddreves. Iz enačbe 4.12 sklepamo, da je strošek odvisen od nivoja posameznih elementov v drevesu. V 4.3 Optimalna dvojiška iskalna drevesa 4-16 Slika 4.5: Dvojiško drevo Tij . drevesu Tij je globina obeh poddreves povečana za 1, kar se gotovo mora odraziti na strošku. Zato lahko zapišemo naslednjo rekurzivno enačbo: c[i, j] = c[i, k − 1] + c[k, j] + w[i, j]. (4.16) Enačba 4.16 nam pove, da je strošek drevesa Tij sestavljen iz stroškov obeh poddreves, povečanih za korekcijski člen zaradi povečane globine. Ta korekcijski člen je ravno utež drevesa w[i, j]. Optimalno drevo običajno gradimo iz dveh optimalnih dreves. Za skupno optimalno drevo velja naslednja rekurzivna Bellmanova enačba: c[i, j] = min {c[i, k − 1] + c[k, j]} + w[i, j]. i<k≤j (4.17) Indeks korena optimalnega dvojiškega iskalnega drevesa označimo z r[i, j] = k. Podajmo postopek reševanja: 1. Na začetnem koraku v korenih poddreves ni imen, i = j, torej dvojiška dreveso so le vozlišča z[i], tj. prazna drevesa. Zanje velja: w[i, i] = q[i], c[i, i] = 0, r[i, i] = 0, za i = 0, 1, 2, . . . , n. (4.18) 2. V drugem koraku imamo drevesa z enim vozliščem, za katere veljajo naslednje enačbe: w[i, i + 1] = q[i] + p[i + 1] + q[i + 1], c[i, i + 1] = q[i] + p[i + 1] + q[i + 1] = w[i, i + 1], r[i, i + 1] = i + 1, za i = 0, 1, 2, . . . , n − 1. (4.19) 4-17 4.3 Optimalna dvojiška iskalna drevesa 3. V naslednjih korakih bomo uporabljali naslednje enačbe: w[i, j] = p[j] + q[j] + w[i, j − 1] c[i, j] = min {c[i, k − 1] + c[k, j]} + w[i, j], i<k≤j r[i, j] = k, za i = 0, 1, 2, . . . , n − j + i. (4.20) Zgled 4.5. Dane so verjetnosti: · Imejmo 3¸ simbole·a = [if f or while]. ¸ 4 2 1 2 3 2 1 p= in q = . 15 15 15 15 15 15 15 Da si olajšamo računanje, odpravimo ulomke, saj to ne vpliva na sestavo optimalnega drevesa: p0 = 15[4 2 1] in q0 = 15[2 3 2 1]. Rezultate izračunov bomo zapisovali in hranili v preglednici 4.3. Indeks m (m = 0, 1, 2, 3) pove, koliko simbolov je vključenih v drevo. Preglednica 4.3: V predalu preglednice so zapisani trije podatki optimalnega drevesa Ti,i+m : w[i, i + m], c[i, i + m], r[i, i + m]. m\i 0 1 2 3 0 T00 : 2, 0, 0 T01 : 9, 9, 1 T02 : 13, 20, 1 T03 : 15, 28, 1(2) 1 T11 : 3, 0, 0 T12 : 7, 7, 2 T13 : 9, 13, 2 T22 T23 2 : 2, 0, 0 : 4, 4, 3 T33 3 : 1, 0, 0 Za vrstico z m = 0 uporabimo enačbe 4.18. Za vrstico z m = 1 koristimo enačbe 4.19: w[0, 1] = q[0] + p[1] + q[1] = 2 + 4 + 3 = 9, c[0, 1] = w[0, 1] = 9, r[0, 1] = 1. w[1, 2] = q[1] + p[2] + q[2] = 3 + 2 + 2 = 7, c[1, 2] = w[1, 2] = 7, r[1, 2] = 2. w[2, 3] = q[2] + p[3] + q[3] = 2 + 1 + 1 = 4, c[2, 3] = w[2, 3] = 4, r[2, 3] = 3. 4.3 Optimalna dvojiška iskalna drevesa 4-18 Za vrstico z m = 2 koristimo enačbe 4.20: w[0, 2] = p[2] + q[2] + w[0, 1] = 2 + 2 + 9 = 13, c[0, 2] = = min {c[0, 0] + c[1, 2], c[0, 1] + c[2, 2]} + w[0, 2] 0<k≤2 min {0 + 7, 9 + 0} + 13 = 20, 0<k≤2 r[0, 2] = 1. w[1, 3] = p[3] + q[3] + w[1, 2] = 1 + 1 + 7 = 9, c[1, 3] = = min {c[1, 1] + c[2, 3], c[1, 2] + c[3, 3]} + w[1, 3] 1<k≤3 min {0 + 4, 7 + 0} + 9 = 13, 1<k≤3 r[1, 3] = 2. Za vrstico z m = 3 uporabimo tudi enačbe 4.20: w[0, 3] = p[3] + q[3] + w[0, 2] = 1 + 1 + 13 = 15, c[0, 3] = = min {c[0, 0] + c[1, 3], c[0, 1] + c[2, 3], c[0, 2] + c[3, 3]} + w[0, 3] 0<k≤3 min {0 + 13, 9 + 4, 20 + 0} + 15 = 28, 0<k≤3 r[0, 3] = 1. Isti rezultat bi dobili, če bi vzeli koren pri elementu z indeksom 2. Upoštevali bomo samo prvo rešitev. Slabost dinamičnega programiranja je v tem, da najdemo samo eno rešitev. Iz rezultatov posameznih korakov sestavimo končno rešitev. Koren drevesa T03 je simbol a[1]. Torej, levo poddrevo je T00 , desno poddrevo pa T13 (slika 4.6a). Levo poddrevo T00 je prazno poddrevo. Poddrevo T13 ima dve vozlišči in za koren simbol a[2] (razberemo iz podatkov za T13 v preglednici 4.3). Torej, levo poddrevo je T11 (tj. prazno poddrevo), desno poddrevo pa je T23 (slika 4.6b). Poddrevo T23 je poddrevo z enim samim vozliščem a[3]. Končno obliko optimalnega dvojiškega drevesa prikazuje slika 4.6. ♦ 4.3 Optimalna dvojiška iskalna drevesa Slika 4.6: Optimalno dvojiško drevo T03 . 4-19 4.3 Optimalna dvojiška iskalna drevesa 4-20 Napišimo psevdokod algoritma OPTIMALNO-DVOJISKO-DREVO: OPTIMALNO-DVOJISKO-DREVO(n,a,p,q,T ) % inicializacija praznih dreves in dreves z enim vozliščem 1 for i ← 0 to n − 1 2 do [w[i, i] c[i, i] r[i, i]] ← [q[i] 0 0] 3 [w[i, i + 1] c[i, i + 1] r[i, i + 1]] ← [q[i] + p[i + 1] + q[i + 1] q[i] + p[i + 1] + q[i + 1] i + 1] % inicializacija praznega drevesa Tnn 4 [w[n, n] c[n, n] r[n, n]] ← [q[n] 0 0] % poišči optimalna drevesa z m vozlišči 5 for m ← 2 to n 6 do for i ← 0 to n − m 7 do j ← i + m 8 [w[i, j] ← w[i, j − 1] + p[j] + q[j] 9 k ← vrednost [t], pri čemer je r[i, j − 1] ≤ t ≤ r[i + 1, j], ki minimizira {c[i, t − 1] + c[t, j]} 10 c[i, j] ← w[i, j] + c[i, k − 1] + c[k, j] 11 r[i, k] ← k Časovna zahtevnost algoritma OPTIMALNO-DVOJISKO-DREVO Izračun c[i, j] zahteva iskanje minimuma izmed m vrednosti, kar pomeni, da ga določimo v času O(m). V eni vrstici v preglednici 4.3 je n − m + 1 različnih c[i, j]. Torej, za izračun vseh c[i, j] v eni vrstici potrebujemo čas: (n − m + 1)O(m) = O(nm − m2 + m) = O(nm − m2 ). Vseh vrstic v preglednici 4.3 je n, če prvo vrstico izpustimo, ker so tam začetni pogoji. Potreben čas je sedaj: n X O(nm − m2 ) = O(n3 ). m=1 Knuth je pokazal, da je časovno zahtevnost mogoče zmanjšati, če skrčimo število kandidatov za koren [Kozak, 1997]. Izbiro k (i < k ≤ j) omejimo z relacijo: r[i, j − 1] ≤ k ≤ r[i + 1, j]. V tem primeru postane časovna zahtevnost O(n2 ). Knuthov prijem je uporabljen tudi v našem algoritmu OPTIMALNO-DVOJISKO-DREVO. 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.