Krok 1

Transkrypt

Krok 1
Programowanie dynamiczne
Technika konstrukcji algorytmów
Własności:
• stosowana najczęściej do problemów optymalizacyjnych
lub znajdowania strategii w grze
• dla zadanego wejścia jest wiele rozwiązań dopuszczalnych,
każde posiada wartość (koszt)
• celem jest znalezienie rozwiązania optymalnego, o
minimalnym lub maksymalnym koszcie
• stosowana gdy podproblemy posiadają wspólne
(pod)podproblemy; metoda "dziel i zwyciężaj"
rozwiązywałaby te same (pod)podproblemy wielokrotnie
• aby uniknąć powtórnego rozwiązywania podproblemów
korzysta z tablicy, umieszczając w niej wyniki już
rozwiązanych podproblemów
1
Konstruowanie algorytmu metodą programowania
dynamicznego:
1. Scharakteryzuj strukturę rozwiązania optymalnego, w
szczególności jak powstaje z optymalnych rozwiązań
podproblemów; upewnij się że zachodzi własność optymalnej
podstruktury
2. Podaj rekurencyjny przepis na koszt rozwiązania
optymalnego dla (pod)zadania
3. Zapisz algorytm znajdujący koszt rozwiązania optymalnego
metodą wstępującą ("bottom-up" - od najmniejszych
podzadań do największych)
4. Podaj metodę znajdowania rozwiązania optymalnego
(niepotrzebne jeśli obliczamy tylko koszt optymalny).
Przykład 1: Liczby Fibonacciego
(problem nieoptymalizacyjny)
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, . . .
F(0)=0, F(1)=1, F(n)=F(n–1)+F(n–2), n>1
oblicz F(n), dla zadanego n.
Krok 1: definicja liczb F.
Krok 2: zależność rekurencyjna
(czyli w zasadzie to samo co krok 1)
F(n)=
n, jeśli n<=1
F(n-1)+F(n-2), wpp.
2
W zapisie algorytmicznym:
Fib(n)
if (n<2) return n;
return Fib(n–1)+Fib(n–2);
Czas wykonania:
T(n) = T(n–1) + T(n–2) + O(1)
Drzewo wywołań rekurencyjnych
Fib(n)
/
Fib(n–1)
/
\
Fib(n–2) Fib(n–3)
/
\
Fib(n–3) Fib(n–4)
\
Fib(n–2)
/
\
Fib(n–3) Fib(n–4)
Głębokość lewego skrajnego liścia:
Głębokość prawego skrajnego liścia:
 liczba węzłów V(n):
2n/2≤ V(n) ≤ 2n
n
n/2
zatem złożoność T(n):
T(n) = Ω(2n/2),
T(n) = O(2n).
Przyczyna złożoności wykładniczej:
wielokrotne obliczanie tego samego podzadania.
3
Krok 3: bottom-up, z tablicowaniem wyników:
Fib2(n)
F[0] = 0
F[1] = 1
for (i=2; i<=n; i++)
F[i]=F[i–1]=F[i–2]
return F[n]
Złożoność czasowa i pamięciowa: Θ(n).
(Oczywiście można lepiej)
Krok 4: nie ma
(koszt rozwiązania jest tożsamy z rozwiązaniem).
Czy w ten sposób można usprawnić np. mergesort?
Nie,
bo w drzewie wywołań rekurencyjnych każdy podproblem
pojawia się tylko raz.
4
Przykład 2:
Podzielić liczbę naturalną n>0 na k>0 składników naturalnych,
tak by iloczyn składników był największy:
n=n1+...+nk , n1⋅n2⋅...⋅nk maksymalny, k<n
Rekurencja po k:
k>1:
Fk(n) = max{nk⋅Fk-1(n-nk), 0<nk<n}, dla n>1
0, dla n=1
k=1:
F1(n) = n, n≥1
Obliczenie wstępujące: tablicuj kolejno funkcje
F1, F2, ..., Fk-1, i na końcu oblicz Fk(n)
F1[i] = i, i=1,...,n
F2[i] = max{j⋅F1(i-j), 0<j<i}, i=1,...,n
F3[i] = max{j⋅F2(i-j), 0<j<i}, i=1,...,n
...
Fk-1[i] = max{j⋅Fk-2(i-j), 0<j<i}, i=1,...,n
Fk[n] = max{j⋅Fk-1(n-j), 0<j<n}.
Pamięć: Θ(n)
dwie tablice długości n, na aktualnie obliczane Fk oraz
potrzebne do tego Fk-1.
Czas: Θ(n2k)
Uwaga: rozmiar danych jest rzędu lg n + lg k,
zatem czas jest funkcją wykładniczą od rozmiaru danych –
formalnie algorytm nie ma złożoności wielomianowej.
5
Problem optymalnego mnożenia ciągu macierzy
dane:
• ciąg macierzy liczbowych A1, A2, ..., An,
• Ak jest rozmiaru pk-1 x pk, k=1,...,n
obliczyć:
• najmniejszą możliwą liczbę operacji mnożenia liczb elementów macierzy potrzebną do obliczenia iloczynu
A1A2... An, czyli:
• optymalne nawiasowanie tego iloczynu
Założenie:
Jeśli A∈Rp x q, B∈Rq x r, to iloczyn C = AB jest obliczany "z
definicji"
Cij = Σk=1,...,q Aik Bkj
koszt obliczania iloczynu C=AB to liczba mnożeń elementów
macierzy:
p*q*r
Koszt mnożenia ciągu macierzy zależy od nawiasowania. Np.
dla rozmiarów
A: 5 x 10, B: 10 x 3, C: 3 x 7, D: 7 x 5
iloczyn ABCD może być obliczony na 5 sposobów
(5 różnych nawiasowań)
1. ((AB)C)D wymaga mnożeń:
5*10*3=150 dla AB, rozmiar wyniku 5 x 3
5*3*7= 105 dla (AB)C, rozmiar 5 x 7
5*7*5=175 dla ((AB)C)D, rozmiar 5 x 5
razem: 430.
6
2. (AB)(CD): 330 mnożeń
3. (A(BC))D: 735
4. A((BC)D): 810
5. A(B(CD)): 505
Intuicja: zacznij od eliminacji największego rozmiaru (tutaj:
10, czyli najpierw iloczyn AB) jest błędna.
Możliwa liczba nawiasowań wyrażenia n-argumentowego:
liczba Catalana C(n)
C(1) = 1
C(n) = Σk=1,...,n-1 C(k)C(n-k), n>1
bo: k jest numerem ostatniego mnożenia, zatem
• k = liczba argumentów do pomnożenia (rekurencyjnie) w
lewej części ciągu,
• n-k = liczba argumentów do pomnożenia w prawej części
ciągu
7
Krok 1:
Jak otrzymujemy rozwiązanie optymalne z rozwiązań
podzadań ?
Obserwacja:
dla zadanego układu nawiasów, ostatnie mnożenie dzieli ciąg
na dwa podzadania:
(A1A2···Ak)(Ak+1Ak+2···An) dla pewnego 1<=k<=n–1
Więc A1, A2,..., Ak oraz Ak+1, Ak+2,..., An są mnożone niezależnie
a następnie mnożone są wyniki (A1A2···Ak) i (Ak+1Ak+2···An).
Pomysł: sprawdź wszystkie możliwe punkty podziału k i
wybierz najlepszy.
Własność optymalnej podstruktury
Jeśli optymalne nawiasowanie Nij dla ciągu macierzy AiAi+1···Aj
dzieli ciąg w pewnym miejscu k, i<=k<=j-1, to to
nawiasowanie zawiera w sobie optymalne nawiasowanie
Nik dla podciągu AiAi+1···Ak i optymalne nawiasowanie Nk+1,j
Ak+1Ak+2···Aj.
Dlaczego?
jeśliby Nij nie zawierało optymalnego nawiasowania N'ik dla
podciągu AiAi+1···Ak, to zastępując Nik w Nij przez N'ik
otrzymalibyśmy nawiasowanie N'ij dla AiAi+1···Aj o mniejszym
koszcie niż Nij – sprzeczne z optymalnością Nij.
Analogicznie dla prawej strony ciągu.
8
Krok 2: rozwiązanie rekurencyjne
Zdefiniujmy m[i, j] , dla 1≤i≤j≤n:
m[i,j] = najmniejsza liczba mnożeń potrzebna do
obliczenia iloczynu AiAi+1...Aj
Potrzebujemy m[1,n],
obliczamy wszystkie wartości m[i,j].
dla i=j, m[i,j] = 0 (jedna macierz)
Niech k – punkt podziału dla AiAi+1···Aj, i≤k≤j–1.
Wówczas:
m[i, k] = najmniejsza liczba mnożeń dla AiAi+1···Ak
m[k+1, j] = najmniejsza liczba mnożeń dla Ak+1Ak+2···Aj
AiAi+1···Ak jest rozmiaru pi–1 x pk
Ak+1Ak+2···Aj jest rozmiaru pk x pj,
zatem
liczba mnożeń dla pomnożenia obu wyników wynosi
pi–1pkpj.
9
Ostatecznie:
Jeśli dzielimy w punkcie k to liczba mnożeń wynosi
m[i, k] + m[k+1, j] + pi–1pkpj
szukamy k które zminimalizuje tę sumę, otrzymujemy
rekurencję:
m[i,j] = 0, dla i=j
m[i,j] = mini≤k≤j-1{m[i,k] + m[k+1,j] + pi–1pkpj}, dla i<j
która prowadzi do algorytmu rekurencyjnego:
RMCO(p, i, j)
// recursive matrix chain order
if (i=j) return 0
else
min=∞
for (k=i;k<j;k++)
q=RMCO(p,i,k)+RMCO(p,k+1,j)+p[i–1]p[k]p[j]
if (q<min) min=q
return min
Main call:
RMCO(p,1,n).
Obserwacja:
• wiele podproblemów rozwiązywanych wielokrotnie
• złożoność wykładnicza
Krok 3: obliczenie metodą wstępującą
z zapamiętywaniem wyników dla podproblemów w tablicy
trójkątnej górnej:
m[i, j] , dla 1≤i≤j≤n:
gdzie m[i, j] = 0 dla i = j, zatem przekątna jest zerowa
10
Przykład podany wcześniej:
p[0]=5, p[1]=10, p[2]=3, p[3]=7, p[4]=5.
rozmiar 1:
m[1, 1] = m[2, 2] = m[3, 3] = m[4, 4] = 0
rozmiar 2:
m[1, 2] = p[0]p[1]p[2] = 5*10*3 = 150
m[2, 3] = p[1]p[2]p[3] = 10*3*7 = 210
m[3, 4] = p[2]p[3]p[4] = 3*7*5 = 105
rozmiar 3:
m[1, 3] = min {
/* k=1 */ m[1, 1] + m[2, 3] + p[0]p[1]p[3],
/* k=2 */ m[1, 2] + m[3, 3] + p[0]p[2]p[3] }
= min {560, 255} = 255
m[2, 4] = min {
/* k=2 */ m[2, 2] + m[3, 4] + p[1]p[2]p[4],
/* k=3 */ m[2, 3] + m[4, 4] + p[1]p[3]p[4] }
= min {255, 560} = 255
rozmiar 4:
m[1, 4] = min {
/* k=1 */ m[1, 1] + m[2, 4] + p[0]p[1]p[4],
/* k=2 */ m[1, 2] + m[3, 4] + p[0]p[2]p[4],
/* k=3 */ m[1, 3] + m[4, 4] + p[0]p[3]p[4] }
= min {505, 330, 430} = 330
Wynik – tablica m[]:
0
x
x
x
150
0
x
x
255
210
0
x
330
255
105
0
11
Algorytm wstępujący
aby zrealizować krok 4 – generowanie optymalnego
nawiasowania:
• obliczając m[i, j], najlepszą wartość k zapamiętujemy w
s[i,j]
• po zakończeniu, s[i, j]=k oznacza, że m[i, j] jest obliczane z
m[i, k] oraz m[k+1, j].
MCO(p, n)
for (i=1; i<=n; i++)
m[i,i]=0
for (d=2; d<=n; d++)
for (i=1; i<=n–d+1;
j=i+d–1 // [i,j]
// matrix chain order
// sub-problem size
i++)
– subproblem boundaries
m[i,j]=∞ // compute m[i,j]
for (k=i; k<j; k++)
q=m[i,k]+m[k+1,j]+p[i–1]p[k]p[j]
if (m[i,j]>q)
m[i,j]=q
s[i,j]=k
return m[1,n]
end.
Złożoność O(n3) oczywista.
Dokładniej:
liczba wykonań instrukcji q = m[.... wynosi:
T(n) = Σd=2,...,n Σi=1,...,n-d+1 d = Σd=2,...,n (n-d+1)d
proste obliczenia:
T(n) = 1/6 n3 + O(n2) = Θ(n3)
12
Krok 4: obliczanie optymalnego rozwiązania
Tablica s[]:
0
x
x
x
1
0
x
x
2
2
0
x
2
2
3
0
np. s[1, 3] = 2
bo k=2 to najlepsze miejsce podziału dla m[1, 3]
optymalne nawiasowanie dla A1A2A3A4:
s[1, 4] = 2, zatem
(A1A2)(A3A4).
Inny przykład: jeśliby s[] miała wartości np.:
0
x
x
x
1
0
x
x
1
2
0
x
3
3
3
0
to nawiasowanie byłoby następujące:
A1A2A3A4 = (A1A2A3)A4, (A1A2A3) = (A1(A2A3))
czyli całość:
A1A2A3A4 = (A1(A2A3))A4.
Ogólnie:
na podstawie tablicy s[] wygenerować drzewo nawiasowania
• korzeń = nr ostatniego mnożenia = s[1,n]
• korzeń lewego poddrzewa: rekurencja, nr ostatniego
mnożenia w lewym podzadaniu
• korzeń prawego poddrzewa: rekurencja, nr ostatniego
mnożenia w prawym podzadaniu
Złożoność algorytmu konstrukcji drzewa?
13
Spamiętywanie (memoization)
• programowanie dynamiczne "top-down" – czyli w postaci
rekurencyjnej
• jak poprzednio, z tablicą na rozwiązania podzadań,
• bez wielokrotnego rozwiązywania tych samych podzadań
Na początku każdego rekurencyjnego wywołania sprawdź, czy
tablica zawiera wynik tego podzadania
• jeśli tak, użyj tego wyniku i zakończ to wywołanie
• jeśli nie, to oblicz wynik a potem zapamiętaj w tablicy
MMCO(p, i, j) //memo matrix chain order
if (m[i,j] ≠ ∞ )
return m[i,j]
if (i = j)
m[i,j] = 0
else
for (k=i; k<=j–1; k++)
q=MMCO(p,i,k)+MMCO(p,k+1,j)+p[i–1]p[k]p[j]
if (m[i,j] > q)
m[i,j] = q
return m[i,j]
Memo-Matrix-Chain(p, n) // Main program:
for (i=1; i<=n; i++)
// initialize table
for (j=i; j<=n; j++)
m[i,j] = ∞
result = MMCO(p,1,n)
// call MMCO
14
Złożoność:
•
•
•
Liczba podzadań: Θ(n2)
Koszt dla pojedynczego podzadania: O(n)
Każde zadanie liczone tylko raz.
Wniosek:
T(n) = O(n3)
jest nieco pochopny, bo:
ile jest wszystkich wywołań procedury?
na pewno więcej niż wywołań w których się oblicza
podzadanie
koszt czasowy obliczyliśmy dla wywołań, które obliczają
podzadanie (wywołanie I typu).
Ile jest wywołań II typu – trafiających na już obliczone
podzadanie?
Ale:
•
•
•
•
•
każde wywołanie typu II jest wykonywane podczas
obliczania jakiegoś podzadania – a więc wywołania typu I
wywołań typu I jest Θ(n2)
każde wywołanie typu I powoduje co najwyżej 2n
następnych wywołań, a więc powoduje co najwyżej 2n
wywołań typu II
zatem liczba wywołań typu II jest O(n2*2n)=O(n3)
każde wywołanie typu II kosztuje O(1)
Zatem wywołania typu II też kosztują O(n3), zatem
rzeczywiście: T(n) = O(n3)
15
Problem plecakowy
Klasyczny problem z optymalizacji kombinatorycznej
rozwiązywany często metodą programowania dynamicznego.
Wersja 0-1:
Dane:
• n elementów, vi, wi to odpowiednio wartość oraz waga
i-tego elementu
• W – pojemność plecaka
Wynik:
• podzbiór elementów taki, że suma wag nie przekracza W
oraz suma wartości jest możliwie największa.
Wersja ułamkowa:
Różni się tylko tym, że można dobierać ułamki elementów
(nie większe niż 1).
Własność optymalnej podstruktury wersji 0-1:
Twierdzenie:
Rozważmy optymalny dobór elementów do plecaka. Jeśli
usuniemy z niego element j-ty, to pozostałe elementy tworzą
optymalny dobór dla plecaka o pojemności W – wj, spośród
n–1 elementów (bez elementu j-tego).
Dowód:
dość oczywisty.
16
Rozwiązanie wersji 0-1 metodą programowania
dynamicznego:
Podproblem P(j, k) powstaje przez:
• ograniczenie zbioru elementów do początkowych j
elementów (0 ≤ j ≤ n)
• ograniczenie wagi plecaka do k (0 ≤ k≤ W)
Definiujemy tablicę:
S[j, k] := wartość optymalnego rozwiązania dla P(j,k)
Dla j=0 nie ma żadnych elementów, więc wartość
optymalnego plecaka o dowolnej pojemności jest 0.
Dla j>0, k≥0 optymalna wartość plecaka S[j,k] obliczana jest
przez rozpatrzenie następujących dwóch możliwości:
• element j-ty nie należy do optymalnego upakowania 
S[j,k] = S[j-1,k]
czyli wszystkie elementy wybrano spośród 1,...,j–1
• element j-ty należy do optymalnego upakowania 
pozostałą przestrzeń k–wj wypełniono optymalnie
elementami spośród 1,...,j–1,
wtedy S[j,k] = S[j-1,k–wj]+ vj
optymalna wartość S[j,k] to większa z dwóch powyższych
Tablica S[] można wypełnić wierszami, w wierszu od lewej do
prawej.
17
tablica S[0..n, 0..W]
// wiersz 0 w tablicy S
for (k=0; k<=W; k++) S[0,k] = 0 ;
for (j=1; j<=n; j++)
// wypełnij wiersz j-ty
for (k=0; k<=W; k++)
if (k-w[j] < 0)
S[j,k] = S[j-1,k] ;
//j-ty elem nie mieści się w plecaku
else
S[j,k]=max( S[j-1,k], S[j-1,k-w[j]]+v[j] )
return S[n,W].
Złożoność: Θ(nW) , (czas i pamięć)
gdyż tyle jest elementów do wypełnienia, a obliczanie
każdego zajmuje czas stały.
Nie jest to złożoność wielomianowa,
zależy liniowo od wartości liczby W, a nie od długości jej
zapisu
(jest to złożoność pseudowielomianowa).
Rekonstrukcja optymalnej zawartości plecaka:
wystarcza tablica S[], nie trzeba podczas jej obliczania
zapamiętywać jakiego wyboru dokonano ustalając
wartość S[j,k].
(ćwiczenie)
O(n)
Przelicz przykład:
n=6,
pojemność plecaka W=19,
wagi elementów: 4, 7, 3, 5, 6, 7,
ich wartości odpowiednio 3, 5, 4, 5, 5, 8.
18
Gdy obliczamy tylko optymalną wartość plecaka – bez
generowania optymalnego upakowania:
Spostrzeżenie 1: wystarczy pamiętać tylko wiersz poprzedni i
aktualny tablicy S, bo wartości w kolejnym wierszu zależą
tylko od poprzedniego wiersza, a nie od wcześniejszych.
Czas ten sam - Θ(nW), pamięć Θ(W)
Spostrzeżenie 2: jeśli wypełniać aktualnie obliczany wiersz od
prawej, to można obliczane wartości wpisywać w miejsce
wartości poprzedniego wiersza, a więc wystarcza
przechowywać tylko 1 wiersz tablicy S.
Metoda 2 (również programowanie dynamiczne)
Istnieje inny algorytm pseudowielomianowy:
Złożoność: Θ(n∑i=1,...,n vi).
Oznaczając V = max {vj} otrzymujemy O(n2 V)
Szczegóły – ćwiczenie.
19
Jeszcze jedno typowe zadanie na programowanie
dynamiczne:
Problem najdłuższego wspólnego podciągu (LCS)
Dla zadanych dwóch ciągów
X = <x1, . . . . , xm>
Y = <y1, . . . . , yn>
znajdź wspólny podciąg o największej długości
Np.
X = <A, B, A, C, >
Y = <A, C, A, B, C>
LCS:
<A,A,C> (nie unikalne)
Zastosowanie:
Miara podobieństwa łańcuchów DNA – słów nad
alfabetem {A, C, G, T} :
Im dłuższy wspólny podciąg tym bardziej podobne
łańcuchy.
Metoda "brute force":
• generuj wszystkie możliwe podciągi ciągu X
• sprawdzaj czy jest to również podciąg ciągu Y
• zapamiętaj najdłuższy o tej własności
Liczba różnych podciągów ciągu n-elementowego:
tyle ile podzbiorów, czyli 2m.
20
Rozwiązanie metodą programowania dynamicznego:
Podproblem: para prefiksów ciągów X i Y
Xi = <x1, . . . . , xi>
Yj = <y1, . . . . , yj>
Tw. (o optymalnej podstrukturze)
Dane są ciągi znaków:
X = <x1, . . . . , xm>
Y = <y1, . . . . , yn>
Z = <z1, . . . . , zk>
takie, że Z jest dowolnym LCS-em dla X i Y.
Wówczas:
1.
Jeśli xm = yn , to
zk = xm = yn
oraz
Zk-1 jest LCS-em dla Xm-1 i Yn-1
2.
Jeśli xm ≠ yn , to
(a) jeśli zk ≠ xm , to
Z jest LCS-em dla Xm-1 i Y
(b) jeśli zk ≠ yn , to
Z jest LCS-em dla X and Yn-1
Uwaga: twierdzenie odnosi się do optymalnego rozwiązania,
ale nie mówi jak go znaleźć.
Dowód: cut-and-paste, ćw.
Wniosek:
LCS dwóch ciągów zawiera LCS-y prefiksów tych ciągów –
zatem problem LCS ma własność optymalnej podstruktury.
21
Defnicja rekurencyjna rozwiązania:
Jeśli xm = yn to 1 podproblem:
znajdź LCS dla Xm-1 i Yn-1
dołącz do niego xm
Jeśli xm ≠ yn to 2 podproblemy:
znajdź LCS dla Xm-1 i Yn
znajdź LCS dla Xm i Yn-1
weź lepszy z nich
Niech
c[i,j] = długość LCS dla Xi i Yj.
c[i,j] =
0,
c[i-1, j-1] + 1
max(c[i, j-1], c[i-1, j])
i=0 lub j=0
i,j > 0, xi = yj,
i,j > 0, xi ≠ yj
Obserwacja:
c[i,j] określone dla 0 <=i<=m, 0<=j<=n,
zatem:
liczba podproblemów wynosi (m+1)(n+1) = Θ(mn)
22
Obliczenie wstępujące:
Tablica c[i,j], (m+1) x (n+1)
optymalne wartości rozwiązań podproblemów
c[i,j] zależy od:
c[i-1, j-1]
c[i-1, j]
c[i, j-1]
czyli elementów tablicy powyżej lub po lewej
Zatem obliczenie tablicy c[]:
• wierszami,
• w wierszu od lewej do prawej
kolumna zerowa i wiersz zerowy:
c[i, 0] = 0, dla każdego i,
c[0, j] = 0, dla każdego j.
23
Algorytm LCS-Length(X, Y)
//m, n – długości słów X i Y
for (i=1; i<=m; i++) c[i,0]=0
for (j=0; j<=n; j++) c[0,j]=0
for (i=1; i<=m; i++)
for (j=1; j<=n; j++)
if (X[i]=Y[j])
c[i,j] = c[i-1,j-1]+1
else
if (c[i,j-1] >= c[i-1,j])
c[i,j] = c[i,j-1]
else
c[i,j] = c[i-1,j]
return c
end.
// column 0
// row 0
Złożoność:
3 pętle:
Θ(m) + Θ(n) + Θ(mn) = Θ(mn)
24
Przykład:
X = <A, B, A, C, >
Y = <A, C, A, B, C>
Inicjalizacja:
0
0
1 A
2 B
3 A
4 C
1
2
3
4
5
A
C
A
B
C
------------------------| 0 | 0 | 0 | 0 | 0 | 0 |
------------------------| 0 |
|
|
|
|
|
------------------------| 0 |
|
|
|
|
|
------------------------| 0 |
|
|
|
|
|
------------------------| 0 |
|
|
|
|
|
-------------------------
Wynik:
0
0
1 A
2 B
3 A
4 C
1
2
3
4
5
A
C
A
B
C
------------------------| 0 | 0 | 0 | 0 | 0 | 0 |
------------------------| 0 | 1 | 1 | 1 | 1 | 1 |
------------------------| 0 | 1 | 1 | 1 | 2 | 2 |
------------------------| 0 | 1 | 1 | 2 | 2 | 2 |
------------------------| 0 | 1 | 2 | 2 | 2 | 3 |
-------------------------
25
Rekonstrukcja optymalnego rozwiązania:
b[i, j] – tablica z informacją który spośród
c[i-1,j-1], c[,j-1], [i-1,j]
został wybrany do obliczenia c[i, j]
'\' jeśli c[i, j] = 1 + c[i–1, j–1]
'<' jeśli c[i, j] = c[i, j–1]
'^' jeśli c[i, j] = c[i–1, j]
for (i=1; i<=m; i++)
for (j=1; j<=n; j++)
if (X[i]=Y[j])
c[i,j] = c[i-1,j-1]+1
b[i,j]  '\'
else
if (c[i,j-1] >= c[i-1,j])
c[i,j] = c[i,j-1]
b[i,j]  '<'
else
c[i,j] = c[i-1,j]
b[i,j]  '^'
return c
end.
wynik – tablica b[]:
0
0
1 A
2 B
3 A
4 C
1
2
3
4
5
A
C
A
B
C
------------------------| 0 | 0 | 0 | 0 | 0 | 0 |
------------------------| 0 | \ | < | \ | < | < |
------------------------| 0 | ^ | < | < | \ | < |
------------------------| 0 | \ | < | \ | < | < |
------------------------| 0 | ^ | \ | < | < | \ |
------------------------26
Obliczanie LCS – w kolejności od końca:
•
•
•
•
zacznij w b[m, n]
przesuwaj się zgodnie ze strzałkami
dodawaj aktualny element gdy strzałka '\'
stop gdy osiągniesz wiersz lub kolumnę 0
Θ(m+n).
Uwagi:
1. Łatwe: jeśli obliczamy tylko długość najdłuższego
wspólnego podciągu, to wystarcza pamięć O(min{m,n}),
dokładniej: wystarcza pamięć na jeden wiersz tablicy a.
2. Łatwe: jeśli obliczamy najdłuższy wspólny podciąg, to do
jego rekonstrukcji nie jest potrzebna tablica b; kierunek ruchu
podczas rekonstrukcji podciągu od końca do początku można
wyznaczyć z tablicy a, mając dostęp do X i Y.
3. Trudne: można obliczyć najdłuższy wspólny podciąg w
takim samym czasie Θ(mn) i w pamięci Θ(m+n).
27

Podobne dokumenty