TDT4120 Algoritmer og datastrukturer Interaktiv pensumguide · H2026
Fremgang
0/14
06
Designmetoder

Dynamisk programmering

Kjernepensum ~50 min lesing · DP · Optimering

Dynamisk programmering (DP) er en designmetode for optimeringsproblemer der den rekursive løsningen løser de samme delinstansene om og om igjen. I stedet for å regne dem ut på nytt løser vi hver delinstans én gang og slår opp svaret senere. Naiv rekursjon kan være eksponentiell; DP gjør den polynomisk ved å bytte gjentatt regning mot litt minne.

To forutsetninger: delstruktur og overlapp

DP er riktig verktøy nøyaktig når problemet har to egenskaper samtidig.

Det er nyttig å se dette som en delinstansgraf: én node per distinkt delinstans, med en kant fra en delinstans til hver delinstans den avhenger av. Overlapp betyr at flere veier ender i samme node. Optimal delstruktur betyr at en nodes verdi kan regnes ut fra verdiene til etterfølgerne. Kjøretiden til DP-en er da ganske enkelt: antall noder ×\times arbeidet per node.

Memoisering vs. iterasjon

Det finnes to måter å fylle inn delinstansgrafen på — de regner ut nøyaktig de samme verdiene, men i motsatt rekkefølge.

Top-down (memoisert)

Skriv den naturlige rekursjonen, men lagre hvert svar i en tabell første gang det regnes ut, og returner det lagrede svaret ved senere kall. Bekvemt, og regner bare ut de delinstansene som faktisk trengs. Bærer rekursjonens stakk-kostnad.

Bottom-up (iterativ)

Sorter delinstansene topologisk etter avhengighet og fyll tabellen med løkker fra de minste opp. Ingen rekursjon, ofte lavere konstantfaktor, og lett å resonnere om plassbruken — men du må selv finne en gyldig utfyllingsrekkefølge.

Begge gir samme asymptotiske kjøretid. Verdien vi lagrer er som regel bare den optimale tallverdien; for å få selve løsningen lagrer vi i tillegg hvilket valg som var optimalt i hver celle, og rekonstruerer ved å følge valgene bakover fra det ferdige svaret.

Oppskrift: slik setter du opp en DP

  1. Karakteriser strukturen

    Vis at en optimal løsning er bygd av optimale delløsninger — finn det første valget som splitter problemet i mindre, uavhengige delinstanser.

  2. Definer verdien rekursivt

    Skriv OPT(delinstans)\text{OPT}(\text{delinstans}) som et maksimum/minimum over de mulige første valgene, uttrykt ved OPT\text{OPT} av mindre delinstanser. Husk grunntilfellene.

  3. Regn ut nedenfra (eller memoiser)

    Fyll en tabell over alle distinkte delinstanser i en rekkefølge som respekterer avhengighetene — bottom-up med løkker, eller top-down med memoisering.

  4. Rekonstruer løsningen

    Lagre hvilket valg som ga optimum i hver celle, og følg valgene bakover fra svaret for å sette sammen den faktiske løsningen.

Stavkapping

I stavkapping har vi en stav av lengde nn og en prisliste p[1..n]p[1..n], og skal kappe den i biter som maksimerer total pris. Det første valget er lengden ii på den første biten; resten er en stav av lengde nin-i, som vi løser optimalt på samme måte. Det gir rekurrensen

r(n)=max1in(p[i]+r(ni)),r(0)=0.r(n) = \max_{1 \le i \le n}\big(\, p[i] + r(n-i) \,\big), \qquad r(0) = 0.

Det er bare n+1n+1 distinkte delinstanser r(0),,r(n)r(0), \dots, r(n), og hver tar O(n)O(n) arbeid, så bottom-up-DP-en er Θ(n2)\Theta(n^2) — mot eksponentiell naiv rekursjon. Med prislisten p=[1,5,8,9]p = [1, 5, 8, 9] fyller vi tabellen nedenfra:

nnr(n)r(n)Optimalt første kutt
0000
111111
225522
338833
4410102+22 + 2
Stavkapping med p=[1,5,8,9]p = [1,5,8,9]. r(4)=10r(4) = 10 via to biter à lengde 2 (5+55+5), bedre enn én bit til 9. Den siste kolonnen er valget vi lagrer for å rekonstruere kuttene.

Lengste felles delsekvens (LCS)

LCS finner den lengste sekvensen som forekommer (ikke nødvendigvis sammenhengende) i begge to strenger XX og YY. Sammenlign de siste tegnene: er de like, hører tegnet med i LCS-en, og vi går videre på begge prefiksene. Er de ulike, er svaret det beste av å droppe det siste tegnet i XX eller i YY:

c[i,j]={0i=0 eller j=0c[i1,j1]+1xi=yjmax(c[i1,j],c[i,j1])xiyj.c[i,j] = \begin{cases} 0 & i=0 \text{ eller } j=0 \\ c[i-1,j-1] + 1 & x_i = y_j \\ \max(c[i-1,j],\, c[i,j-1]) & x_i \ne y_j. \end{cases}

Tabellen cc har (m+1)(n+1)(m+1)(n+1) celler og hver fylles i O(1)O(1), så LCS er Θ(mn)\Theta(mn). Lagrer vi hvilken av de tre reglene som ga hver celle, kan vi rekonstruere selve delsekvensen ved å følge pilene fra c[m,n]c[m,n] tilbake til hjørnet.

Binært ryggsekkproblem

Det binære ryggsekkproblemet (appendiks E) gir nn gjenstander med vekt wiw_i og verdi viv_i og en sekk med kapasitet WW. Hver gjenstand tas helt eller ikke i det hele tatt (derav «binært»). Vi maksimerer verdien innenfor kapasiteten. Det første valget er om gjenstand ii er med:

K[i,c]=max(K[i1,c],  vi+K[i1,cwi]),K[i, c] = \max\big(\, K[i-1, c],\ \ v_i + K[i-1, c - w_i] \,\big),

der den andre grenen bare gjelder når wicw_i \le c. Tabellen har nWn \cdot W celler, hver fylt i O(1)O(1), så DP-en er Θ(nW)\Theta(nW).

Sjekk deg selv

Hva gjør delinstanser overlappende?