330 likes | 651 Views
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);
E N D
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 Nielimitowane przedmioty (1, 1) (2, 1)(3, 11) (4, 16) (5, 24)
Alg. Problem plecakowy classGOOD:weight=0price=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(B)):C[i][j] = 0; for k in range(0, Columns(A)):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 mniejsze w miejscu podziału "najwyższego" poziomu – 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 = -1for 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 15125 11875 9375 7875 15750 0 10500 7125 4375 2625 0 5375 2500 750 0 3500 1000 0 5000 0 0 3 3 3 1 1 0 3 3 3 2 0 3 3 3 0 5 4 0 5 0 0 j m 5 4 6 2 3 1 Rozwiązanie: A1= [30, 35] A2= [35, 15] A3= [15, 5] A4= [5, 10] A5= [10, 20] A6= [20, 25] 1 2 i 3 4 5 6 optdiv j 4 3 6 5 2 1 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-1m[i][j] = -1for 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] = tmpoptdiv[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 macierzyprzy wykorzyst. spamiętywania def MemorizedMatrixChain(p, len):for i in range(1,len): for j in range(1,len):m[i,j] = -1return 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 = 0cnt=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].maxKSize = 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)
2 2 1 1 1 1 3 2 2 1 1 1 1 1 Kodowanie Huffmana (1952) W kolejnym kroku scalane są dwa drzewa o najmniejszej wadze. NIE PIEPRZ PIETRZE WIEPRZA PIEPRZEM I II Częstotliwości: A W N: 1 T N T: 1 W: 1 A: 1 III M: 1 R: 4 Z: 4 : 4 I: 6 M P: 6 A W E: 7 T N
drzewo N: 101110 1 35 T: 101111 0 W: 10100 15 20 A: 10101 M: 10110 7 8 9 11 R: 010 E Z: 011 : 100 4 4 4 5 5 6 R Z I P I: 110 P: 111 2 3 E: 00 1 1 1 2 normalnie: 35 x 8 = 280 b po kompresji: 110 bitów średnio: 3, 14 bity na znak W A M 1 1 T N Kodowanie Huffmana (1952) NIE PIEPRZ PIETRZE WIEPRZA PIEPRZEM