1 of 62

Dinamikus Programozás2022.09.27

2 of 62

Ismétlés

3 of 62

Oszd meg és uralkodj

  • Divide & Conquer (,,Oszd meg és uralkodj”) paradigma

4 of 62

Oszd meg és uralkodj

  • Felosztás: hogyan osztjuk a feladatot több részfeladatra

  • Uralkodás: a részfeladatokat rekurzív módon megoldjuk. Ha a részfeladatok mérete elég kicsi, akkor közvetlenül megoldja a részfeladatokat.

  • Összevonás: a részfeladatok megoldásait összevonjuk az eredeti feladat megoldásává

PL: vállalat adója ->részleg->munkások

5 of 62

Rekurzió

  • Faktoriális számítás: n! = n· (n - 1) · … · 2 · 1

  • Rekurzív összefüggés: n! = n · (n - 1)!

  • Alapeset: n = 1

  • Rekurzív eset: n > 1: n · (n - 1)!

6 of 62

Fibonacci

  • Keressük meg a Fibonacci sorozat (1, 1, 2, 3, 5, 8, 13...) N. elemét

  • Alapeset:

az első és a második elem értéke 1.

  • Rekurzív eset:

az N. elem az N-1 - edik és az N-2 - dik elemek összege.

7 of 62

Fibonacci

def fibo( n ) :

if (( n == 1 ) || (n == 0)):

return 1

else

return fibo(n-1) + fibo(n-2)

8 of 62

Lépcső

n=4

9 of 62

Lépcső

  • P(n) = P(n − 1) + P(n − 2), ha n ≥ 3 (utolsó lépésként egyet vagy kettőt léphetünk)

def P( n ) :

if ( n == 1 )

return 1

elseif ( n == 2 )

return 2

else

return P( n−1) + P( n−2)

10 of 62

Dinamikus programozás

11 of 62

Dinamikus programozás

Az eddigiekben egy-egy összetett feladat megoldására oszd-meg-és-uralkodj megoldást használtunk.

A rekurzív megoldás legnagyobb hátránya azonban az, hogy legtöbb esetben, ahogy növekszik az elemszámunk, vele együtt hatványozottan növekszik a megoldáshoz szükséges idő hossza és a lépések száma is.

12 of 62

Dinamikus programozás

Ennek a problémának a kiküszöbölésére ad hatékony megoldást a dinamikus programozás.

Ebben a tananyagban egy másik, szintén részproblémára bontáson alapuló, algoritmus családdal, a dinamikus programozással ismerkedünk meg.

13 of 62

Dinamikus programozás

A rekurzív algoritmusok sokszor azért lassúak, mert bizonyos részproblémákat többször is kiszámolnak.

Tipikusan ez történik, amikor optimalizálási feladatunk van és a megoldás kedvezményeket lépésről lépésre építjük fel.

14 of 62

Dinamikus programozás

Ezt úgy tudjuk elkerülni, ha az egyszer már kiszámolt rész megoldásokat eltároljuk, és később újra felhasználjuk azokat.

A több időt valójában most több tárra „cseréljük”, melynek eredményeként a rekurzív programok időkomplexitása drasztikusan csökkenthető.

15 of 62

Dinamikus programozás

A dinamikus programozás a részproblémákra bontás gyengeségeit két fő módszerrel próbálja orvosolni:

Az egyik módszer lényege, hogy programunk futása alatt

  • a megoldott rész problémák (optimális) megoldását eltároljuk,
  • ha ugyanezen részprobléma megoldására szükségünk lenne később, csak kiolvassuk a megoldást,
  • nem oldjuk meg újra a részfeladatot

(TÁBLÁZATKITÖLTÉS módszere)

16 of 62

Dinamikus programozás

A második módszer:

Általában amikor a részproblémákat többször is szükséges megoldani, akkor az összes lehetséges „kis” részproblémát ki kell számolni, hogy az eredeti nagy probléma megoldása kiszámítható legyen.

17 of 62

Dinamikus programozás

Ezért a dinamikus programozásban tipikusan:

  • a legkisebb problémától indulunk,
  • minden egyes nagyobb részprobléma megoldását kiszámítjuk a kisebb részfeladatok megoldásainak felhasználásával,
  • egészen addig míg az eredeti/legnagyobb feladatig el nem jutunk

(LENTRŐL FELFELÉ ÉPÍTKEZÉS módszere).

18 of 62

Dinamikus programozás

Vegyük észre, hogy ez ellentétes az oszd-meg-és-uralkodj algoritmusok filozófiájával, ahol mindig az eredeti problémát bontjuk kisebb és kisebb problémákra

(FELÖLRŐL LEFELÉ ÉPÍTKEZÉS módszere)

19 of 62

Dinamikus programozás

A Dinamikus programozás gyakorlatilag „ugyanaz” mint a oszd meg és uralkodj módszer/rekurzió.

Ám még előzőekben sok egymásba ágyazás/időigény mellett dolgoztunk, most a memóriában történő tároláson van a hangsúly.

20 of 62

Dinamikus programozás

Nézzünk meg egy példát:

Rendezzük a következő számsort 8, 4, 1, 5, 3, 2, 6, 7 !

Próbáljunk oszd meg és uralkodj logikával gondolkodni

21 of 62

Dinamikus programozás

Összefésülő (merge)rendezés:

A tömböt két részre vágjuk (lehetőleg fele-fele arányban), a részeket külön-külön rendezzük (rekurzió!), majd a rendezett részeket összefésüljük.

22 of 62

Dinamikus programozás

Probléma: Rekurzív algoritmusok -> többször is kiszámolnak.

Megoldás: A részmegoldások -> eltároljuk es újra felhasználjuk.

A több időt több tárra ,,cseréljük" -> exponenciális idő -> polinomidő

Ehhez viszont el kell tároljuk a rész problémák megoldását.

23 of 62

Dinamikus programozás

A megoldás lépései általában:

Adjuk meg a rekurzív megoldást (alap- és rekurzív eset).

A rekurzió alapján építsük fel ,,lentről felfelé”: az algoritmus kezdjen az alapesettel, majd a teljes megoldásig egy jó sorrendben számolja ki a rész problémákat.

24 of 62

Fibonacci

def fib( n ) :

if (( n == 2 ) || (n == 1)):

result = 1

else

result = fib(n-1) + fib(n-2)

return result

25 of 62

Táblázatkitöltés

A részeredmények (TÁBLÁZATKITÖLTÉS) tárolásának módszere:

A részproblémára bontási algoritmusokat kibővítjük egy- vagy kétdimenziós tömbbel, melyet a részeredmények tárolására használunk fel.

26 of 62

Táblázatkitöltés

Amikor egy részproblémát kell megoldanunk először megvizsgáljuk, hogy a keresett érték megtalálható-e a részeredmények között.

Ha megtalálható, akkor felhasználjuk, egyébként kiszámoljuk, majd eltároljuk.

27 of 62

Fibonacci

def fib( n ) :

if (( n == 2 ) || (n == 1)):

result = 1

else

result = fib(n-1) + fib(n-2)

return result

Rendkívül gyenge a hatásfoka, nagyon sokáig fut.

Nézzünk egy példát: fib(5)

28 of 62

  • Nézzük meg, hogyan nézne ki a példánk, ha eltárolnánk az értékeket.

Fibonacci

29 of 62

  • Nézzük meg, hogyan nézne ki a példánk, ha eltárolnánk az értékeket.

Fibonacci

30 of 62

  • Nézzük meg, hogyan nézne ki a példánk, ha eltárolnánk az értékeket.

1

Fibonacci

31 of 62

  • Nézzük meg, hogyan nézne ki a példánk, ha eltárolnánk az értékeket.

1 1 2

Fibonacci

32 of 62

  • Nézzük meg, hogyan nézne ki a példánk, ha eltárolnánk az értékeket.

1 1 2 4

Fibonacci

33 of 62

  • Nézzük meg, hogyan nézne ki a példánk, ha eltárolnánk az értékeket.

1 1 2 4 5

Fibonacci

34 of 62

  • Sok számot többször ki kell számolnunk, emiatt sok az ismételt lépés.

  • 5 számnál ez nem tűnhet nagy valaminek, de ennek a megoldásnak a növekedése

  • A dinamikus programozás elve pedig azt „kérdezi”, hogy miért nem tároljuk el az ismétlődő értékeket.

Fibonacci

35 of 62

def fib( n, memo ) :

if memo[n] != null:

return memo[n]

if (( n == 2 ) || (n == 1)):

result = 1

else

result = fib(n-1) + fib(n-2)

memo[n] = result

return result

  • Nézzük meg, hogyan nézne ki a példánk, ha eltárolnánk az értékeket.

memo = 1 1 2 3

T(n) = 2n + 1 …… O(n)

Fibonacci

36 of 62

def fib( n, memo ) :

if memo[n] != null:

return memo[n]

if (( n == 2 ) || (n == 1)):

result = 1

else

result = fib(n-1) + fib(n-2)

memo[n] = result

return result

  • fib(5)

memo =

Fibonacci

37 of 62

def fib( n, memo ) :

if memo[n] != null:

return memo[n]

if (( n == 2 ) || (n == 1)):

result = 1

else

result = fib(n-1) + fib(n-2)

memo[n] = result

return result

  • fib(5)

memo =

Fibonacci

38 of 62

def fib( n, memo ) :

if memo[n] != null:

return memo[n]

if (( n == 2 ) || (n == 1)):

result = 1

else

result = fib(n-1) + fib(n-2)

memo[n] = result

return result

  • fib(5)

memo = 1

Fibonacci

39 of 62

Fibonacci

def fib( n, memo ) :

if memo[n] != null:

return memo[n]

if (( n == 2 ) || (n == 1)):

result = 1

else

result = fib(n-1) + fib(n-2)

memo[n] = result

return result

  • fib(5)

memo = 1 1

Fibonacci

40 of 62

def fib( n, memo ) :

if memo[n] != null:

return memo[n]

if (( n == 2 ) || (n == 1)):

result = 1

else

result = fib(n-1) + fib(n-2)

memo[n] = result

return result

  • fib(5)

memo = 1 1 2

Fibonacci

41 of 62

def fib( n, memo ) :

if memo[n] != null:

return memo[n]

if (( n == 2 ) || (n == 1)):

result = 1

else

result = fib(n-1) + fib(n-2)

memo[n] = result

return result

  • fib(5)

memo = 1 1 2 3 5

Fibonacci

42 of 62

Alulról felfelé építkezés

A TÁBLÁZATKITÖLTÉSEN alapuló dinamikus programozási megoldásnál még gyorsabb lehet, ha nem FELÜLRŐL LEFELÉ, rekurzív módon töltjük ki a táblázatot, hanem az ALULRÓL FELFELÉ ÉPÍTKEZÉS módszerével:

43 of 62

Alulról felfelé építkezés

-Hozzuk létre a megfelelő méretű táblázatot, amiben minden cella egy része problémának felel meg.

-Töltsük az alapesetek megoldásait.

-„alulról felfelé” az alapesetekből kiindulva, a teljes megoldásig számoljuk ki a részproblémák megoldásait a már korábban kiszámolt megoldások alapján

44 of 62

Lépcső

Jelölje P(n) azt a számot, hányféleképpen mehetünk fel egy n lépcsőfokból álló lépcsőn, ha egyszerre csak 1 vagy 2 lépcsőfokot léphetünk.

Adjunk egy dinamikus programozási eljárást a P(n) érték kiszámítására!

P(6) = ?

45 of 62

Lépcső

Az előző órán a rekurzív algoritmushoz megadtuk az összefüggéseket:

Alapeset:

  • P(1) = 1 (ha egy lépcső van, egyféleképpen mehetünk)
  • P(2) = 2 (ha két lépcső van, vagy kétszer egyet lépünk, vagy egyszer kettőt)

Rekurzív eset:

  • P(n) = P(n − 1) + P(n − 2), ha n ≥ 3 (utolsó lépésként egyet vagy kettőt léphetünk)

46 of 62

Lépcső

Ezek alapján a következő dinamikus programozási eljárást adhatjuk meg, ahol egy T tömböt töltünk ki, T[i] a P(i) értékét tartalmazza:

47 of 62

Lépcső

Nézzünk egy példát: egy 8 magas lépcsőre szeretnénk felmenni.

Sok lépést spórolunk meg az előző megoldáshoz képest!

48 of 62

Melyik a jobb…?

Melyik a jobb megoldás?

49 of 62

Melyik a jobb…?

Melyik a jobb megoldás?

50 of 62

Sakk

Jelölje R(k; n) azt a számot, hányféleképpen juthatunk egy k x n méretű sakktábla bal alsó sarokból a jobb felső sarkába, ha csak a jobbra vagy a felfelé szomszédos mezőre léphetünk. Adjunk meg két dinamikus programozási eljárást (egyet négyzetes, egyet lineáris táblázatkitöltéssel) az R(k; n) érték kiszámítására!

R(3,4) = ?

51 of 62

Sakk

Megoldás:

Az előző órán megadtuk a rekurzív algoritmusokhoz az összefüggéseket.

A következő összefüggések állnak fent:

Alapeset:

  • R(k, 1) = 1 (csak felfelé mehetünk)
  • R(1, n) = 1 (csak jobbra mehetünk)

Rekurzív eset:

  • R(k, n) = R(k, n−1)+R(k−1, n), ha n, k ≥ 2 (az első lépés jobbra vagy felfelé történhet)

52 of 62

Sakk

Ezek alapján a következő dinamikus programozási eljárást adhatjuk meg, T[i, j] az R(i, j) értékét tartalmazza:

53 of 62

Sakk

54 of 62

Táblázat

Feladat

Adott egy k×n-es tábla. Minden mezőre meg van adva egy Cij pozitív szám, ami

a mezőről begyűjthető érték.

Egy játékos a bal alsó sarokból szeretne eljutni a jobb felső sarokba úgy, hogy csak jobbra és felfelé léphet a szomszédos mezőre.

Az útja során összegyűjtheti a mezőkről az értékeket.

Mennyi értéket tudunk összeszedni maximálisan?

Hogyan határoznánk meg ezt az utat, amin a maximális értéket szedhetjük össze?

55 of 62

Táblázat

Megoldás

Az előző algoritmust annyiban kell módosítanunk, hogy nem a lépések számát, hanem az addig összeszedhető maximum értéket kell eltároljuk.

56 of 62

Táblázat

Sakk vs Pontok

57 of 62

Pénzváltás

Oldjuk meg a pénzváltási feladat alábbi változatát a következő értékekre:

P1=1, P2=5, P3=7,

F=11

Adottak különböző pénzérmék korlátlan mennyiségben és egy összeg.

Adjuk meg, hogy összesen hány különböző módon lehet felváltani az összeget a megadott pénzérmékkel!

58 of 62

Pénzváltás

1. eset: Pn-t bevesszük és az F-Pn összes felváltási lehetőségét tekintjük.

2.eset: Pn-t nem vesszük bele és F összes felváltási lehetőségét tekintjük a többi érmével.

C(P,n,F)=C(P,n,F-Pn)+C(P,n-1,F)

59 of 62

Pénzváltás

1,5,7

1

1

1

1

1

1,5

1

1

1

1

1

1

1

1

1

1

1

1

1

1

1

1

1

1

0

1

2

3

4

5

6

7

8

9

10

11

C(P,n,F)=C(P,n,F-Pn)+C(P,n-1,F)

1+1+1+1+…..

60 of 62

Pénzváltás

1,5,7

1

1

1

1

1

2

2

3

3

3

4

4

1,5

1

1

1

1

1

2

2

2

2

2

3

3

1

1

1

1

1

1

1

1

1

1

1

1

1

0

1

2

3

4

5

6

7

8

9

10

11

1+1+1+1+…..

1+1+1+1+1+1

5 + 1

1+1+1+1+1+1+1+1+1+1

5 + 1+1+1+1+1

5+5

61 of 62

Pénzváltás

62 of 62

Ajánlott kitekintő