Programowanie dynamiczne (optymalizacja dynamiczna).
Transkrypt
Programowanie dynamiczne (optymalizacja dynamiczna).
Programowanie dynamiczne (optymalizacja dynamiczna). W wielu przypadkach zadania, których złożoność wynikająca z pełnego przegl ą du jest du ż a (zwykle wyk ł adnicza) mo ż na rozwi ą za ć w czasie wielomianowym stosując metodę optymalizacji dynamicznej. Programowanie dynamiczne - metoda znajdowania rozwiązań problemów optymalizacyjnych podobna do metody „dziel i zwyciężaj”- w metodzie tej zadany problem dzielimy na podproblemy, rozwiązujemy je, a następnie łączymy rozwiązania podproblemów. •metoda „dziel i zwyciężaj” - wielokrotnie rozwiązuje ten sam problem, wykonuje więc dużo więcej pracy •programowanie dynamiczne - algorytm oparty na tej metodzie rozwiązuje każdy podproblem tylko jeden raz zapamiętując wynik, unika się w ten sposób wielokrotnych obliczeń dla tego samego podproblemu Barbara Marszał-Paszek Rozpatrzmy rekurencyjny algorytm obliczający ciąg Fibonacciego: Fib(0)= 0 Fib(1)=1 Fib(n)= Fib(n-1)+Fib(n-2) dla n≥2 int Fib( int n ) { if( n < 1 ) return 0; if( n == 1 ) return 1; return Fib(n-1) + Fib(n-2); } [Fib(5)] = Fib(4) + Fib(3) [Fib(4)] = Fib(3) + Fib(2) [Fib(3)] = Fib(2) + Fib(1) [Fib(3)] = Fib(2) + Fib(1) [Fib(2)] = Fib(1) + Fib(0) [Fib(2)] = Fib(1) + Fib(0) [Fib(2)] = Fib(1) + Fib(0) Barbara Marszał-Paszek Algorytm rekurencyjny mo ż na poprawi ć wykorzystuj ą c tak zwane "zapamiętywanie". Wykorzystujemy w tym celu tablicę zawierającą już obliczone wartości: int fib[MAX]; // inicjujemy ją, aby oznaczyć wszystkie wartości jako "nieznane" for( i= 0; i < MAX; i++ ) fib[i]= -1; int Fib( int n }{ if ( n < 1 ) return 0; if ( n == 1 ) return 1; if ( fib[n] == -1 ) fib[n]= Fib(n-1) + Fib(n-2); return fib[n]; } Barbara Marszał-Paszek Etapy programowania dynamicznego • • • • Scharakteryzowanie struktury optymalnego rozwiązania Rekurencyjne zdefiniowanie kosztu optymalnego rozwiązania. Obliczenie optymalnego kosztu metodą wstępującą. Konstruowanie optymalnego rozwiązania na podstawie wcześniejszych obliczeń. Problem: Dla danego ciągu n macierzy <A1, A2, ..., An>, gdzie dla i=1, 2, ..., n macierz Ai ma wymiar pi-1 x pi, wyznaczyć taką kolejność mnożenia macierzy A1· A2 ·...·An aby koszt obliczeń był jak najmniejszy. Barbara Marszał-Paszek Pseudokod standardowego algorytmu mnożenia dwóch macierzy MNOŻENIE_MACIERZY(A,B) { if ile_kolumn[A] ≠ ile_wierszy[B] then error else { for i=1 to ile_wierszy[A] for j=1 to ile_kolumn[B] do { C[i,j]:=0 for k=1 to ile_kolumn[A] do C[i,j]:=C[i,j]+A[i,k]·B[k,j] } return C } } Barbara Marszał-Paszek Obserwacja: Czas obliczeń jest zdeterminowany przez ilość mnożeń skalarnych. Jeśli A i B są macierzami o rozmiarze odpowiednio p x q i q x r, to ilość mnożeń wynosi pqr. Przykład: A1 o rozmiarze 10 x 100 A2 o rozmiarze 100 x 5 A3 o rozmiarze 5 x 50 Ile mnożeń skalarnych? • ((A1A2)A3)10 x 100 x 5 + 10 x 5 x 50 = 5000 + 2500 = 7500 • (A1(A2A3) 100 x 5 x 50 + 10 x 100 x 50 = 25000 + 50000=75000 Barbara Marszał-Paszek Struktura optymalnego rozwiązania Oznaczenie: Ai..j=Ai⋅Ai+1…·Aj Obserwacja: Umieszczenie nawiasów w podci ą gu A 1 A 2 … A k w optymalnym nawiasowaniu A1A2… An jest optymalnym nawiasowaniem A1A2… Ak. Dowód: Gdyby istniało nawiasowanie A1A2… Ak o niższym koszcie, to w połączeniu z optymalnym nawiasowaniem A1A2… An otrzymalibyśmy nawiasowanie A1A2… Ak o koszcie niższym czyli sprzeczność. Barbara Marszał-Paszek Zdefiniowanie rekurencyjne kosztu optymalnego. Podproblemy: wyznaczenie optymalnego nawiasowania iloczynów Ai⋅Ai+1…·Aj gdzie 1 ≤ i ≤ j ≤ n Oznaczenie: m[i, j] – minimalna liczba mnożeń skalarnych niezbędnych do obliczenia macierzy Ai..j Oznaczenie: s[i, j] – wartość k, która realizuje minimalną wartość m[i, j] Niech dane wejściowe stanowi ciąg <p0, p1, ...,pn> rozmiarów macierzy, gdzie d ł ugo ś ć [p]=n+1, u ż ywamy macierzy m[1..n,1..n] oraz s[1..n,1..n] do zapamiętywania kosztów oraz indeksów k dających optymalny podział Barbara Marszał-Paszek MACIERZE_KOSZT(p) n:=długość[p]-1 for i=1 to n do m[i, i]:=0 for w=2 to n do { for i=1 to n-w+1 do { j=i+w-1 m[i,j]:=∞ for k=i to j-1 do { q:=m[i,k]+m[k+1,j]+pi-1pkpj if q<m[i,j] then { m[i,j]:=q, s[i,j]:=k } } } } return (m, s) Barbara Marszał-Paszek Konstruowanie optymalnego rozwiązania MNOŻENIE_ŁAŃCUCHA_MACIERZY(A, s, i, j) if j>i then X:= MNOŻENIE_ŁAŃCUCHA_MACIERZY(A, s, i, s[i, j]) Y:= MNOŻENIE_ŁAŃCUCHA_MACIERZY(A, s, s[i, j]+1, j) return MNOŻENIE_MACIERZY(X, Y) else return Ai Barbara Marszał-Paszek Charakterystyczne elementy metody programowania 1.Problem wykazuje optymalną podstrukturę, jeśli jego optymalne rozwiązanie jest funkcją optymalnych rozwiązań podproblemów; metoda dowodzenia jest następująca: zakładamy, że jest lepsze rozwiązanie podproblemu, po czym pokazujemy, iż to założenie zaprzecza optymalności rozwiązania całego problemu 2.„Rozsądnie mała” przestrzeń istotnie różnych podproblemów. Mówimy, że problem ma w ł asno ś ć wspólnych podproblemów, je ś li algorytm rekurencyjny wielokrotnie oblicza rozwiązanie tego samego podproblemu bo algorytmy oparte na programowaniu dynamicznym rozwiązują dany podproblem tylko jeden raz i zapamiętują gotowe rozwiązania, które potem wystarczy odczytać w stałym czasie. Jeśli problem wykazuje te własności warto spróbować, czy daje się stosować metoda programowania dynamicznego. Barbara Marszał-Paszek PROBLEM NAJDŁUŻSZEGO WSPÓLNEGO PODCIĄGU (Problem LCS) Problem znalezienia Najdłuższego Wspólnego Podciągu (LCS – Longest Common Subsequences) pojawia się w kilku praktycznych zastosowaniach : 1.Poszukiwanie ”podobieństwa” dwóch pary nici DNA jako jednej z miar stopnia pokrewieństwa odpowiadających im organizmów 2.Wy ł apywanie plagiatów muzycznych poprzez analiz ę ich linii melodycznych 3.Porównywanie zawartości plików 4.Kryptografii Najefektywniejsza metoda jego rozwiązania opiera się na programowaniu dynamicznym. Barbara Marszał-Paszek Dane są ciągi: X = <x1, x2, ..., xm> i Y = <y1, y2, ..., yn>. Problem: Znaleźć najdłuższy wspólny podciąg ciągów X i Y. Jak działa algorytm wyczerpujący? • • • • Bierzemy krótszy z ciągów (załóżmy, że jest to X), generujemy wszystkie podciągi ciągu X, sprawdzamy, który podciąg jest też podciągiem Y, zapamiętujemy najdłuższy podciąg. Ile jest podciągów ciągu X? Każdy podciąg ciągu X jest jednoznacznie wyznaczony przez pewien podzbiór zbioru {1, 2, ..., m}, zatem ilość podciągów jest równa ilości podzbiorów zbioru {1, 2, .., m}, czyli 2m. Czyli razem ze sprawdzeniem O(2mn). Wniosek: algorytm wyczerpujący ma złożoność wykładniczą !. Barbara Marszał-Paszek Struktura optymalnego rozwiązania Na szczęście ten problem ma własność optymalnej podstruktury i naturalna przestrzeń podproblemów odpowiada w tym przypadku parom ”prefiksów” ciągów wejściowych. Oznaczenie: Niech X = <x1, x2, ..., xm> i Xi = <x1, x2, ..., xi> dla i=0,1,2,...,m będzie i-tym prefiksem ciągu X, przy czym Xo jest ciągiem pustym. Twierdzenie (o optymalnej podstrukturze NWP) Niech X = <x1, x2, ..., xm> i Y= <y1, y2, ..., yn> oraz niech Z = <z1, z2, ..., zk> będzie NWP ciągów X i Y. Wtedy: • jeśli xm = yn, to zk= xm=yn oraz Zk-1 jest NWP(Xm-1, Yn-1) • jeśli xm ≠ yn i zk ≠ xm, to Z jest NWP(Xm-1 ,Y), • jeśli xm ≠ yn i zk ≠ yn, to Z jest NWP(X, Yn-1). Barbara Marszał-Paszek Rekurencyjne zdefiniowanie kosztu optymalnego rozwiązania Obserwacja: Z twierdzenia wynika, że aby obliczyć NWP(X, Y) trzeba rozwiązać jeden lub dwa podproblemy: •jeżeli xm = yn, to znajdujemy NWP(Xm-1, Yn-1), a następnie dołączając xm=yn otrzymujemy NWP(X, Y) •jeżeli xm ≠ yn, to znajdujemy NWP(Xm-1, Y) oraz NWP(X, Yn-1), a następnie wybierając dłuższy z nich otrzymujemy NWP(X, Y) Barbara Marszał-Paszek Oznaczenie: c[i, j] – długość NWP(Xi, Yj) Uwaga: jeśli i=0 lub j=0, to c[i, j]=0 Wniosek: Barbara Marszał-Paszek Obliczanie długości NWP Obserwacja 1: Jest O(mn) rożnych podproblemów problemu NWP. Dowód: X ma długość m, Y ma długość n Obserwacja 2: Problem NWP ma własność wspólnych podproblemów. Dowód: Aby znaleźć NWP(X, Y) możemy potrzebować NWP(Xm-1, Y) oraz NWP(X, Yn-1). Oba te podproblemy mogą zawierać wspólny podproblem znalezienia NWP(Xm-1, Yn-1). Wniosek: Można stosować programowanie dynamiczne. dane wejściowe: ciągi X i Y c[1.. m, 1.. n ] - przechowuje wartości c[i, j] b[1.. m, 1.. n] - b[i, j] wskazuje na pole w tablicy c odpowiadające wybranemu podczas obliczeń c[i, j] optymalnemu rozwiązaniu podproblemu Barbara Marszał-Paszek NWP_Długość(X,Y){ m:=długość(X) n:=długość(Y) for i:=1 to m do c[i,0]:=0 for j:=1 to n do c[0,j]:=0 for i:=1 to m for j:=1 to n if xi=yjthen { c[i, j]:=c[i-1, j-1]+1; b[i, j]:=„↖” } else if c[i-1, j] ≥ c[i, j-1] then { c[i, j]:=c[i-1, j]; b[i, j]:=„↑” } else { c[i, j]:=c[i, j-1]; b[i, j]:=„←” } return c i b } Barbara Marszał-Paszek Przykład: X = <A, B, C, B, D, A, B> Y = <B, D, C, A, B, A> Rozwiązanie: <B, C, B, A> Barbara Marszał-Paszek Konstrukcja NWP na podstawie wcześniejszych obliczeń Procedura Drukuj_NWP - wypisuje NWP(X, Y) korzystając z tablicy b, każda strzałka typu „↖” w polu b[i, j] oznacza, że xi=yj należy do NWP Drukuj_NWP(b, X, Y, i, j) if i=0 lub j=0 then return; if b[i, j]=„” then { Drukuj_NWP(b, X, Y, i-1, j-1); wypisz xi } else if b[i, j]=„↑” then Drukuj_NWP(b, X, Y, i-1, j) else Drukuj_NWP(b, X, Y, i, j-1); Obserwacja: Czas działania wynosi O(m*n) Barbara Marszał-Paszek Inne rozwiązania algorytmiczne tego samego problemu. Wagner i Fisher 1974 zaproponowa ł rozwi ą zanie problemu LCS z wykorzystaniem programowania dynamicznego przedstawiony wcześniej o rozwiązaniu O(mn). Hirschberg 1975 zaproponował metodę dziel i zwycię żaj w oparciu o programowanie dynamiczne o złożoności czasowej O(n2) ale o pamięciowej tylko O(n). Hirschberg 1977 – O(pn) gdzie p to długość LCS Hunt i Szymański 1977 – O(rlogn) gdzie r liczba wszystkich dopasowań. Należy zwrócić uwagę, że te dwa algorytmy są efektywne dla p i r małych. Niestety w najgorszym przypadku gdy p=n a r=n2 algorytmy osiągają wartości O(n2) i O(n2logn) gorsze od standardowej metody opartej na programowaniu dynamicznym. Masek i Paterson 1980 zaproponowali algorytm O(n2/logn). Barbara Marszał-Paszek Allison i Dix 1986 (lub Crochemore 2001) zaproponowali algorytm oparty na bitowej równoległości o złożoności obliczeniowej gdzie w to rozmiar słowa maszynowego. Np. na 32-bitowej maszynie uzyskano szybszy o ok. 30 razy, na 64- bitowej maszynie uzyskano szybszy o ok. 55 razy. Barbara Marszał-Paszek