6.2 DP-klassikere — stavkapping, LCS og ryggsekk
Rod-Cutting (stavkapping), LCS (lengste felles delsekvens) og 0-1-Knapsack (ryggsekk) — de navngitte DP-problemene og deres rekurrenser.
To sjangre henter herfra, og de skrives ut i klarspråk:
- Sjanger C — håndkjøring: du utfører algoritmen steg for steg og leverer bare sluttilstanden i det formatet oppgaven ber om. For dette kapitlet betyr det som regel en utfylt tabell, en lengde eller en oppdeling.
- Sjanger H — åpen algoritmedesign: du får et problem i ny innpakning og skisserer en algoritme på fem–ti linjer. De tre problemene her er malene du gjenkjenner innpakningene mot.
Ett faktum går igjen som et rent kunnskapsspørsmål, og det er verdt å merke seg allerede nå: 0-1-ryggsekk løses i , og det er ikke det samme som polynomisk tid. Uttrykket kalles pseudopolynomisk, og begrepet innføres i dette kapitlet — det er koblingen videre til teorien om vanskelige problemer i Del 7.
Prioritet: bør sitte. Rekurrensene her er korte nok til å pugges, men det er ikke poenget: du skal kunne utlede dem ved å se på det siste valget, for det er den ferdigheten en designoppgave faktisk måler.
Forkunnskaper
Dette kapitlet forutsetter mekanikken fra kap. 6.1: optimal substruktur, overlappende delproblemer, skillet mellom memoisering og utfylling nedenfra og opp, og at rekonstruksjon krever en lagret valgtabell. Alle tre problemene her er den mekanikken anvendt på et navngitt problem.
I tillegg trenger du:
- kap. 1.1 — mot , og hvorfor det er et bevisst valg hvilken du skriver.
- kap. 1.5 — å summere en rekke. Stavkapping gir summen , og den skal du kjenne igjen.
Ordet pseudopolynomisk dukker også opp om Ford-Fulkerson i kap. 5.2, men du trenger ikke ha lest det kapitlet: begrepet bygges opp fra grunnen av her, i den formen ryggsekkproblemet krever.
Stavkapping — når oppdelingen er verdt mer enn hele (~15 min)
Dette er stavkapping (rod cutting), og det er det enkleste av de navngitte DP-problemene. Grepet er det samme som i kap. 6.1: se på ett valg, og la resten være et mindre delproblem av samme type.
Det naturlige valget her er lengden på det første stykket. Kapper du først av meter, får du for det stykket, og resten er en stav av lengde som du løser på nøyaktig samme måte. Prøver du alle lovlige , har du rekurrensen:
Intuisjon: det finnes ingen grunn til å behandle det første stykket spesielt — vi kunne like gjerne sett på det siste. Poenget er at ett valg deler problemet i «et stykke vi tar betalt for nå» og «et mindre stavkappingsproblem». Grunntilfellet sier at en stav med lengde null er verdt ingenting, og det er det du trenger for å stoppe rekursjonen.
Legg merke til at «ikke kappe i det hele tatt» er med som et lovlig valg: gir . Det er ikke en detalj — for enkelte prislister er hele staven faktisk best.
Problemet der en stav av heltallslengde skal deles i heltallsbiter, og hver bit av lengde selges for , slik at samlet inntekt blir størst mulig.
Kappene er gratis, og du kan bruke like mange biter av samme lengde som du vil. Det er et rent optimeringsproblem, og det har både optimal substruktur (en optimal oppdeling av resten inngår i en optimal oppdeling av helheten) og overlappende delproblemer (den samme reststaven dukker opp fra mange forskjellige første kapp).
Løses med dynamisk programmering i tid og plass.
I ord: inntekten for en stav av lengde er den største av «pris for et stykke på pluss den beste inntekten for resten», over alle lovlige .
Kjøretiden følger direkte: delproblem sammenligner kandidater, og , altså .
p[1..n], indeksert fra 1, med p[i] som prisen for et stykke av lengde i. Alle lengder er heltall. To tabeller fylles: r[0..n] med inntektene og s[1..n] med valgene.2. Pre-/postbetingelse. Før: n >= 0 og p[1..n] er gitt. Etter: r[j] er den største oppnåelige inntekten for en stav av lengde j, for hver j, og s[j] er lengden på det første stykket i en optimal oppdeling av j.
3. Pseudokoden.
Extended-Bottom-Up-Cut-Rod(p, n)
Input: prislisten p[1..n]
Output: inntektstabellen r[0..n] og valgtabellen s[1..n]
la r[0..n] og s[1..n] vaere nye tabeller
r[0] = 0
for j = 1 to n
q = -uendelig
for i = 1 to j
if q < p[i] + r[j-i]
q = p[i] + r[j-i]
s[j] = i
r[j] = q
return r, s
Print-Cut-Rod-Solution(p, n)
Input: prislisten p og stavlengden n
Output: lengdene i en optimal oppdeling
(r, s) = Extended-Bottom-Up-Cut-Rod(p, n)
while n > 0
skriv ut s[n]
n = n - s[n]4. Invarianten i én setning. Før hver runde av den ytre løkka med telleverdi j inneholder r[0..j-1] de riktige optimalverdiene — derfor er hver r[j-i] den indre løkka slår opp i, allerede ferdig.
5. Kjøretid. Den indre løkka går j runder for hver j, så antall sammenligninger er , altså . Print-Cut-Rod-Solution gjør høyst n runder à konstant arbeid, altså — rekonstruksjonen øker dermed ikke den asymptotiske kjøretiden.
Verkstedet har denne prislisten, i hundre kroner per stykke:
| Lengde | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| Pris | 4 | 10 | 16 | 18 | 21 | 24 | 29 | 39 |
Fyll tabellene og for , og oppgi den beste inntekten for en åtte meters profil sammen med hvilken oppdeling som gir den.
| Kandidater | |||
|---|---|---|---|
| 1 | 4 | 1 | |
| 2 | , | 10 | 2 |
| 3 | , , | 16 | 3 |
| 4 | , , , | 20 | 1 |
| 5 | , , , , | 26 | 2 |
| 6 | , , , , , | 32 | 3 |
| 7 | , , , , , , | 36 | 1 |
| 8 | , , , , , , , | 42 | 2 |
og .
Rekonstruksjonen. Start på . , så kapp av 2 meter og fortsett med 6. , kapp av 3 og fortsett med 3. , kapp av 3 og fortsett med 0. Ferdig.
Svaret:
- Beste inntekt: 42 (altså 4 200 kroner).
- Oppdelingen: 2 + 3 + 3.
Merk hva som skjer her. Å selge profilen hel gir bare . Oppdelingen er verdt 3 mer, og det er ikke synlig fra prislisten uten å regne. Merk også at oppgaven ber om selve oppdelingen, ikke bare inntekten — uten valgtabellen hadde du bare hatt tallet 42.
Når likhet oppstår, som ved der tre kandidater alle gir 20, låser pseudokoden seg på den første som ble funnet (). Det er et vilkårlig, men deterministisk valg: en annen tie-break gir en annen oppdeling med samme verdi, og begge er riktige svar.
Oppgi kjøretiden til Extended-Bottom-Up-Cut-Rod på strammest mulig form, og skriv den ene setningen som begrunner den.
En trelastbedrift har denne prislisten for planker:
| Lengde | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| Pris | 2 | 6 | 7 | 10 | 13 |
Oppgi tabellen og den optimale oppdelingen av en plank på 5 meter.
Lengste felles delsekvens (~20 min)
Det er problemet lengste felles delsekvens (longest common subsequence, LCS), og det er den mest brukte DP-malen i faget. Får du en oppgave om å sammenligne to sekvenser — tekst, gensekvenser, hendelseslogger, ruter — er dette mønsteret du gjenkjenner den mot.
Grepet er igjen å se på ett valg, og her er det det siste tegnet i hver sekvens. La og , og la være lengden på lengste felles delsekvens av de første tegnene i og de første i . Da er det to tilfeller:
- Er , kan du ta med det tegnet, og resten av problemet er .
- Er de forskjellige, kan ikke begge være med som siste tegn. Da må minst ett av dem droppes, og du tar det beste av å droppe eller å droppe .
Intuisjon: i det første tilfellet er det aldri dumt å ta med et tegn som matcher — det kan bevises med et bytteargument, og det er derfor rekurrensen ikke trenger å sammenligne med noe annet der. I det andre tilfellet vet vi bare at minst ett av de to tegnene ikke kan brukes som siste tegn, og da må begge muligheter prøves.
Tabellen har celler og hver celle koster konstant tid, altså .
En delsekvens av en sekvens er det du får ved å stryke null eller flere elementer og beholde rekkefølgen på resten.
PRG er en delsekvens av SPRANG, fordi tegnene står i den rekkefølgen der — men de står ikke ved siden av hverandre. Det er forskjellen fra en delstreng, som må være sammenhengende.
En sekvens av lengde har delsekvenser, og det er derfor uttømmende søk ikke er aktuelt: for to sekvenser av lengde 30 hver ville det bety over en milliard sammenligninger.
Den lengste sekvensen som er delsekvens av begge de to inputsekvensene.
Løses med dynamisk programmering i tid og plass, der og er lengdene. Vil du bare ha lengden og ikke selve sekvensen, holder det med to rader av tabellen, altså plass — men da mister du muligheten til å rekonstruere.
LCS er ikke nødvendigvis entydig: to forskjellige delsekvenser kan ha samme maksimale lengde, og begge er riktige svar.
I ord: matcher tegnene, teller du dem med og går skrått opp til venstre. Matcher de ikke, tar du det beste av å stryke det siste tegnet i den ene eller i den andre sekvensen.
Grunntilfellene er hele rad 0 og hele kolonne 0, som alle er null: en tom sekvens har ingen felles delsekvens med noe.
X[1..m] og Y[1..n], indeksert fra 1. Verditabellen er c[0..m, 0..n] og retningstabellen b[1..m, 1..n]. Retningene skrives SKRA (opp til venstre), OPP og VENSTRE, og svarer til , og i figuren under.2. Pre-/postbetingelse. Før: m, n >= 0. Etter: c[i,j] er lengden på lengste felles delsekvens av X[1..i] og Y[1..j] for alle i, j, og b[i,j] peker på cellen den verdien kom fra. Svaret på hele problemet står i c[m,n].
3. Pseudokoden.
LCS-Length(X, Y)
Input: sekvensene X[1..m] og Y[1..n]
Output: tabellene c[0..m, 0..n] og b[1..m, 1..n]
la c[0..m, 0..n] og b[1..m, 1..n] vaere nye tabeller
for i = 0 to m
c[i, 0] = 0
for j = 0 to n
c[0, j] = 0
for i = 1 to m
for j = 1 to n
if X[i] == Y[j]
c[i, j] = c[i-1, j-1] + 1
b[i, j] = SKRA
else if c[i-1, j] >= c[i, j-1]
c[i, j] = c[i-1, j]
b[i, j] = OPP
else
c[i, j] = c[i, j-1]
b[i, j] = VENSTRE
return c, b4. Invarianten i én setning. Når løkkene når celle (i, j), er alle tre cellene den leser — (i-1, j-1), (i-1, j) og (i, j-1) — allerede ferdig utfylt, fordi tabellen fylles rad for rad ovenfra og venstre mot høyre.
5. Kjøretid. Tabellen har celler, og hver celle koster konstant tid: . Plassbruken er også når b skal beholdes til rekonstruksjon.
b er ferdig utfylt av LCS-Length, og X[1..m] er tilgjengelig. Rutinen kalles med i = m, j = n.2. Pre-/postbetingelse. Før: b er fylt ut for alle celler med i >= 1 og j >= 1. Etter: rutinen har skrevet ut én lengste felles delsekvens, i riktig rekkefølge fra venstre mot høyre, og tabellene er urørt.
3. Pseudokoden.
Print-LCS(b, X, i, j)
Input: retningstabellen b, sekvensen X, og en celle (i, j)
Output: en lengste felles delsekvens av X[1..i] og Y[1..j]
if i == 0 or j == 0
return
if b[i, j] == SKRA
Print-LCS(b, X, i-1, j-1)
skriv ut X[i]
else if b[i, j] == OPP
Print-LCS(b, X, i-1, j)
else
Print-LCS(b, X, i, j-1)4. Grunnideen i én setning. Hvert kall flytter seg minst ett hakk mot celle (0, 0), og skriver ut et tegn nøyaktig når retningen er SKRA — altså nøyaktig når de to tegnene matchet.
5. Kjøretid. Hvert kall reduserer i + j med minst 1, så antall kall er høyst , og hvert kall gjør konstant arbeid: . Det er mindre enn , så rekonstruksjonen øker ikke den asymptotiske kjøretiden — men den krever at b faktisk ble lagret.
Finn lengste felles delsekvens av SPRANG og PARKING. Fyll tabellen , oppgi lengden, og oppgi selve delsekvensen.
SPRANG, kolonner er PARKING):| 1 (P) | 2 (A) | 3 (R) | 4 (K) | 5 (I) | 6 (N) | 7 (G) | ||
|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
| 1 (S) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 (P) | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| 3 (R) | 0 | 1 | 1 | 2 | 2 | 2 | 2 | 2 |
| 4 (A) | 0 | 1 | 2 | 2 | 2 | 2 | 2 | 2 |
| 5 (N) | 0 | 1 | 2 | 2 | 2 | 2 | 3 | 3 |
| 6 (G) | 0 | 1 | 2 | 2 | 2 | 2 | 3 | 4 |
Retningstabellen , som er det rekonstruksjonen leser:
| 1 (P) | 2 (A) | 3 (R) | 4 (K) | 5 (I) | 6 (N) | 7 (G) | |
|---|---|---|---|---|---|---|---|
| 1 (S) | |||||||
| 2 (P) | |||||||
| 3 (R) | |||||||
| 4 (A) | |||||||
| 5 (N) | |||||||
| 6 (G) |
Rekonstruksjonen. Start i celle , som er :
G er med, gå til . Der står : N er med, gå til . Der står : gå til , som er — videre til venstre til , som er : R er med, gå til . Der står , videre til , som er : P er med. Så er vi i og stopper.Tegnene skrives ut i den rekkefølgen kallene returnerer, altså framlengs —
P, R, N, G.Svaret:
- Lengde: 4.
- Lengste felles delsekvens: PRNG.
Merk at
PRNG ikke står sammenhengende i noen av de to ordene — det er en delsekvens, ikke en delstreng. Merk også at hele rad 1 er null: S finnes ikke i PARKING i det hele tatt.Kjøretid: celler fylles, hver på konstant tid, altså . Rekonstruksjonen bruker 9 kall, godt innenfor grensen .
LAK er en delsekvens av FLASKE, men ingen delstreng. Blander du dem, får du et helt annet — og enklere — problem, og feil rekurrens.Av-én-feil i indeksene. Tabellen har rad 0 og kolonne 0, mens sekvensene er indeksert fra 1. Cellen handler om X[1..i] og Y[1..j], altså om tegnene og — ikke om X[i+1]. Sjekk alltid det minste tilfellet: skal være 1 hvis de to første tegnene er like, ellers 0.
Felle #6 — å påstå at rekonstruksjon fra en DP-tabell er «trivielt gratis». Verditabellen gir deg lengden, ikke sekvensen. Skal du ut med selve delsekvensen, må retningstabellen lagres — eller retningen leses tilbake ved å sammenligne nabocellene i . Det koster , altså ingen asymptotisk økning, men leddet må stå i svaret.
Felle #7 — å blande pseudopolynomisk med NP-hardt. Den kommer for fullt i neste avsnitt: at 0-1-ryggsekk løses i betyr verken at problemet er lett i teoretisk forstand, eller at det er bevist vanskelig.
Å tro at LCS er entydig. To ulike delsekvenser kan ha samme maksimale lengde. Retningstabellens tie-break avgjør hvilken du får ut, og et annet valg er like riktig.
Finn lengste felles delsekvens av LAKS og FLASKE. Oppgi tabellen og selve delsekvensen.
Ta stilling til hver påstand.
a) Kjøretiden til LCS-Length er uansett hvor mange tegn de to sekvensene har felles.
b) Vil du bare ha lengden på lengste felles delsekvens, klarer du deg med plass proporsjonal med den korteste sekvensen.
c) Bytter du om på hvilken sekvens som er og hvilken som er , kan lengden bli en annen.
0-1-ryggsekk og hva pseudopolynomisk betyr (~20 min)
Dette er 0-1-ryggsekkproblemet, og navnet kommer av nettopp den todelingen: hver gjenstand får verdien 0 eller 1, aldri noe imellom. Igjen ser vi på ett valg — gjenstand — og lar resten være et mindre delproblem:
- Lar du gjenstand ligge, har du de første gjenstandene og hele kapasiteten igjen.
- Tar du den med (mulig bare når ), får du verdien pluss det beste du kan få av de første med kapasitet .
Intuisjon: delproblemet må ha to parametre. Med bare «de første gjenstandene» vet du ikke hvor mye plass som er brukt opp, og da kan du ikke avgjøre om gjenstand får plass. Restkapasiteten er den andre dimensjonen, og det er derfor tabellen blir todimensjonal.
Tabellen har rader og kolonner, hver celle koster konstant tid, altså . Og her ligger fellen som gjør dette problemet interessant helt inn i Del 7.
Problemet der gjenstander med vekt og verdi skal velges ut, slik at samlet vekt er høyst kapasiteten og samlet verdi er størst mulig — og hver gjenstand enten tas hel eller ikke i det hele tatt.
Løses med dynamisk programmering i tid og plass. Kjøretiden er pseudopolynomisk, ikke polynomisk, fordi er en tallverdi i inputen og ikke en telling av elementer.
Kan ikke løses grådig: å ta gjenstandene i synkende verdi per kilo gir ikke alltid optimum, og det er lett å konstruere et moteksempel.
og når gjenstanden er for tung, med for alle .
I ord: enten lar du gjenstanden ligge og beholder hele kapasiteten, eller så tar du den, betaler av kapasiteten og legger til verdien . Tabellen fylles rad for rad, fordi hver rad bare leser raden over.
Du har fem gjenstander og kapasitet kilo:
| Gjenstand | Vekt | Verdi | |
|---|---|---|---|
| 1 | telt | 5 | 12 |
| 2 | sovepose | 4 | 10 |
| 3 | primus | 3 | 6 |
| 4 | kikkert | 2 | 5 |
| 5 | matpakke | 1 | 2 |
Fyll tabellen , oppgi den største oppnåelige verdien, og oppgi hvilke gjenstander som skal med. Sammenlign til slutt med det du ville fått ved å pakke grådig etter verdi per kilo.
| Rad / kapasitet | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 (ingen) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 (telt) | 0 | 0 | 0 | 0 | 0 | 12 | 12 | 12 | 12 | 12 |
| 2 (sovepose) | 0 | 0 | 0 | 0 | 10 | 12 | 12 | 12 | 12 | 22 |
| 3 (primus) | 0 | 0 | 0 | 6 | 10 | 12 | 12 | 16 | 18 | 22 |
| 4 (kikkert) | 0 | 0 | 5 | 6 | 10 | 12 | 15 | 17 | 18 | 22 |
| 5 (matpakke) | 0 | 2 | 5 | 7 | 10 | 12 | 15 | 17 | 19 | 22 |
Rekonstruksjonen. Start i og gå oppover. Er lik , ble gjenstand ikke tatt; er de forskjellige, ble den tatt, og du trekker fra kapasiteten.
- : matpakken er ikke med.
- : kikkerten er ikke med.
- : primusen er ikke med.
- : soveposen er med. Ny kapasitet: .
- : teltet er med. Ny kapasitet: .
Svaret:
- Største verdi: 22.
- Pakkelisten: telt og sovepose, samlet vekt 9 kilo.
Sammenligningen med grådighet. Verdi per kilo er 2,4 for teltet, 2,5 for soveposen, 2,0 for primusen, 2,5 for kikkerten og 2,0 for matpakken. Pakker du grådig etter den rangeringen, tar du sovepose (4 kg), kikkert (2 kg) og primus (3 kg) — akkurat ni kilo — til en samlet verdi på . Det er dårligere enn 22, og det er et konkret moteksempel mot å løse 0-1-ryggsekk grådig.
Kjøretid: celler i selve kjernen, hver på konstant tid, altså . Rekonstruksjonen går én rad om gangen oppover, altså , og øker ikke kjøretiden.
Nå til det som gjør dette problemet spesielt. Kjøretiden ser polynomisk ut — det er et produkt av to tall fra oppgaven, ikke noe eksponentielt. Men det er den ikke, og forskjellen er verdt å bruke et par minutter på.
Når vi måler kjøretid i dette faget, måler vi den mot størrelsen på inputen. For et array med tall er en grei størrelse: arrayet består av elementer, så det tar plass proporsjonal med . Kapasiteten er noe helt annet. Den er ett tall, og et tall på skrives med omtrent siffer. Skal du skrive kapasiteten 1 000 000, bruker du sju siffer — ikke en million.
Konsekvensen: en algoritme som bruker tid, bruker tid som vokser eksponentielt i antall siffer i . Legger du til ett siffer på kapasiteten, blir problemet ti ganger så tungt, selv om inputen bare ble ett tegn lengre. Tabellen under er kjørt for et fast lite antall gjenstander:
| Kapasitet | Siffer i | Celler med |
|---|---|---|
| 10 | 2 | 30 |
| 100 | 3 | 300 |
| 1 000 | 4 | 3 000 |
| 10 000 | 5 | 30 000 |
| 100 000 | 6 | 300 000 |
Det er dette som kalles pseudopolynomisk: polynomisk i tallverdiene, men ikke i lengden på inputen. Er kapasitetene små, er algoritmen helt utmerket i praksis — og det er nettopp derfor den brukes. Er de store, hjelper den ikke.
Hva dette IKKE betyr. At kjøretiden er pseudopolynomisk, er ikke et bevis for at problemet er vanskelig, og «pseudopolynomisk» er ikke et synonym for «NP-hardt». Det er en egenskap ved algoritmen, ikke en dom over problemet. Det motsatte forholdet gjelder heller ikke: at vi har en pseudopolynomisk algoritme, betyr ikke at problemet er lett. Skillet er felle #7 — å blande pseudopolynomisk og NP-hardt — og det er en av de feilene som oftest skiller riktig fra galt i Del 7. Samme skille gjelder
Ford-Fulkerson i kap. 5.2, der kjøretiden avhenger av kapasitetenes størrelse på nøyaktig samme måte.En kjøretid som er polynomisk i tallverdiene i inputen, men ikke i lengden på inputen målt i siffer eller bits.
0-1-ryggsekkens er standardeksempelet: er kapasiteten, altså ett tall, og et tall på skrives med omtrent siffer. Dobler du , dobler du arbeidet — men inputen ble bare ett bit lengre.
Merk hva begrepet ikke sier: det er en egenskap ved algoritmen, ikke en påstand om at problemet er NP-hardt, og heller ikke en påstand om at problemet er lett.
Varianten der gjenstandene kan deles: du kan ta 40 % av en sekk sand og få 40 % av verdien.
Til forskjell fra 0-1-varianten løses den grådig: sortér etter verdi per vektenhet, fyll med den beste først, og del den siste gjenstanden slik at sekken blir akkurat full. Algoritmen og bytteargumentet som viser at den er optimal, hører til kap. 6.4.
Kontrasten er poenget her: den lille endringen fra «hel eller ingenting» til «kan deles» flytter problemet fra dynamisk programmering til grådighet. På pakkelisten i Eksempel 3 ville den fraksjonelle varianten gitt 22,2 mot 0-1-variantens 22.
Forklar med egne ord hva det betyr at kjøretiden til 0-1-ryggsekk er pseudopolynomisk, og si i én setning hva begrepet ikke sier.
Tre gjenstander skal pakkes i en sekk med kapasitet :
| Vekt | Verdi | |
|---|---|---|
| 1 | 3 | 9 |
| 2 | 4 | 10 |
| 3 | 5 | 12 |
Oppgi siste rad i tabellen , den største oppnåelige verdien og hvilke gjenstander som velges.
Ta stilling til påstandene. Ja eller nei først, deretter én setning.
a) Fordi 0-1-ryggsekk løses i , er problemet løst i polynomisk tid.
b) Fraksjonell ryggsekk kan løses grådig.
c) Å ta gjenstandene i synkende verdi per vektenhet gir alltid optimal løsning på 0-1-ryggsekk.
To turgrupper har hver ført dagbok over hvilke hytter de overnattet på, i den rekkefølgen de kom dit. Den ene lista har oppføringer, den andre . Turlaget vil vite den lengste rekkefølgen av hytter begge gruppene besøkte i samme rekkefølge, og vil ha selve hyttelista, ikke bare hvor lang den er. Gruppene kan ha vært på hytter den andre ikke besøkte, og de kan ha brukt ulikt antall dager.
Beskriv en algoritme. Svar i designformatet: hvilket klassisk problem dette er, paradigmet, konstruksjonen, rekonstruksjonen og kjøretiden.
Kjøretidene i dette kapitlet samlet
| Algoritme | Beste | Verste | Forventet | Krav/egenskap |
|---|---|---|---|---|
Bottom-Up-Cut-Rod(p, n) | begge løkkene går alltid fullt ut; gir bare inntekten | |||
Extended-Bottom-Up-Cut-Rod(p, n) | fyller også valgtabellen s; nødvendig for å få oppdelingen | |||
Print-Cut-Rod-Solution(p, n) | krever utfylt s; øker ikke kjøretiden | |||
LCS-Length(X, Y) | alle celler fylles uansett innhold; plass med b | |||
Print-LCS(b, X, m, n) | krever utfylt retningstabell b | |||
Knapsack-01(w, v, n, m) | pseudopolynomisk — er en tallverdi, ikke en telling | |||
| Fraksjonell ryggsekk | grådig etter verdi per vektenhet; sorteringen dominerer (kap. 6.4) |
De tre DP-radene følger samme regel: antall delproblemer ganger arbeidet per delproblem. Stavkapping har delproblemer med voksende arbeid, altså en sum som gir . LCS og ryggsekk har delproblemer med konstant arbeid, altså . Kan du de to regnestykkene, trenger du ikke pugge tallene.
Begrepsbank
Begrepsbanken er flashcard- og repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.
Rutinen som fyller inntektstabellen og valgtabellen for stavkapping.
Den går gjennom lengdene og prøver for hver av dem alle lovlige lengder på det første stykket. Kjøretid , plass .
Kravet den stiller: prislisten må være indeksert fra 1 og lengdene må være heltall. Uten valgtabellen får du bare inntekten, ikke oppdelingen.
Rutinen som fyller lengdetabellen og retningstabellen for to sekvenser.
Den går rad for rad og kolonne for kolonne, og hver celle avgjøres av tre naboer: skrått opp til venstre når tegnene matcher, ellers den største av cellen over og cellen til venstre. Kjøretid , plass .
Kravet den stiller: sekvensene må være indeksert fra 1, og tabellene må ha en rad 0 og en kolonne 0 med nuller — grunntilfellene.
Rutinen som leser retningstabellen baklengs fra celle og skriver ut selve delsekvensen.
Den skriver ut et tegn nøyaktig når retningen er skrå, og flytter seg ellers opp eller til venstre. Kjøretid , altså mindre enn utfyllingen — rekonstruksjonen øker ikke den asymptotiske kjøretiden.
Kravet den stiller: retningstabellen må faktisk være lagret. Har du bare , kan retningen leses tilbake ved å sammenligne nabocellene, men da må du si det.
Tabellen der s[j] er lengden på det første stykket i en optimal oppdeling av en stav på .
Den fylles samtidig med inntektstabellen r, uten ekstra asymptotisk kostnad. Oppdelingen leses ut ved å skrive ut s[n], sette n = n - s[n] og gjenta til n er null.
Uten s har du bare inntekten. Ber oppgaven om selve oppdelingen — og det gjør den ofte — er tabellen påkrevd.
Målestokken kjøretid egentlig sammenlignes mot: hvor mange tegn eller bits som trengs for å skrive ned hele inputen.
En liste med tall har størrelse omtrent . Ett enkelt tall har størrelse omtrent bits — kapasiteten 1 000 000 skrives med sju siffer, ikke en million. Det er hele grunnen til at ikke er polynomisk.
Regelen å huske: teller inputen elementer, er tallet en størrelse; er inputen ett tall, er verdien noe helt annet enn størrelsen.
En delsekvens bevarer rekkefølgen, men trenger ikke være sammenhengende. En delstreng må være sammenhengende.
LAK er en delsekvens av FLASKE, men ikke en delstreng. LAS er også en delsekvens. ASK er en delstreng av FLASKE, og dermed også en delsekvens.
Blander du dem, løser du feil problem: lengste felles delstreng har en annen rekurrens, og LCS-tabellen gir feil svar på det.
Fordi den beste gjenstanden per vektenhet kan blokkere en kombinasjon som fyller sekken bedre.
Pakkelisten i Eksempel 3 viser det: grådig etter verdi per kilo velger sovepose, kikkert og primus til verdi 21, mens telt og sovepose gir 22. Uten muligheten til å dele gjenstander blir «restplassen» avgjørende, og den ser den grådige regelen ikke.
Den fraksjonelle varianten har ikke problemet: der fylles restplassen alltid opp av en bit av neste gjenstand, og da er grådighet optimal.
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.
Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Norges teknisk-naturvitenskapelige universitet. Dette er ikke offisielt studiemateriell. Les mer.