Prezentace se nahrává, počkejte prosím

Prezentace se nahrává, počkejte prosím

(c) Pavel Töpfer, 2006Programování - 231 Dynamické programování Programovací technika pro řešení takových optimalizačních úloh, u nichž platí, že úloha.

Podobné prezentace


Prezentace na téma: "(c) Pavel Töpfer, 2006Programování - 231 Dynamické programování Programovací technika pro řešení takových optimalizačních úloh, u nichž platí, že úloha."— Transkript prezentace:

1 (c) Pavel Töpfer, 2006Programování Dynamické programování Programovací technika pro řešení takových optimalizačních úloh, u nichž platí, že úloha se dá rozložit na menší podúlohy, z jejichž optimálních řešení se skládá optimální řešení celé původní úlohy. Slouží k tvorbě časově efektivních algoritmů, zpravidla za cenu jistého zvýšení paměťových nároků - čas i paměť polynomiální (nahradí backtracking s exponenciální časovou složitostí). Princip: Rozklad úlohy na menší podúlohy stejného charakteru, z jejichž řešení se sestaví řešení původní úlohy – podobné jako u techniky „Rozděl a panuj“. Na rozdíl od techniky „Rozděl a panuj“ nemusejí být podúlohy navzájem nezávislé  širší možnosti použití.

2 (c) Pavel Töpfer, 2006Programování Postup: Na rozdíl od „Rozděl a panuj“ se obvykle nepoužívá rekurzivní rozklad úlohy shora, ale postupuje se naopak zdola od elementárních úloh ke složitějším. Výsledky řešení všech podúloh se přitom ukládají (zpravidla do pole) pro další použití při řešení složitějších podúloh. Tak se postupuje, dokud se nevyřeší celá původní úloha. Lze postupovat také rekurzivním rozkladem shora a do pomocného pole si při tom průběžně ukládat všechny spočítané hodnoty, aby se nepočítaly opakovaně znovu (viz „chytrá rekurze“).

3 (c) Pavel Töpfer, 2006Programování Paměťové nároky: Potřebujeme paměť navíc pro uložení výsledků všech dílčích podúloh – při iteračním postupu zdola dokonce často i těch, které nakonec nebudeme vůbec potřebovat (ale předem nevíme, které dílčí výsledky později potřebovat budeme a které ne, takže si počítáme a ukládáme systematicky všechny). Tyto dodatečné paměťové nároky mohou být velikosti O(N), velmi často bývají O(N 2 ) – tedy dvourozměrné pole. V každém případě jsou polynomiální.

4 (c) Pavel Töpfer, 2006Programování Časová efektivita: Díky ukládání výsledků všech menších podúloh máme zajištěno, že se žádná podúloha nebude počítat opakovaně vícekrát – může se jen vícekrát použít její výsledek uložený již jednou v poli. Proto nemusejí být podúlohy na sobě navzájem nezávislé a přesto je zaručena polynomiální časová složitost výpočtu. Počet provedených operací = počet řešených podúloh (počet prvků pole určeného pro uložení všech dílčích výsledků) x náročnost řešení každé jednotlivé podúlohy (s využitím toho, že už známe výsledky podúloh menších). Příklad: N 2 x N = N 3  časová složitost O(N 3 )

5 (c) Pavel Töpfer, 2006Programování Příklady Už známe: Fibonacciho čísla Určení N-tého Fibonacciho čísla rekurzivní funkcí – chybně použitý princip „Rozděl a panuj“, vznikající podúlohy byly závislé  exponenciální časová složitost Určení N-tého Fibonacciho čísla postupným počítáním zdola v cyklu – princip dynamického programování, nic se nepočítá opakovaně vícekrát  lineární časové složitost

6 (c) Pavel Töpfer, 2006Programování Trojúhelník z čísel N řádků, obsahují 1, 2, 3, …, N čísel – zadán na vstupu Příklad pro N=5: Úkol: nalézt cestu z vrcholu na základnu s maximálním součtem čísel, smí se jít vždy jen šikmo směrem dolů. Řešení:

7 (c) Pavel Töpfer, 2006Programování Postup řešení 1. Neefektivní: - uložit všechna čísla do pole  paměť O(N 2 ) - backtrackingem postupně zkoušet všechny cesty  čas O(2 N ) 2. Dynamické programování: - načíst a uložit vždy jen jeden řádek čísel, spočítat a uložit maximální součet čísel na cestě vedoucí do každého z nich  paměť O(N) [některé z těchto údajů se ve výsledku neuplatní, ale ještě nevíme které, tak počítáme a ukládáme všechny] - další řádek spočítat vždy z předchozího, každé číslo má jen dva možné předchůdce a součty maximálních cest do nich vedoucích máme uloženy - výsledkem je maximální spočítaná hodnota v posledním řádku - počítáme tedy řádově N 2 čísel, každé určíme v konstantním čase  časová složitost O(N 2 )

8 (c) Pavel Töpfer, 2006Programování Dokonalé naplnění vozidla Máme N předmětů známých hmotností. Lze z nich vybrat skupinu předmětů se součtem hmotností rovným přesně dané hodnotě C ? Tzn. lze na vozidlo nosnosti C naložit některé z předmětů tak, aby bylo auto plně vytíženo? Poznámka: Hmotnosti všech předmětů i výsledná hmotnost C jsou celá čísla nepřevyšující předem známou hodnotu Max (lze jimi tedy indexovat, jinak by to byla mnohem těžší úloha). Postup řešení: Sledujeme maximální možné zaplnění fiktivních „aut“ o nosnostech 1,…,C pomocí prvních i předmětů. Bereme jeden předmět za druhým (v libovolném pořadí) a zaplnění každého z aut vždy zkoušíme zlepšit. Po zpracování všech N předmětů známe optimální zaplnění auta C, což nám dává požadovaný výsledek.

9 (c) Pavel Töpfer, 2006Programování Otázka: Proč jsme uvažovali všechna ta menší auta, když ta vůbec neexistují a nikdo se nás na ně neptal? Odpověď: To je právě dynamické programování – s jejich pomocí snáze (a hlavně efektivněji) dokážeme přepočítávat optimální zaplnění cílového auta, když postupně přibývají předměty.

10 (c) Pavel Töpfer, 2006Programování Z k (i) = maximální zaplnění auta nosnosti k pomocí prvních i předmětů nechť i-tý předmět má hmotnost A Z k (i) = Z k (i-1) … pokud A > k (i-tý předmět se na k-té auto nevejde) Z k (i) = max (Z k (i-1), A + Z k-A (i-1) ) … pokud A <= k   i-tý předmět i-tý předmět nepoužijeme použijeme

11 (c) Pavel Töpfer, 2006Programování Složitost: Paměť – stačí jedno pole velikost C, v něm se přepočítávají hodnoty Z k, a to odzadu (!!!) – k výpočtu Z k (i) potřebujeme znát hodnoty Z m (i-1) pro m <= k. Čas – postupně N-krát přepočítat maximálně C hodnot v poli. Výsledek: Z C (N) = maximální zaplnění kapacity C pomocí všech N předmětů. Pokud je tato hodnota rovna C, existuje dokonalé zaplnění.

12 (c) Pavel Töpfer, 2006Programování Výběr předmětů: Existuje-li dokonalé zaplnění, které předměty máme vybrat? 1. Přímočarý, ale paměťově náročný postup: U každého „auta“ si stále pamatujeme seznam čísel předmětů, pomocí nichž jsme docílili zatím nejlepšího zaplnění. Při každé změně hodnoty Z k na A + Z k-A (i-1) (použili jsme i-tý předmět) zkopírujeme seznam naložených předmětů od (k-A)-tého auta a přidáme k němu i. Nakonec máme u výsledného auta i seznam naložených předmětů. 2. Postup s lineární pamětí: Druhé pole P velikosti C, do něhož ukládáme pořadová čísla předmětů. P k = číslo předmětu, který jsme naposledy naložili do auta nosnosti k při postupném výpočtu hodnot Z k (i) - mění se vždy při změně hodnoty Z k :když Z k (i) > Z k (i-1), pak P k := i Z hodnot uložených v poli P se odzadu od P C zpětně zrekonstruuje způsob zaplnění C-tého auta.

13 (c) Pavel Töpfer, 2006Programování Floyd-Warshallův algoritmus Máme ohodnocený graf bez záporných cyklů (mohou v něm být záporně ohodnocené hrany). A = ( a i, j ) – matice vzdáleností velikosti N x N a i, j = délka hrany (i, j) pro i, j = 1, …, N Úkol: nalézt nejkratší vzdálenosti mezi všemi dvojicemi vrcholů Postup řešení 1. Postupně z každého z N vrcholů grafu spustit Dijkstrův algoritmus - časová složitost N x O(N 2 ) = O(N 3 ) - lze použít jen v případě nezáporného ohodnocení hran 2. Floyd-Warshallův algoritmus (Floydův algoritmus) - pracuje na principu dynamického programování - paměťová složitost O(N 2 ), časová složitost O(N 3 )

14 (c) Pavel Töpfer, 2006Programování Zavedeme matici U = ( u i, j ), jejíž hodnoty budeme postupně v N iteracích přepočítávat (číslo iterace uvádíme v horním indexu): u i, j (m) = délka nejkratší cesty z vrcholu i do vrcholu j vedoucí pouze přes vrcholy z množiny {1, …, m} Postup: u i, j (0) = a i, j - přímo vstupní hodnoty (triviální) u i, j (m) - počítáme postupně pro m = 1, …, N u i, j (N) - požadovaný výsledek (triviální) Zbývá ukázat, jak spočítáme hodnoty u i, j (m+1), když už známe u i, j (m) pro všechny dvojice i, j : u i, j (m+1) = min ( u i, j (m), u i, m+1 (m) + u m+1, j (m) )   vrchol m+1 nepoužijemevrchol m+1 použijeme (právě 1 x)

15 (c) Pavel Töpfer, 2006Programování Efektivita algoritmu: Časová složitost O(N 3 ) – všechny prvky matice U velikosti N x N musíme N-krát přepočítat, každou tuto hodnotu spočítáme v konstantním čase. Paměťová složitost O(N 2 ) - při realizaci přesně podle definice potřebujeme dvě verze matice U, abychom si nepřepisovali hodnoty, které ještě budeme potřebovat - ve skutečnosti přepisování nevadí a stačí provádět výpočet v jedné matici (místo „správné“ hodnoty občas při výpočtu použijeme hodnotu dokonce už „lepší“)

16 (c) Pavel Töpfer, 2006Programování Implementace: for i:=1 to N do for j:=1 to N do if existuje_hrana(i,j) then U[i,j]:=delka_hrany(i,j) else U[i,j]:=NEKONECNO; for m:=1 to N do for i:=1 to N do for j:=1 to N do begin x:=U[i,m]+U[m,j]; if x < U[i,j] then U[i,j]:=x end;

17 (c) Pavel Töpfer, 2006Programování Určení cesty: Co když nám nestačí délky nejkratších cest, ale chceme také vědět, kudy nejkratší cesta vede? Zavedeme druhou matici P = ( p i, j ) velikosti N x N, jejíž prvky budeme přepočítávat zároveň s maticí U: p i, j = nejvyšší číslo vrcholu ležícího na dosud nejkratší nalezené cestě z vrcholu i do vrcholu j pro všechna i, j = 1, …, N

18 (c) Pavel Töpfer, 2006Programování for i:=1 to N do for j:=1 to N do if existuje_hrana(i,j) then U[i,j]:=delka_hrany(i,j) else U[i,j]:=NEKONECNO; for m:=1 to N do for i:=1 to N do for j:=1 to N do begin x:=U[i,m]+U[m,j]; if x < U[i,j] then begin U[i,j]:=x; P[i,j]:=m end end;

19 (c) Pavel Töpfer, 2006Programování Po skončení výpočtu matic U, P dokážeme z obsahu matice P určit, kudy vede nejkratší cesta mezi libovolnou dvojicí vrcholů i, j : - pokud p i, j = 0, nepoužili jsme při hledání nejkratší cesty z i do j žádný jiný vrchol grafu a nejkratší cestou je tedy přímá hrana mezi těmito dvěma vrcholy - pokud p i, j  0, získáme seznam vrcholů tvořících nejkratší cestu z vrcholu i do vrcholu j voláním následující procedury procedure Cesta(i,j: word); var m: word; begin m:=P[i,j]; if m <> 0 then begin Cesta(i,m); write(m); Cesta(m,j) end end;

20 (c) Pavel Töpfer, 2006Programování Součin matic Počítáme součin matic A 1. A 2. …. A N Úkol: vhodným uzávorkováním minimalizovat počet vykonaných elementárních násobení čísel (máme tedy nalézt ono nejlepší uzávorkování, nikoli spočítat součin matic). Pozorování: nechť matice B má rozměry P x Q nechť matice C má rozměry Q x R  součin matic B.C má rozměry P x R a k jeho výpočtu je zapotřebí vykonat P.Q.R násobení čísel Nechť v naší úloze matice A i má rozměry P i-1 x P i pro i = 1, …, N Vstupní data pro úlohu: N, P 0, P 1, …, P N

21 (c) Pavel Töpfer, 2006Programování Příklad: N=3, zadané rozměry matic násobíme tedy maticeA 1. A 2. A 3 rozměrů 2 x 5 5 x 2 2 x 3 existují dvě možná uzávorkování ( A 1. A 2 ). A 3 rozměry 2 x 2 2 x 3 počet násobení 20 12celkem 32 A 1. ( A 2. A 3 ) rozměry 2 x 5 5 x 3 počet násobení 30 30celkem 60

22 (c) Pavel Töpfer, 2006Programování Řešení backtrackingem - systematické zkoumání všech možných uzávorkování daného součinu matic  exponenciální časová složitost. Rekurzivní funkce PocetNasobeni (K, L) určuje minimální počet násobení čísel potřebný k určení součinu matic A K. A K+1. …. A L. Funkce vyzkouší všechny možnosti, které z naznačených L-K násobení lze vykonat jako poslední, pro každou takovou možnost určí počet provedených násobení čísel a z nich určí minimum: PocetNasobeni (K, L) = min ( PocetNasobeni (K, i) + i = K, …, L-1 PocetNasobeni (i+1, L) + P K-1. P i. P L )

23 (c) Pavel Töpfer, 2006Programování function PocetNasobeni(K, L: integer): integer; var Poc: integer; {k určení počtu násobení} Min: integer; {minimální počet násobení} i: integer; begin if K = L then PocetNasobeni:=0 {úsek tvořen jedinou maticí, žádné násobení se neprovádí} else begin Min:=maxint; for i:=K to L-1 do {"poslední" násobení za i-tou maticí} begin Poc:=PocetNasobeni(K,i) + PocetNasobeni(i+1,L) {násobení v úsecích} + P[K-1] * P[i] * P[L]; {poslední násobení} if Poc < Min then Min:=Poc end; PocetNasobeni:=Min end end; {function PocetNasobeni}

24 (c) Pavel Töpfer, 2006Programování Příčina neefektivity: pro mnohé dvojice K, L se počítá PocetNasobeni (K, L) opakovaně vícekrát Odstranění neefektivity: již jednou spočítané hodnoty PocetNasobeni (K, L) si budeme pamatovat v poli a nebudeme je počítat opakovaně Výsledná složitost: každou hodnotu PocetNasobeni (K, L) budeme počítat jen jednou, těchto hodnot je O(N 2 ) a výpočet každé z nich má časovou složitost O(N)  časová složitost algoritmu O(N 3 ) Realizace: buď „chytrá rekurze“ (tzn. doplnění rekurzivního algoritmu polem), nebo výpočet zdola bez rekurze dynamickým programováním

25 (c) Pavel Töpfer, 2006Programování Řešení dynamickým programováním: M i, j = minimální počet násobení potřebný k výpočtu součinu i po sobě jdoucích matic počínaje maticí A j tzn. A j. A j + 1. …. A j + i - 1 Hodnoty M i, j jsou definovány pro každé i od 1 do N, vždy pro j od 1 do N-i+1. M 1, j = 0 pro všechna j od 1 do N M N, 1 = výsledek (minimální počet násobení čísel potřebný k vynásobení všech N zadaných matic) M i, j ukládáme do matice M[1..N, 1..N] Hodnoty matice počítáme po řádcích (s rostoucím i): první řádek – triviální – samé 0 i-tý řádek (pro i > 1) – spočítáme pomocí hodnot na řádcích 1 až i-1

26 (c) Pavel Töpfer, 2006Programování M i, j = min ( M k, j + M i - k, j + k + P j - 1. P j + k - 1. P j + i - 1 ) k=1,…,i-1   počet maticsoučin součinposlední vlevo od A j... A j + k - 1 A j + k … A j + i – 1 násobení „posledního“ násobení Efektivita algoritmu: - paměťová složitost O(N 2 ) – matice M velikosti N x N - časová složitost O(N 3 ) – výpočet každého prvku matice M má složitost O(N)

27 (c) Pavel Töpfer, 2006Programování Implementace: for j:=1 to N do M[1,j]:=0; for i:=2 to N do {řádky matice M} for j:=1 to N-i+1 do begin Min:=maxint; for k:=1 to i-1 do begin Poc:=M[k,j] + M[i-k,j+k] {násobení v úsecích} + P[j-1] * P[j+k-1] * P[j+i-1]; {poslední násobení} if Poc < Min then Min:=Poc end; M[i,j]:=Min end;

28 (c) Pavel Töpfer, 2006Programování Určení vhodného uzávorkování: - použijeme druhé pole D[1..N, 1..N] - hodnoty D i, j určujeme zároveň s hodnotami M i, j - D i, j = k, pomocí něhož je určeno minimální M i, j - správné uzávorkování zrekonstruujeme zpětně z obsahu pole D počínaje od D N, 1

29 (c) Pavel Töpfer, 2006Programování Minimální triangulace Konvexní N-úhelník A 1 A 2 … A N (N > 3) je zadán kartézským souřadnicemi svých vrcholů v rovině. diagonála = spojnice dvou vrcholů, které spolu nesousedí na obvodu triangulace = soubor neprotínajících se diagonál, které rozdělují plochu N-úhelníka na samé trojúhelníky (existuje více triangulací, každá je tvořena N-3 diagonálami) velikost triangulace = součet délek všech diagonál tvořících triangulaci minimální triangulace = ta z triangulací, která má minimální velikost Úkol: nalézt minimální triangulaci daného N-úhelníka. Nejprve budeme hledat pouze velikost minimální triangulace, později doplníme i určení diagonál, které ji tvoří.

30 (c) Pavel Töpfer, 2006Programování Řešení – dynamické programování Postupujeme zdola od minimálních triangulací malých mnohoúhelníků, z nich sestavujeme minimální triangulace větších a větších mnohoúhelníků, až najdeme minimální triangulaci celého zadaného N-úhelníka. M i, j = velikost minimální triangulace mnohoúhelníka tvořeného i po sobě jdoucími vrcholy na obvodu daného N-úhelníka počínaje vrcholem A j, tzn. mnohoúhelníka A j A j + 1 … A j + i - 1, zvětšená navíc o velikost úsečky A j A j + i – 1 Indexy vrcholů zde chápeme cyklicky, tzn. A N+1 = A 1, A N+2 = A 2, atd.

31 (c) Pavel Töpfer, 2006Programování Hodnoty M i, j jsou definovány pro všechna i od 3 do N-1 a pro všechna j od 1 do N. M 3, j = | A j A j + 2 | pro všechna j od 1 do N M N-1, j = velikost minimální triangulace celého N-úhelníka takové, která obsahuje diagonálu A j A j + N - 2  minimum z těchto hodnot je výsledkem úlohy M i, j ukládáme do matice M[2..N-1, 1..N] Hodnoty matice počítáme po řádcích (s rostoucím i): M 2, j = 0 – triviální (definujeme formálně z technických důvodů) M 3, j = | A j A j + 2 | pro všechna j od 1 do N – plyne přímo z definice i-tý řádek (pro i > 3) – spočítáme pomocí hodnot na řádcích 3 až i-1

32 (c) Pavel Töpfer, 2006Programování Výpočet M i, j : - každá triangulace i-úhelníka A j A j + 1 … A j + i - 1 musí obsahovat dvojici úseček A j A j + k a A j + k A j + i - 1 pro nějaké k od 1 do i-2 (neboť musí existovat právě jeden trojúhelník se stranou A j A j + i - 1, ten má třetí vrchol nutně v nějakém bodě A j + k ) - zkoušíme tedy možnosti pro všechna taková k, velikost triangulace vždy spočítáme z již známých minimálních triangulací menších mnohoúhelníků M i, j = min ( M k + 1, j + M i - k, j + k ) + délka (A j A j + i - 1 ) k=1,…,i-2 Efektivita algoritmu: - paměťová složitost O(N 2 ) – matice M velikosti N x N - časová složitost O(N 3 ) – výpočet každého prvku matice M má složitost O(N)

33 (c) Pavel Töpfer, 2006Programování Určení seznamu diagonál: - použijeme druhé pole D[1..N, 1..N] - hodnoty D i, j určujeme zároveň s hodnotami M i, j - D i, j = k, pomocí něhož je určeno minimální M i, j - seznam diagonál použitých v minimální triangulaci zrekonstruujeme zpětně z obsahu pole D počínaje od hodnoty D N-1, x, pokud M N-1, x je výsledkem úlohy (tzn. minimální prvek na řádku N-1 matice M) - tato fáze výpočtu má lineární časovou složitost


Stáhnout ppt "(c) Pavel Töpfer, 2006Programování - 231 Dynamické programování Programovací technika pro řešení takových optimalizačních úloh, u nichž platí, že úloha."

Podobné prezentace


Reklamy Google