Algorytmy i struktury danych dziel i zwyciężaj programowanie dynamiczne algorytmy zachłanne
Dziel i zwyciężąj Dzielimy problem na podproblemy (najlepiej o zbliżonych rozmiarach); Znajdujemy rozwiązania podproblemów (powtarzając cały algorytm aż do uzyskania jednostkowych problemów); uzyskane wyniki częściowe scalamy. Przykłady – duża liczba alg. rekurencyjnych m.in. sortowanie szybkie; slinia (rekurencja); obliczanie wyrazów ciągu fibonacciego (rekurencja) * * Mniej efektywne niż inne podejścia.
Dziel i zwyciężąj Problemy: Duży koszt scalania rozwiązań podproblemów; Duża ilość jednakowych podproblemów. Fib(n) = Fib(n-1) + Fib(n-2) = Fib(n-2) + Fib(n-3) + Fib(n-3) + Fib(n-4) = Fib(n-3) + Fib(n-4) + Fib(n-3) + Fib(n-3) + Fib(n-4) ….
Programowanie dynamiczne Problemy: Rekurencyjna definicja rozwiązania; Konstrukcja optymalnego rozwiązania metodą począwszy od wyników cząstkowych
Problem plecakowy - dyskretny Sformułowanie: Dla danego zbioru przedmiotów (opisanych przez wagę i cenę) i plecaka o rozmiarze K znaleźć upakowanie plecaka o największej wartości. Dysponujemy dowolną (lub ograniczoną) ilością przedmiotów każdego rodzaju. Przedmiotów nie wolno dzielić. Tj. dla Z = {(c1,w1, m1), (c2,w2,m2), (c3,w3,m3), …, (cn,wn,mn)} znaleźć podzbiór L = (l1, l2, …, ln), taki że å li*wi £ K, li Î C+, li £ mi i å li*ci = max
Problem plecakowy - dyskretny Przykład (bez limitów): Przedmioty (waga, cena): Z = { (1, 1), (2, 1), (3, 11), (4, 16), (5, 24) } Plecak: K = 7 Optymalne upakowanie: KN(K,Z) = 27 Liczby przedmiotów: L = (0, 0, 1, 1, 0)
Problem plecakowy - dyskretny Rozwiązanie: Podproblem – jeżeli podzielimy plecak na dwa mniejsze, to każdy z nich musi być optymalnie upakowany. Wypełnij tablicę KN pomocniczą od 1 do K, wpisując do k-tej komórki KN(k) = max { ci + KN(k-wi); 1 £ i < n } . gdzie K – rozmiar plecaka, n - liczba przedmiotów Oprócz wartości należy (np. w odrębnej tablicy) zapamiętywać jakie elementy zostały zapakowane.
Problem plecakowy - dyskretny Wynik Przedmioty 1 1,0,0,0,0 2 2,0,0,0,0 3 11 0,0,1,0,0 4 16 0,0,0,1,0 5 24 0,0,0,0,1 6 25 1,0,0,0,1 7 27 0,0,1,1,0 8 35 0,0,1,0,1 Nielimitowane przedmioty (1, 1) (2, 1) (3, 11) (4, 16) (5, 24)
Alg. Problem plecakowy class GOOD: weight=0 price=0 def Knapsack (goods,KSize) : KTmp = Array(KSize+1,0) for i in range(1,KSize): KTmp[i] = KTmp[i-1] for j in range(1, n+1): if Z[j].weight >= i and\ Z[j].price + KTmp[i-Z[j].weight] > KTmp[i]: KTmp[i] = Z[j].weight + KTmp[i-Z[j].weight] return KTmp[k]
Mnożenie ciągu macierzy Sformułowanie: Dla danego ciągu macierzy <A1, A2, … An> tak dobrać tak kolejność operacji, aby zminimalizować ilość mnożeń. Reprezentacja danych: m[1, n] = minimalna liczba mnożeń potrzebnych do obliczenia A1* … *An
Prosta implementacja mnożenia dwóch macierzy def MulMatrix(A, B): if Columns(A)!=Rows(B): ERROR else: for i in range(0, Rows(A)): for j in range(0, Columns(A)): C[i][j] = 0; for k in range(0, Columns(B)): C[i][j] += C[i][j]+ A[i][k]*B[k][j] return C
Mnożenie ciągu macierzy Przykład: Dla danego ciągu macierzy <A1, A2, … An> tak dobrać kolejność operacji, aby zminimalizować ilość mnożeń. [p, q] ´ [q, r] = [p, r] O([p, q] ´ [q, r]) = p*q*r A1= [10, 100] A2= [100, 3] A3= [3, 50] O((A1´A2) ´ A3) = 10*100*3 + 10*3*50 = 4500 O(A1 ´ (A2´A3)) = 100*3*50 + 10*100*50 = 65000
Mnożenie ciągu macierzy Rozwiązanie: Podproblem – jeżeli podzielimy wyrażenie na dwa mniejszew miejscu podziłau "najwyższego" pozomu – nawiasowania w obu podwyrażeniach muszą być optymalne. m[i, j] = { min (m[i, k]+m[k+1, j]+pi-1pkpj; i<=k<j } Wypełnij tablicę m[i, j] poczynając od mnożenia par, potem trójek itd. Oprócz wartości należy np. w odrębnej tablicy zapamiętywać jakie nawiasowanie zostało przyjęte za optymalne dla danej sytuacji
Nieoptymalne rozwiązanie def RecursiveMatrixChain(p, i, j): if i == j: return 0 w = -1 for k in (i,j+1): q = p[i-1]*p[k]*p[j]+RecursiveMatrixChain(p,i,k)+\ RecursiveMatrixChain(p[], k+1, j) if q<w: w = q return w RecursiveMatrixChain(p, 1, len-1)
Mnożenie ciągu macierzy j m Rozwiązanie: A1= [30, 35] A2= [35, 15] A3= [15, 5] A4= [5, 10] A5= [10, 20] A6= [20, 25] 6 5 4 3 2 1 1 5000 1000 3500 750 2500 5375 2625 4375 7125 10500 15750 7875 9375 11875 15125 2 i 3 4 5 6 optdiv j 6 5 4 3 2 1 1 5 4 3 2 1 2 i 3 4 5 6
Implementacja wyznaczania optymalnego nawiasowania def MatrixChain(p, len): for i in range(1,len): m[i][j] = 0 for h in range(2,len): for i in range(1,len-h-1): j = i+h-1 m[i][j] = -1 for k in range (i,j+1): tmp = m[i][k]+m[k+1][j] + p[i-1]*p[k]*p[j] if m[i][j] < 0 or tmp < m[i][j]: m[i][j] = tmp optdiv[i][j] = k
Rekur. implementacja wyznaczania optymalnego nawiasowania def RecursiveMatrixChain(p, i, j): if (i == j) return 0 m[i][j] = -1; for k in range(i,j) q = RecursiveMatrixChain(p,i,k)+\ RecursiveMatrixChain(p,k+1,j) + p[i-1]*p[k]*p[j] if m[i][j]<0 or q <= m[i][j]: m[i][j] = q return m[i][j] RecursiveMatrixChain(p[], 1, len-1);
Spamiętywanie Odmiana programowania dynamicznego; Rekurencyjne podejście -> dziel i zwyciężaj; Szybka pamięć dla rozwiązań chwilowych;
Impl. mnożenia ciągu macierzy przy wykorzyst. spamiętywania def MemorizedMatrixChain(p, len): for i in range(1,len): for j in range(1,len): m[i,j] = -1 return LookupMatrixChain(p,1,len-1)
Implementacja LookupMatrixChain def LookupMatrixChain(p, i, j): if m[i][j] >= 0: return m[i][j] if i == j: m[i][j] = 0 else: for k in range(i,j) q = LookupMatrixChain(p,i,k) +\ LookupMatrixChain(p,k+1,j) + p[i-1]*p[k]*p[j] if (q <= m[i][j]): m[i][j] = q return m[i][j]
Inne klasyczne zastosowania programowania dynamicznego Najdłuższy wspólny podciąg; Triangulacja wielokąta z minimalną długością boków.
Algorytmy zachłanne Algorytm jest rozumiany jako ciąg decyzji optymalizacyjnych; W kolejnym kroku wybierane jest najlepsze dostępne lokalnie rozwiązanie; Niestety nie zawsze ta strategia prowadzi do optymalnych rozwiązań całościowych.
Problem plecakowy - ciągły Sformułowanie: Dla danego zbioru przedmiotów (opisanych przez wagę i cenę) i plecaka o rozmiarze K znaleźć upakowanie plecaka o największej wartości. Dysponujemy ograniczoną ilością przedmiotów każdego rodzaju. Przedmioty MOŻNA dzielić. Tj. dla Z = { (c1,w1,m1), (c2,w2,m2), (c3,w3,m3) … (cn,wn,mn) } znaleźć podzbiór L = (l1, l2, ..., ln), taki że å li*wi £ K, li Î R+, li £ mi i å li*ci = max
Problem plecakowy - ciągły Przykład (bez limitów): Przedmioty: Z = { (3, 1), (60, 10), (80, 15), (210, 30), (270, 45) } Plecak: K = 45 Optymalne upakowanie: KN(K, Z) = 315 Liczby przedmiotów: L = (0, 0, 0, 1.5, 0)
Problem plecakowy - ciągły Przykład (z limitami): Przedmioty: Z = { (3, 1), (60, 10), (80, 15), (210, 30), (270, 45) } Plecak: K = 45 Optymalne upakowanie: KN(K, Z) = 300 Liczby przedmiotów: L = (0, 0, 0, 1, 1/3) lub (0, 1, 0, 1, 1/9)
Ciągły problem plecakowy – algorytm // nielimitowana ilość przedmiotów deef Knapsack (goods, KSize): # przedmiot o najlepszym stosunku ceny do wagi i = GetMaxPrizeToValue(goods, n) KValue = KSize / goods[i].weight * goods[i].price return KValue
Ciągły problem plecakowy – algorytm Knapsack (goods, KSize) KValue = 0 cnt=0 while K >= 0 : # najlepszy stosunek ceny do wagi i max > 0 i = GetMaxPrizeToValue(goods, n) if KSize< goods[i].weight * goods[i].max: cnt = KSize / goods[i].weight else: Z[i].max KSize = KSize - cnt KValue += KValue + cnt* goods[i].price goods[i].max = 0 return KValue
Inne klasyczne zastosowania algorytmów zachłannych Przydział jak największej ilości zajęć do zasobu (przy założeniu wzajemnego wykluczania); Drzewo spinające w grafie. Własność zachłannego wyboru; Matroidy.
Kodowanie Huffmana Kodowanie o stałej długości – kody wszystkich znaków są jednakowe, np.: A = 01000001, B = 01000010, C = 01000011, … Kodowanie o zmiennej długości – kody znaków mają różne długości (im rzadszy znak, tym większa długość kodu) Kod prefixowy (kod dowolnego znaku nie jest prefixem innego)
Kodowanie Huffmana (1952) W kolejnym kroku scalane są dwa drzewa o najmniejszej wadze. NIE PIEPRZ PIETRZE WIEPRZA PIEPRZEM I II 2 Częstotliwości: 2 1 1 N: 1 T: 1 W: 1 A: 1 M: 1 R: 4 Z: 4 : 4 I: 6 P: 6 E: 7 A 1 W 1 T N III 3 2 1 2 1 1 M A W 1 1 N T
Kodowanie Huffmana (1952) NIE PIEPRZ PIETRZE WIEPRZA PIEPRZEM : 100 normalnie: 35 x 8 = 280 b po kompresji: 110 bitów średnio: 3, 14 bity na znak 8 7 9 11 T drzewo 35 15 20 4 5 6 2 3 1 E R Z I P W A M N