Programowanie dynamiczne Ciąg Fibonacciego fib(0)=1 fib(1)=1 fib(n)=fib(n-1)+fib(n-2), gdzie n 2 Elementy tego ciągu stanowią liczby naturalne tworzące ciąg o takiej własności, że kolejny wyraz (z wyjątkiem dwóch pierwszych) jest sumą dwóch poprzednich (tj. 1,1,2,3,5, 8,13, ). Obliczanie czwartego elementu ciągu Fibonacciego fib(4) fib(3) fib(2) fib(2) fib(1) fib(1) fib(0) fib(1) fib(0) Każde pogrubione wyrażenie stanowi problem elementarny. Problem o rozmiarze n 2 zostaje rozbity na dwa problemy o mniejszym stopniu skomplikowania: n-1 i n-2. Proces dekompozycji zatrzymuje się na przypadkach elementarnych. Zaimplementuj powyższy algorytm. Wada Można dostrzec, że znaczna część obliczeń jest wykonywana więcej niż jeden raz (np. cała gałęź zaczynająca się od fib(2) jest wręcz zdublowana). Z powyższą wadą radzi sobie programowanie dynamiczne. koncepcja: dla danego problemu stwórz rekurencyjny model jego rozwiązania (wraz z jednoznacznym określeniem przypadków elementarnych). Stwórz tablicę w której będzie można zapamiętywać rozwiązania przypadków elementarnych i rozwiązania podproblemów, które zostaną obliczone na ich podstawie. inicjacja: wpisz do tablicy wartości, które odpowiadają przypadkom elementarnym progresja: na podstawie wartości wpisanych do tablicy używając formuły rekurencyjnej oblicz rozwiązanie problemu wyższego rzędu i wpisz je do tablicy. Postępuj w ten sposób aż do uzyskania pożądanej wartości.
Ciąg Fibonacciego przy użyciu programowania dynamicznego koncepcja: wzór rekurencyjny jest znany, więc pozostaje zadeklarować tablicę fib[n] do przechowywania obliczanych wartości. inicjacja: początkowymi wartościami w tablicy fib będą oczywiście warunki początkowe: fib[0]=1 oraz fib[1]=1 progresja: ten punkt zależy ściśle od wzoru rekurencyjnego, który implementujemy za pomocą tablicy. W naszym przypadku wartością fib[i] w tablicy (dla 2 i) jest suma dwóch poprzednio obliczonych wartości: fib[i-1] oraz fib[i-2]. Zaimplementuj powyższy algorytm. Współczynnik dwumianowy Newtona =!!! dla 0 k n Dla n i k, które nie są małe nie możemy obliczyć współczynnika dwumianowego bezpośrednio z tej definicji, ponieważ wartość n! jest bardzo duża nawet dla średnich wartości n. Możemy zastosować poniższy wzór rekurencyjny = 1 1 + 1 0 < < 1 = 0 = Napisz program obliczania współczynnika dwumianowego przy użyciu podejścia typu dziel i zwyciężaj. Algorytm ten jest bardzo niewydajny, oblicza 2 1 wyrazów w celu obliczenia wartości. Problem polega na tym, że te same realizacje są rozwiązywane w każdym wywołaniu rekurencyjnym. Przykładowo wywołania dwumianowy n-1,k-1 oraz dwumianowy n-1,k wiążą się z obliczeniem wartości dwumianowy n-2,k-1 i realizacja ta jest rozwiązywana niezależnie w każdym wywołaniu. Podejście typu dziel i zwyciężaj nigdy nie jest wydajne, kiedy realizacja jest dzielona na dwie mniejsze realizacje, których rozmiar jest zbliżony do rozmiaru oryginalnej realizacji. Napisz program obliczający współczynnik dwumianowy wykorzystując programowanie dynamiczne. Znamy już właściwość rekurencyjną, więc wykorzystajmy ją do skonstruowania rozwiązania wykorzystującego tablicę B, gdzie B[i][j] będzie zawierać wartość " # $ %.
Etapy tworzenia algorytmu: 1. Określamy właściwość rekurencyjną. Zapisujemy ją w kontekście użycia tablicy B,[# 1][$ 1] +,[# 1][$] 0<$<# B[i][j]=+ 1 $=0 $=# 2. Rozwiązujemy realizację problemu w porządku wstępującym, rozpoczynając od pierwszego wiersza i wyliczając po kolei wartości w wierszach tablicy B. Obliczmy wartość B[4][2]= 4 2 Obliczamy wiersz 0: Etap ten jest wykonywany wyłącznie w celu dokładnego odwzorowania algorytmu. Wartość B[0][0] nie jest potrzebna w dalszych obliczeniach. Obliczamy wiersz 1: B[1][0]=1 B[1][1]=1 Obliczamy wiersz 2: B[2][0]=1 B[2][1]=B[1][0]+B[1][1]=1+1=2 B[2][2]=1 Obliczamy wiersz 3: B[3][0]=1 B[3][1]=B[2][0]+B[2][1]=1+2=3 B[3][2]=B[2][1]+B[2][2]=2+1=3 Obliczamy wiersz 4 B[4][0]=1 B[4][1]=B[3][0]+B[3][1]=1+3=4 B[4][2]=B[3][1]+B[3][2]=3+3=6
W przykładzie tym obliczaliśmy po kolei coraz większe wartości współczynnika dwumianowego. W każdym przebiegu wartości potrzebne do wykonania bieżących działań były już obliczane i zachowane. Łańcuchowe mnożenie macierzy Rozważmy obliczenie iloczynu n macierzy M = M 1 M 2... M n, gdzie każde M i jest macierzą mającą r i-1 wierszy i r i kolumn. Bez względu na to jaki algorytm do macierzy stosujemy istotny wpływ na całkowitą liczbę operacji potrzebnych do policzenia M ma kolejność mnożenia macierzy. Przykład Przyjmujemy, że mnożenie macierzy rozmiaru p x q przez macierz rozmiaru q x r wymaga p q r operacji. Chcemy obliczyć iloczyn M = M 1 M 2 M 3 M 4 o wymiarach odpowiednio [10 x 20], [20 x 50], [50 x 1], [1 x 100], Obliczmy M wg schematu M = M 1 (M 2 (M 3 M 4 )) M34 = M 3 M4 = 50 1 100 = 5000 - koszt mnożenia, wymiar powstałej macierzy M34 [50 x 100] M 234 = M 2 M34= 20 50 100 = 100000- koszt mnożenia, wymiar macierzym 234 to [20 x 100] M 1 M 234 = 10 20 100 = 20000 - koszt mnożenia 5000 + 100000 + 20000 = 125000 - całkowity koszt mnożenia Policzmy teraz M dla schematu
Chcemy otrzymać optymalną kolejność mnożenia na podstawie powyższej tablicy s. W tym celu najpierw odwiedzamy element s[1][n], ( w naszym przykładzie n=4). Więc s[1][4]=3, co oznacza, że optymalna kolejność dla mnożenia macierzy od 3 4 do 3 5 posiada rozkład (3 4 3 6 3 7 )3 5 (s[1][4] czyli 3 jest punktem, w którym macierze powinny zostać rozdzielone w celu otrzymania czynników). Następnie chcemy określić jak wygląda rozkład dla mnożenia macierzy od 3 4 do 3 7. Zatem sprawdzamy ile wynosi s[1][3]. Widzimy, że s[1][3]=1. Więc rozkład od 3 4 do 3 7 przybiera następującą postać 3 4 (3 6 3 7 ). Zatem wiemy, że poprawna kolejność nawiasów jest następująca ((3 4 (3 6 3 7 ))3 5. Zaimplementuj powyższy algorytm łańcuchowego mnożenia macierzy.