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

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

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ž.

Podobné prezentace


Prezentace na téma: "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ž."— Transkript prezentace:

1 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í. (c) Pavel Töpfer, 2006 Programování - 23

2 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“). (c) Pavel Töpfer, 2006 Programování - 23

3 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(N2) – tedy dvourozměrné pole. V každém případě jsou polynomiální. (c) Pavel Töpfer, 2006 Programování - 23

4 Č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: N2 x N = N3  časová složitost O(N3) (c) Pavel Töpfer, 2006 Programování - 23

5 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 (c) Pavel Töpfer, 2006 Programování - 23

6 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í: (c) Pavel Töpfer, 2006 Programování - 23

7 Postup řešení 1. Neefektivní: - uložit všechna čísla do pole  paměť O(N2) - backtrackingem postupně zkoušet všechny cesty  čas O(2N) 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ě N2 čísel, každé určíme v konstantním čase  časová složitost O(N2) (c) Pavel Töpfer, 2006 Programování - 23

8 Dokonalé naplnění vozidla Máme N předmětů známých hmotností
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. (c) Pavel Töpfer, 2006 Programování - 23

9 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. (c) Pavel Töpfer, 2006 Programování - 23

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

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

12 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 Zk na A + Zk-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ů. Pk = číslo předmětu, který jsme naposledy naložili do auta nosnosti k při postupném výpočtu hodnot Zk(i) - mění se vždy při změně hodnoty Zk: když Zk(i) > Zk(i-1), pak Pk := i Z hodnot uložených v poli P se odzadu od PC zpětně zrekonstruuje způsob zaplnění C-tého auta. (c) Pavel Töpfer, 2006 Programování - 23

13 Floyd-Warshallův algoritmus Máme ohodnocený graf bez záporných cyklů (mohou v něm být záporně ohodnocené hrany). A = ( ai, j ) – matice vzdáleností velikosti N x N ai, 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(N2) = O(N3) - 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(N2), časová složitost O(N3) (c) Pavel Töpfer, 2006 Programování - 23

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

15 Efektivita algoritmu: Časová složitost O(N3) – 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(N2) - 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ší“) (c) Pavel Töpfer, 2006 Programování - 23

16 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; (c) Pavel Töpfer, 2006 Programování - 23

17 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 = ( pi, j ) velikosti N x N, jejíž prvky budeme přepočítávat zároveň s maticí U: pi, 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 (c) Pavel Töpfer, 2006 Programování - 23

18 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; (c) Pavel Töpfer, 2006 Programování - 23

19 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 pi, 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 pi, 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; (c) Pavel Töpfer, 2006 Programování - 23

20 Součin matic Počítáme součin matic A1. A2. …
Součin matic Počítáme součin matic A1 . A2 . … . AN Ú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 Ai má rozměry Pi-1 x Pi pro i = 1, …, N Vstupní data pro úlohu: N, P0, P1, …, PN (c) Pavel Töpfer, 2006 Programování - 23

21 Příklad: N=3, zadané rozměry matic násobíme tedy matice A A A3 rozměrů x x x 3 existují dvě možná uzávorkování ( A A2 ) A3 rozměry x x 3 počet násobení celkem A ( A A3 ) rozměry x x 3 počet násobení celkem 60 (c) Pavel Töpfer, 2006 Programování - 23

22 Ř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 AK . AK+1 . … . AL . 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) PK-1 . Pi . PL ) (c) Pavel Töpfer, 2006 Programování - 23

23 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} (c) Pavel Töpfer, 2006 Programování - 23

24 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(N2) a výpočet každé z nich má časovou složitost O(N)  časová složitost algoritmu O(N3) Realizace: buď „chytrá rekurze“ (tzn. doplnění rekurzivního algoritmu polem), nebo výpočet zdola bez rekurze dynamickým programováním (c) Pavel Töpfer, 2006 Programování - 23

25 Řešení dynamickým programováním: Mi, j = minimální počet násobení potřebný k výpočtu součinu i po sobě jdoucích matic počínaje maticí Aj tzn. Aj . Aj … . Aj + i Hodnoty Mi, j jsou definovány pro každé i od 1 do N, vždy pro j od 1 do N-i+1. M1, j = 0 pro všechna j od 1 do N MN, 1 = výsledek (minimální počet násobení čísel potřebný k vynásobení všech N zadaných matic) Mi, 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 (c) Pavel Töpfer, 2006 Programování - 23

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

27 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; (c) Pavel Töpfer, 2006 Programování - 23

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

29 Minimální triangulace Konvexní N-úhelník A1 A2 … AN (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ří. (c) Pavel Töpfer, 2006 Programování - 23

30 Ř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. Mi, 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 Aj , tzn. mnohoúhelníka Aj Aj + 1 … Aj + i - 1 , zvětšená navíc o velikost úsečky Aj Aj + i – 1 Indexy vrcholů zde chápeme cyklicky, tzn. AN+1 = A1, AN+2 = A2 , atd. (c) Pavel Töpfer, 2006 Programování - 23

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

32 Výpočet Mi, j : - každá triangulace i-úhelníka Aj Aj + 1 … Aj + i - 1 musí obsahovat dvojici úseček Aj Aj + k a Aj + k Aj + i - 1 pro nějaké k od 1 do i-2 (neboť musí existovat právě jeden trojúhelník se stranou Aj Aj + i - 1, ten má třetí vrchol nutně v nějakém bodě Aj + 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ů Mi, j = min ( Mk + 1, j + Mi - k, j + k ) + délka (Aj Aj + i - 1 ) k=1,…,i-2 Efektivita algoritmu: - paměťová složitost O(N2) – matice M velikosti N x N - časová složitost O(N3) – výpočet každého prvku matice M má složitost O(N) (c) Pavel Töpfer, 2006 Programování - 23

33 Určení seznamu diagonál: - použijeme druhé pole D[1. N, 1
Určení seznamu diagonál: - použijeme druhé pole D[1..N, 1..N] - hodnoty Di, j určujeme zároveň s hodnotami Mi, j - Di, j = k, pomocí něhož je určeno minimální Mi, j - seznam diagonál použitých v minimální triangulaci zrekonstruujeme zpětně z obsahu pole D počínaje od hodnoty DN-1, x , pokud MN-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 (c) Pavel Töpfer, 2006 Programování - 23


Stáhnout ppt "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ž."

Podobné prezentace


Reklamy Google