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.