7. Diamikus rogramozás 7.1. Rekurzió memorizálással. Láttuk, hogy a artíció robléma rekurzív algoritmusa Ω(2 ) eljáráshívást végez, edig a lehetséges részroblémák száma csak 2 (vagy ( + 1)/2, ha csak az k eseteket vesszük.) Eek az az oka, hogy ugyaazo részrobléma megoldása több más részrobléma megoldásához kell, és az algoritmus ezeket midig újra kiszámítja. Tehát egyerűe gyorsíthatjuk a számítást, ha mide részrobléma (azaz P2(, k)) megoldását tároljuk egy tömbbe. Ha hivatkozuk egy részrobléma megoldására, akkor először elleőrizzük, hogy kiszámítottuk-e már, és ha ige, akkor kiolvassuk az értéket a táblázatból, egyebkét rekurzíva számítuk, és utáa tároljuk az értéket a táblázatba. A táblázat iicializálásához válasszuk egy olya értéket, amely em lehet egyetle részrobléma megoldása sem. Esetükbe ez lehet a 0. ublic class ParticioRM{ rivate static log[][] T2; ublic static log P(it ){ T2=ew log[+1][+1]; retur P2(,); //P rivate static log P2(it, it k){ if (T2[][k]=0) //P2(,k)-t már kiszámítottuk retur T2[][k]; //értéke a T2 táblázatba log Pk; if (k==1 ==1) //rekurzív számítás Pk=1; else if (k>=) Pk=P2(,-1)+1; else Pk=P2(, k-1)+p2(-k, k); T2[][k]=Pk; //memorizálás retur Pk; //P2 Nyilvávaló, hogy az algoritmus futási ideje Θ( 2 ), és a tárigéye is Θ( 2 ) lesz, ha csak 2 méretű táblázatak foglaluk memóriát diamikusa az aktuális araméter függvéyébe. 7.2. A artíció robléma megoldása táblázat-kitöltéssel. A rekurziót teljese kiküszöbölhetjük táblázat-kitöltéssel. Az 1. árá szemléltetett táblázatot haszáljuk a részroblémák megoldásaiak tárolására. Tehát a T 2[, k] táblázatelem tartalmazza a P2(, k) részrobléma megoldását. A táblázat első sora azoal kitölthető, mert P2(, 1) = 1. Olya kitöltési sorredet keresük, hogy mide (, k), k > 1 részrobléma kiszámítása eseté azok a részroblémák, amelyek szükségesek P2(, k) kiszámításához, már korábba kiszámítottak legyeek. Általáosa, rekurzív összefüggéssel defiiált roblémamegoldás eseté egy r (rész)robléma összetevői azok a részroblémák, amelyek megoldásától r megoldása függ. Tehát a táblázat-kitöltéssel alkalmazásához meg kell állaítai a részroblémákak egy olya sorredjét, hogy mide r részrobléma mide összetevője előbb álljo a sorredbe, mit r. A 1. P2(1,k) = 1, P2(,1) = 1, 2. P2(,) = 1 + P2(, 1), 3. P2(,k) = P2(,) ha < k, 4. P2(,k) = P2(,k 1) + P2( k,k) ha k <. rekurzív összefüggések megadják az összetevőket: 1
k N T2[,k]=P2(,k)? k?? k 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 k N 1. ábra. Táblázat a Partíció robléma megoldásához. 1. P2(1, k)-ak és P2(, 1)-ek ics összetevője, 2. P2(, ) összetevője P2(, 1), 3. P2(, k) összetevője P2(, ), ha ( < k), 4. P2(,k) összetevői: P2(,k 1) és P2( k,k), ha (k < ). Tehát a táblázat kitöltése (k-szerit) sorokét balról jobbra haladó lehet. Az algoritmus futási ideje és tárigéye is Θ( 2 ). ublic static log P(it ){ log[][] T2=ew log[+1][+1]; for (it ; i<=; i++) T2[i][1]=1; //az első sor kitöltése for (it ki=2; ki<=; ki++){ //az ki. sor kitöltése T2[ki][ki]=T2[ki][ki-1]+1; //P2(,)=P2(,-1)+1 for (it i=ki+1; i<=; i++){ //P2(i,ki)=T2[i,ki] számítása it 1=(i-ki<ki)? i-ki : ki; //P2(,k)=P2(,), ha k> T2[i][ki]=T2[i][ki-1]+T2[i-ki][1];//P2(,k)=P2(,k-1)+P2(-k,k) retur T2[][]; 7.3. A artíció robléma megoldása lieáris táblázat-kitöltéssel. Látható, hogy elegedő lee a táblázatak csak két sorát tároli, mert mide (, k) részrobléma összetevői vagy a k-adik, vagy a k 1-edik sorba vaak. Sőt, elég egy sort tároli balról-jobbra (övekvő -szerit) haladó kitöltésél, mert amelyik részroblémát felülírjuk (( k, k)), aak később ée az új értéke kell összetevőkét. 2
ublic static log P(it ){ log[] T=ew log[+1]; for (it ; i<=; i++) //az első sor kitöltése T[i]=1; for (it ki=2; ki<=; ki++){ //az ki. sor kitöltése ++T[ki]; //P2(,)=P2(,-1)+1 for (it i=ki+1; i<=; i++){ // T[i]=T[i]+T[i-ki]; //P2(,k)=P2(,k-1)+P2(-k,k) retur T[]; P(405)= 9147679068859117602 7.4. A ézváltás robléma. Probléma: Pézváltás Bemeet: P = { 1,..., ozitív egészek halmaza, és E ozitív egész szám. Kimeet: Olya S P, hogy S = E. Megjegyzés: A ézek tetszőleges címletek lehetek, em csak a szokásos 1, 2, 5 10, 20, stb., és mide éz csak egyszer haszálható a felváltásba. Először azt határozzuk meg, hogy va-e megoldás. A megoldás szerkezetéek elemzése. Tegyük fel, hogy E = i1 +... + ik, i 1 <... < i k egy megoldása a feladatak. Ekkor E ik = i1 +... + ik 1 megoldása lesz aak a feladatak, amelyek bemeete a felváltadó E ik érték, és a felváltáshoz legfeljebb a első i k 1 ( 1,..., ik 1) ézeket haszálhatjuk. Részroblémákra botás. Botsuk részroblémákra a kiidulási roblémát: Mide (X, i)(1 X E, 1 i N) számárra vegyük azt a részroblémát, hogy az X érték felváltható-e legfeljebb az első 1,..., i ézzel. Jelölje V (X,i) az (X,i) részrobléma megoldását, ami logikai érték; V (X, i) = Igaz, ha az X összeg előállítható legfeljebb az első i ézzel, egyébkét Hamis. Összefüggések a részroblémák és megoldásaik között. Nyilvávaló, hogy az alábbi összefüggések teljesülek a részroblémák megoldásaira: 1. V (X,i) = (X = i ), ha i = 1 2. V (X,i) = V (X,i 1) (X > i ) V (X i,i 1) ha i > 1 Rekurzív megoldás. Mivel a megoldás kifejezhető egy V(X,i) logikai értékű függvéyel, ezért a felírt összefüggések alajá azoal tuduk adi egy rekurzív függvéyeljárást, amely a ézváltás robléma megoldását adja. ublic boolea V(it X, it i){ // Globális:P // Módszer: Rekurzív megoldás retur (X==P[i]) (i>1) && V(X,i-1) (i>1) && (X>P[i]) && V(X-P[i],i-1); Ez a megoldás azoba ige lassú, legrosszabb esetbe a futási idő Ω(2 ). Megoldás a részroblémák megoldásaiak táblázatos tárolásával. Vegyük egy V T táblázatot, amelybe mide lehetséges részrobléma megoldását tároljuk. Mivel mide részroblémát két érték határoz meg, X és i, ezért téglala alakú táblázat kell. V T [X, i] az (X, i) részrobléma megoldását tartalmazza. A 3
N i? i-1 1 1 X-P[i] X E 2. ábra. A ézváltás táblázata részroblémák kiszámítási sorredje. Olya kiszámítási sorredet kell megállaítai, amelyre teljesül, hogy amikor az (X, i) részroblémát számítjuk, akkor eek összetevőit már korábba kiszámítottuk. Mivel az (X, 1) részroblémákak ics összetevőjük, ezért közvetleül kiszámíthatóak, azaz a táblázat első sorát számíthatjuk először. Ha i > 1, akkor az (X,i) részrobléma összetevői az (X,i 1) és (X i,i 1), ezért az i-edik sor bármely elemét ki tudjuk számítai, ha már kiszámítottuk az i 1-edik sor mide elemét. Tehát a táblázatkitöltés sorredje: sorokét (alulról felfelé), balról-jobbra haladó lehet. Egy megoldás előállítása a megoldás visszafejtésével. Akkor és csak akkor va megoldása a roblémáak, ha a V T táblázat kitöltése utá V T [E,N] értéke igaz. Ekkor az (1-2.) kéletek szerit a legagyobb i idexű i éz, amely szereelhet E előállításába, az a legagyobb idex, amelyre V T [E,i] = True (V T [E,i 1] = False) De ekkor V T [E P[i],i 1] igaz, tehát E i előállítható az első i 1 éz felhaszálásával. Tehát a feti eljárást folytati kell E := E i,i := i 1-re midaddig, amíg E 0 lesz. ublic class PezValt1{ ublic static it[] Valto(it E, it[] P){ it =P.legth; it MaxE=1000; boolea VT[][]=ew boolea[e+1][+1]; it i,x; for (x=1; x<=e; x++) //iicializálás VT[x][0]=false; VT[0][0]=true; VT[P[0]][0]=true; for (; i<; i++) for (x=1; x<=e; x++) VT[x][i]=P[i]==x VT[x][i-1] x>=p[i] && VT[x-P[i]][i-1]; it db=0; x=e; i=-1; if (VT[E][-1]) retur ull; it C[]=ew it[]; do{ //megoldás visszafejtés while (i>=0 && VT[x][i]) i--; C[db++]=++i; x-=p[i]; 4
while(x>0); for (i=db; i<; i++) C[i]=0; retur C; Ha csak arra kell válaszoli, hogy létezi-e megoldása a roblémáak, akkor elég a táblázat egy sorát tároli, mert sorokét visszafelé (x-szerit csökkeő sorredbe) haladó kitöltést alkalmazhatuk. ublic class PezValt1L{ ublic static boolea Valto(it E, it[] P){ it =P.legth; boolea VT[]=ew boolea[e+1]; it i,x; for (x=1; x<=e; x++) VT[x]=false; VT[0]=true; if (P[0]<=E) VT[P[0]]=true; for (; i<; i++) for (x=e; x>0; x--) VT[x]=P[i]==x VT[x] x>=p[i] && VT[x-P[i]]; retur VT[E]; 7.5. Az otimális ézváltás robléma. Probléma: Otimális ézváltás Bemeet: P = { 1,..., ozitív egészek halmaza, és E ozitív egész szám. Kimeet: Olya S P, hogy S = E és S miimális Először is lássuk be, hogy az a mohó stratégia, amely midig a lehető legagyobb ézt választja, em vezet otimális megoldáshoz. Legye E = 8 és a ézek halmaza legye {5,4,4,1,1,1. A mohó módszer a 8 = 5 + 1 + 1 + 1 megoldást adja, míg az otimális a 8 = 4 + 4. Az otimális megoldás szerkezetéek elemzése. Tegyük fel, hogy E = i1 +... + ik, i 1 <... < i k egy otimális megoldása a feladatak. Ekkor E ik = i1 +... + ik 1 otimális megoldása lesz aak a feladatak, amelyek bemeete a felváltadó E ik érték, és a felváltáshoz legfeljebb a első i k 1 ( 1,..., ik 1) ézeket haszálhatjuk. Ugyais, ha lee kevesebb ézből álló felváltása E ik -ak, akkor E-ek is lee k-ál kevesebb ézből álló felváltása. Részroblémákra és összetevőkre botás. A részroblémák legyeek ugyaazok, mit az előző esetbe. Mide (X,i) (1 X E,1 i N) számárra vegyük azt a részroblémát, hogy legkevesebb háy éz összegekét lehet az X értéket előállítai legfeljebb az első i { 1,..., i éz felhaszálásával. Ha ics megoldás, akkor legye ez az érték N +1. Jelölje az (X,i) részrobléma otimális megoldásáak értékét Ot(X,i). Defiiáljuk az otimális megoldás értékét X = 0-ra és i = 0-ra is, azaz legye Ot(X,0) = N + 1 és Ot(0,i) = 0. Így Ot(X, i)-re az alábbi rekurzív összefüggés írható fel. A részroblémák otimális megoldásáak kifejezése az összetevők otimális megoldásaival. 5
N + 1 ha i = 0 X > 0 0 ha X = 0 Ot(X,i) = Ot(X,i 1) ha X < i mi(ot(x,i 1),1 + Ot(X i,i 1)) ha X i (1) ublic class OtPezValt { ublic static it[] Valto(it E, it[] P){ it =P.legth; it [] Ot=ew it[e+1]; it V[][]=ew it[e+1][+1]; it i,x,rot; for (x=1; x<=e; x++){ Ot[x]=+1; V[x][0]=+1; Ot[0]=0; if (P[0]<=E){ Ot[P[0]]=1; V[P[0]][0]=0; for (; i<; i++) for (x=e; x>0; x--){ if (x>=p[i]) rot=ot[x-p[i]]+1; else rot=+1; if (rot<ot[x]) { Ot[x]=rot; V[x][i]=i; else V[x][i]=V[x][i-1]; it db=0; x=e; i=-1; if (Ot[E]>) retur ull; do{ i=v[x][i]; db++; x-=p[i]; --i; while(x>0); it C[]=ew it[db]; db=0; x=e; i=-1; do{ i=v[x][i]; C[db++]=i; x-=p[i]; --i; while(x>0); retur C; 6
A diamikus rogramozás stratégiája. A diamikus rogramozás, mit robléma-megoldási stratégia az alábbi öt léés végrehajtását jeleti. 1. Az [otimális] megoldás szerkezetéek elemzése. 2. Részroblémákra és összetevőkre botás úgy, hogy: a) Az összetevőktől való függés körmetes legye. b) Mide részrobléma [otimális] megoldása kifejezhető legye (rekurzíva) az összetevők [otimális] megoldásaival. 3. Részroblémák [otimális] megoldásáak kifejezése (rekurzíva) az összetevők [otimális] megoldásaiból. 4. Részroblémák [otimális] megoldásáak kiszámítása alulról-felfelé haladva: a) A részroblémák kiszámítási sorredjéek meghatározása. Olya sorba kell raki a részroblémákat, hogy mide részrobléma mide összetevője (ha va) előbb szereelje a felsorolásba, mit. b) A részroblémák kiszámítása alulról-felfelé haladva, azaz táblázat-kitöltéssel. 5. Egy [otimális] megoldás előállítása a 4. léésbe kiszámított (és tárolt) iformációkból. 7.6. Otimális biáris keresőfa előállítása A F = (M, R, Adat) absztrakt adatszerkezetet biáris keresőfáak evezzük, ha 1. F biáris fa, 2. Adat : M Elemti és Elemti-o értelmezett egy lieáris redezési reláció, 3. ( x M)( F bal(x) )( q F jobb(x) )(Adat() Adat(x) Adat(q)) A BINKERFAKERES függvéyeljárás egy yilvávaló megoldása a fába keresés feladatak. x a 2 a 1 q a 3 3. ábra. Biáris keresőfa ublic BiFa<E> BiKerFaKeres(E a, BiFA F){ while (F=ull) && (a=f.elem) if (a<f.elem) F=F.bal else F=F.jobb; retur F; Tegyük fel, hogy ismerjük mide k i kulcs keresési gyakoriságát, ami i (i = 1,...,) Továbbá ismert azo k kulcsok (sikertele) keresési gyakorisága, amelyre k i < k < k i+1, ami q i (,...,), és q 0 a k < k 1 kulcsok keresési gyakorisága. 7
x 5 k5 x 3 k 3 x 10 k 10 k 1 x 1 x 4 k 4 x 6 k 6 x 2 k 2 x 8 k 8 x 7 k 7 x9 k9 4. ábra. 10 adatot (kulcsot) tartalmazó biáris keresőfa x 5 k 5 x 3 k 3 x 10 k 10 x 1 k 1 x 4 k 4 x 6 k 6 y 10 y 0 x 2 k 2 y 3 y 4 y 5 x 8 k 8 y 1 y 2 x 7 k 7 x9 k9 y 6 y 7 y 8 y 9 5. ábra. Biáris keresőfa kiegészítve sikertele keresési otokkal 8
Átlagos keresési idő (költség): V (F) = i d F (x i ) + q i d F (y i ), ahol d F () a ot mélysége az F fába. Probléma: Otimális biáris keresőfa előállítása. Bemeet: P = 1,..., sikeres és Q = q 0,...,q sikertele keresési gyakoriságok. Kimeet: Olya F biáris keresőfa, amelyek a V (F) költsége miimális. Az otimális megoldás szerkezete. Tegyük fel, hogy a k 1,...,k kulcsokat tartalmazó F biáris keresőfa otimális, azaz V (F) miimális. Jelölje x r a fa gyökerét. Ekkor az F 1 = F bal(xr ) fa a k 1,...,k kulcsokat, az F 2 = F jobb(xr ) fa edig a k r+1,...,k kulcsokat tartalmazza. Mivel k r F 1 F 2 iorder(f 1 ) = k 1,...k iorder(f 2 ) = k r+1,...,k 6. ábra. Ha az otimális fa gyökerébe a k r kulcs va. d F (x) = d F1 (x) + 1 és d F (x) = d F2 (x) + 1. V (F) = = = + = + = i=r+1 i=r+1 i d F (x i ) + i d F (x i ) + q i d F (y i ) i (d F1 (x i ) + 1) + i + i + q i d F (y i ) + r + i (d F2 (x i ) + 1) + i=r+1 i=r+1 q i (d F1 (y i ) + 1) + r q i (d F2 (y i ) + 1) q i + i d F1 (x i ) + q i d F1 (y i ) i d F2 (x i ) + i=r q i d F2 (y i ) q i +V (F 1 ) +V (F 2 ) i d F (x i ) + i=r q i d F (y i ) Tehát V (F) = i + q i +V (F 1 ) +V (F 2 ) (2) Az F 1 fa a k 1,...,k kulcsokat tartalmazó otimális biáris keresőfa a 1,..., sikeres és q 0,...,q sikertele keresési gyakoriságokra, az F 2 fa edig k r+1,...,k kulcsokat tartalmazó otimális biáris keresőfa a r+1,..., sikeres és q r,...,q sikertele keresési gyakoriságokra. A bizoyítás a kivágás-és-beillesztés módszerrel végezhető. Ha lee olya F 1 biáris keresőfa a 1,..., sikeres és q 0,...,q sikertele keresési gyakoriságokra, hogy V (F 1 ) < V (F 1 ), akkor az F fába F 1 helyett az F 1 részfát véve olya fát kaák a 1,..., sikeres és q 0,...,q sikertele keresési gyakoriságokra, amelyek költsége i + q i + V (F 1 ) + V (F 2 ) < V (F). Ugyaígy bizoyítható, hogy F 2 is otimális fa a k r+1,...,k kulcsokra a r+1,..., sikeres és q r,...,q sikertele keresési gyakoriságokra. Részroblémákra botás. 9
Mide (i, j) idexárra 0 i j tekitsük azt a részroblémát hogy mi az otimális biáris keresőfa az i+1,..., j sikeres és q i,...,q j sikertele keresési gyakoriságokra. Jelölje Ot(i, j) az otimális fa költségét az (i, j) részroblémára. Az otimális megoldás értékéek rekurzív kiszámítása. Vezessük be a j j W(i, j) = u + q u u=i+1 u=i jelölést. Mide (i, j)-re a (2) kélet miatt biztosa létezik olya i < r j, hogy Ot(i, j) = W(i, j) + Ot(i,r 1) + Ot(r, j), csak azt em tudjuk, hogy melyik r-re. Tehát azt az r-et keressük, amelyre a feti összeg miimális lesz. Tehát Ot(i, j) a következő rekurzív összefüggéssel számítható. { qi ha i = j Ot(i, j) = W(i, j) + mi (Ot(i,r 1) + Ot(r, j)) ha i < j (3) i<r j Az összetevők és a kiszámítási sorred meghatározása. Az (i,i) részroblémákak ics összetevőjük, mert Ot(i,i) = q i. Az (i, j), i < j részrobléma összetevői az (i,r 1) és (r, j), r = i + 1,..., j részroblémák. Tehát a táblázatot ki tudjuk töltei átlósa haladva, a m-edik átlóba m = 1,..., azo (i, j)részroblémákat számítjuk, amelyekre j i = m. Kiszámítás alulról-felfelé haladva (táblázat-kitöltés). N q j? qj qi 0 q0 q1 0 i N 7. ábra. Táblázat-kitöltési sorred Ahhoz, hogy egy otimális megoldást elő tudjuk állítai, mide (i, j) részroblémára tároljuk egy G táblázat G[i, j]-elemébe azt az r értéket, amelyre a (3) kéletbe az miimum előáll. Ez az r lesz a k i+1,...,k j kulcsokat tartalmazó otimális biáris keresőfa gyökere. A G[i, j] értékeket felhaszálva a FASIT rekurzív eljárás állítja elő téylegese az algoritmus kimeetét jelető keresőfát. ublic class OtBiKerFa { ublic class BiFaot{ it bal, jobb; rivate void Fasit(it Aa, it i, it j){ // Előallítja az i+1..j elemek OB keresőfáját a G értékekből 10
// Globális: G, F if (Aa=0){ F[Aa].bal = G[i][Aa-1]; F[Aa].jobb = G[Aa][j]; Fasit(G[i][Aa-1], i, Aa-1); Fasit(G[Aa][j], Aa, j); rivate it[][] G; rivate BiFaot[] F; ublic BiFaot[] Eit(float[] P, float[] Q){ it =P.legth-1; F=ew BiFaot[+1]; float[][] Ot=ew float[+1][+1]; float[][] W=ew float[+1][+1]; G=ew it[+1][+1]; it otr; float V, otv; for (it ; i<=; i++){ W[i][i]=Q[i]; G[i][i]=0; Ot[i][i]=Q[i]; F[i]=ew BiFaot(); //iicializálás for (it m=1; m<=; m++) for (it ; i<=-m; i++){ it j=i+m; W[i][j]=W[i][j-1]+P[j]+Q[j]; otr=j; otv=ot[i][j-1]+q[j]; //=Ot[j,j] for (it r=i+1; r<=j-1; r++){ V = Ot[i][r-1]+Ot[r][j]; if (V < otv){ otv=v; otr=r; ; Ot[i][j]=W[i][j]+otV; G[i][j]=otr; Fasit(G[0][], 0, ); F[0].bal=G[0][]; retur F; A OPTBINKERFA algoritmus futási ideje Θ( 3 ). Bizoyítás élkül megjegyezzük, hogy a for (it r=i+1; r<=j-1; r++) ciklusba elegedő lee az r ciklusváltozót G[i,j-1]+1 -től G[i+1,j]-ig futtati, és ezzel az algoritmus futási ideje Θ( 2 ) lee. 11