TDT4120 Algoritmer og datastrukturer Interaktivt kompendium · H2026
Fremgang
0/15

07

Grådighet

Kjernepensum

En grådig algoritme bygger løsningen gjennom en serie valg, og tar hvert valg lokalt. Den gjør det som ser best ut her og nå, uten å løse noen delinstanser først. Dynamisk programmering løser delinstansene for alle valgene og bygger på den beste delløsningen. En grådig algoritme velger først og løser bare den ene delinstansen som er igjen. Slike algoritmer er som regel korte og raske. Det kan være vanskelig å vise at de gir riktig svar, og for mange problemer gjør de ikke det.

Grådige valg

Du får punktene x1≤x2≤⋯≤xnx_1 \le x_2 \le \cdots \le x_n på tallinja, og skal dekke dem med færrest mulig lukkede intervaller med lengde 1. Et intervall må dekke x1x_1, og det er ingen punkter til venstre for x1x_1. Legg derfor intervallet [x1,x1+1][x_1, x_1 + 1], som dekker x1x_1 og går lengst til høyre. Fjern punktene det dekker, og gjenta med resten. Punktene er sortert, så algoritmen tar Θ(n)\Theta(n) tid.

En algoritme som velger slik, er grådig. Den gjør et valg cc, her intervallet [x1,x1+1][x_1, x_1 + 1], og krymper instansen til det som er igjen etter valget, her punktene til høyre for x1+1x_1 + 1. Så løser den delinstansen og setter cc og delløsningen sammen. Valget er det beste lovlige valget sett helt lokalt, og det tas før delinstansen er løst, så algoritmen kan ikke se om det lønner seg. Dynamisk programmering som prøver alle valgene, gir også riktig svar på et problem som kan løses grådig, bare langsommere.

For at den grådige algoritmen skal være riktig, må det første valget være trygt.

Intervallproblemet har grådighetsegenskapen. La II være intervallet som dekker x1x_1 i en optimal løsning. II starter i x1x_1 eller til venstre for det, så det slutter i x1+1x_1 + 1 eller tidligere. Flytt II til [x1,x1+1][x_1, x_1 + 1]. Det flyttede intervallet dekker fortsatt x1x_1 og alle punktene II dekket til høyre for x1x_1. Løsningen har like mange intervaller og tar det grådige valget.

Grådighetsegenskapen sier ingenting om valgene etter det første. Til det trengs optimal delstruktur: det grådige valget satt sammen med en optimal løsning på delinstansen som er igjen, gir en optimal løsning. I intervallproblemet tar du den optimale løsningen som har [x1,x1+1][x_1, x_1 + 1]. De andre intervallene i den dekker punktene til høyre for x1+1x_1 + 1, så de er minst like mange som i en optimal løsning på delinstansen. Bytt dem ut med en optimal løsning på delinstansen. Antallet intervaller øker ikke, så løsningen er fortsatt optimal.

Delinstansen er et problem av samme form, så grådighetsegenskapen gjelder der også, og optimal delstruktur sier at det holder å løse den optimalt. Med induksjon over størrelsen på instansen er alle de grådige valgene trygge.

Læreboka gir en oppskrift i tre trinn.

  1. Ett valg, én delinstans

    Formuler problemet så hvert valg etterlater nøyaktig én delinstans.

  2. Grådighetsegenskapen

    Vis at det alltid finnes en optimal løsning som tar det grådige valget.

  3. Optimal delstruktur

    Vis at det grådige valget sammen med en optimal løsning på delinstansen gir en optimal løsning.

Intervallproblemet følger oppskriften. Valget etterlater bare punktene til høyre for x1+1x_1 + 1, flyttingen av II til [x1,x1+1][x_1, x_1 + 1] viser grådighetsegenskapen, og byttet av de andre intervallene mot en optimal løsning på delinstansen viser optimal delstruktur.

Flyttingen av II er et utvekslingsargument, den vanlige måten å vise grådighetsegenskapen på. Start med en vilkårlig optimal løsning. Bytt ut ett av valgene i den med det grådige valget, og vis at løsningen fortsatt er lovlig og ikke blir dårligere. Da finnes en optimal løsning med det grådige valget. Å vise at et grådig valg ikke er trygt, er lettere. Ett moteksempel holder.

Eksamensoppgave Grådighetsegenskapen er ikke nok
Kontinuasjonseksamen, august 2018, oppgave 4

Om vi viser at grådighetsegenskapen (the greedy-choice property) holder har vi likevel ikke vist at grådighet er korrekt. Forklar.

Vis løsning Skjul løsning

Grådighetsegenskapen sier bare at det første grådige valget er trygt: det finnes en optimal løsning som tar det. Den sier ingenting om valgene etter det. For å vise at alle valgene er trygge, bruker du induksjon, og da må delinstansen som er igjen etter et grådig valg, være et problem av samme form, der en optimal løsning sammen med valget gir en optimal løsning. Det er optimal delstruktur.

Svar
Du må også vise optimal delstruktur. Først da gir induksjon at alle de grådige valgene er trygge, og ikke bare det første.

Det kontinuerlige ryggsekkproblemet

I det binære ryggsekkproblemet er hver gjenstand med eller ikke med. I det kontinuerlige ryggsekkproblemet kan du ta med en andel mellom 0 og 1 av hver gjenstand, og får samme andel av verdien. Gjenstand ii har verdi viv_i og vekt wiw_i, og sekken har kapasitet WW.

Det grådige valget er gjenstanden med høyest kilopris vi/wiv_i / w_i. Ta med mest mulig av den, så mest mulig av den med nest høyest kilopris, og fortsett til sekken er full. Bare den siste gjenstanden blir delt. Grådighetsegenskapen vises med et utvekslingsargument. Tar en løsning mindre enn mulig av gjenstanden med høyest kilopris, har sekken tom plass eller noe med samme eller lavere kilopris. Fyll den tomme plassen, eller bytt ut like mange kilo av det andre, med den beste gjenstanden, og totalverdien øker eller står stille. Optimal delstruktur: kapasiteten som er igjen, må fylles optimalt med det som er igjen av gjenstandene. Sorteringen etter kilopris tar O(nlg⁡n)O(n \lg n), og resten er én gjennomgang.

For det binære problemet er det samme valget ikke trygt. Med W=50W = 50 og gjenstandene under har gjenstand 1 høyest kilopris.

Gjenstandwiw_iviv_ivi/wiv_i / w_i
110606
2201005
3301204

Velger du etter kilopris, tar du 1 og 2, til 160, og da får ikke 3 plass. Det beste er 2 og 3, til 220. I det kontinuerlige problemet tar den grådige algoritmen 1, 2 og to tredjedeler av 3, til 60+100+80=24060 + 100 + 80 = 240, og det er optimalt. Utvekslingsargumentet virker ikke i det binære problemet, fordi en gjenstand bare kan byttes ut hel. Bytter du ut 3 med 1 i den beste løsningen, står 20 kilo tomt, og verdien synker fra 220 til 160. For å avgjøre om en gjenstand skal være med, må du sammenligne den beste løsningen med gjenstanden og den beste uten, og den sammenligningen er dynamisk programmering.

Aktivitetsutvalg

Flere aktiviteter vil bruke det samme rommet, og rommet kan bare brukes av én om gangen. Aktivitet aia_i varer i det halvåpne intervallet [si,fi)[s_i, f_i), så to aktiviteter overlapper ikke når den ene slutter før eller idet den andre starter. Aktivitetsutvalg (activity selection) er å velge flest mulig aktiviteter der ingen to overlapper. Aktivitetene er sortert etter sluttid, f1≤f2≤⋯≤fnf_1 \le f_2 \le \cdots \le f_n.

Velger du en aktivitet aka_k, deler den resten i to delinstanser: aktivitetene som slutter ved sks_k eller før, og de som starter ved fkf_k eller senere. Den delingen gir en løsning med dynamisk programmering som prøver hver aka_k. Det grådige valget er a1a_1, aktiviteten som slutter først, og er det valget trygt, trenger du ikke prøve de andre. Aktiviteten a1a_1 lar rommet være ledig for flest mulig av de andre, og siden ingen aktivitet slutter før a1a_1 starter, er bare én delinstans igjen, aktivitetene som starter ved f1f_1 eller senere.

Grådighetsegenskapen vises med et utvekslingsargument. La AA være en optimal løsning, og la aja_j være aktiviteten i AA som slutter først. Bytt ut aja_j med a1a_1. De andre aktivitetene i AA overlapper ikke aja_j, og siden aja_j slutter først, starter de ved fjf_j eller senere. Siden f1≤fjf_1 \le f_j, slutter a1a_1 før eller idet de starter, så ingen av dem overlapper a1a_1. Løsningen er like stor og inneholder a1a_1. Optimal delstruktur: aktivitetene som starter ved f1f_1 eller senere, er en instans av samme problem og må velges optimalt.

Recursive-Activity-Selector får indeksen kk til den sist valgte aktiviteten, og leter fra k+1k + 1 etter den første aktiviteten ama_m som ikke overlapper aka_k. Aktivitetene er sortert, så fm≥fkf_m \ge f_k, og da overlapper ama_m ikke aka_k nøyaktig når sm≥fks_m \ge f_k. Aktiviteten ama_m tas med, og resten løses rekursivt fra den. Det første kallet har k=0k = 0, med en tenkt aktivitet a0a_0 der f0=0f_0 = 0. Greedy-Activity-Selector gjør det samme med en løkke.

aktivitetsutvalg.py
def recursive_activity_selector(s, f, k, n):  # plass 0 er a0, med f[0] = 0
  m = k + 1
  while m <= n and s[m] < f[k]:
      m = m + 1
  if m <= n:
      return [m] + recursive_activity_selector(s, f, m, n)
  return []

def greedy_activity_selector(s, f, n):
  A = [1]
  k = 1
  for m in range(2, n + 1):
      if s[m] >= f[k]:
          A.append(m)
          k = m
  return A
Greedy-Activity-Selector
0 / 0

Én rad per aktivitet, sortert etter sluttid, med aktiviteten tegnet fra sis_i til fif_i. Den stiplede linjen er f[k]f[k], sluttiden til den sist valgte aktiviteten. Grønne aktiviteter er valgt, og grå er hoppet over.

Hver aktivitet sjekkes én gang, så begge versjonene tar Θ(n)\Theta(n) tid når aktivitetene er sortert, og O(nlg⁡n)O(n \lg n) med sorteringen.

Eksamensoppgave Videokanaler med målgrupper
Kontinuasjonseksamen, august 2019, oppgave 9

Du skal velge ut videokanaler til forsiden på et videonettsted. Du har et sett med kanaler som hver har en estimert målgruppe, oppgitt som et aldersintervall (f.eks. fra 10 til 25 år). Du skal velge ut flest mulig kanaler, men får ikke velge noen som har overlappende målgrupper. Hvordan vil du gå frem? Forklar kort.

Vis løsning Skjul løsning

Hver kanal er en aktivitet med et aldersintervall i stedet for et tidsintervall, så dette er aktivitetsutvalg. Sorter kanalene etter øvre aldersgrense, velg den første, og gå gjennom resten i rekkefølge. Ta med en kanal når den ikke overlapper den sist valgte, og hopp over den ellers. Regnes begge endene av et aldersintervall med, overlapper en kanal for 25 til 40 år med en for 10 til 25 år, så testen er at den nedre grensen er større enn den øvre grensen til den sist valgte. Utvekslingsargumentet for aktivitetsutvalg viser at kanalen med lavest øvre grense alltid kan være med.

Svar
Som Greedy-Activity-Selector: sorter etter øvre aldersgrense, og velg hver kanal som ikke overlapper den sist valgte. O(nlg⁡n)O(n \lg n) med sorteringen.

Huffman-koder

En tekst skal lagres med færrest mulig bits. Hvert tegn cc i alfabetet CC får et binært kodeord, og den kodede teksten er kodeordene etter hverandre. Tegnet cc står c.freqc.\mathit{freq} ganger i teksten. Med like lange kodeord trengs ⌈lg⁡n⌉\lceil \lg n \rceil bits per tegn for nn tegn. Får de hyppige tegnene korte kodeord og de sjeldne lange, blir teksten kortere.

Prefiksfrie koder

I en prefiksfri kode er ingen kodeord et prefiks av et annet. Da er det første kodeordet i en kodet tekst entydig, og teksten kan dekodes ett kodeord om gangen fra starten. Koden tegnes som et binært kodetre med tegnene i løvnodene. Kodeordet til et tegn er stien fra rota til løvnoden, der 0 er venstre barn og 1 er høyre barn, så lengden av kodeordet er dybden dT(c)d_T(c). Antall bits i den kodede teksten er kostnaden til treet:

B(T)=∑c∈Cc.freq⋅dT(c)B(T) = \sum_{c \in C} c.\mathit{freq} \cdot d_T(c)

Et optimalt kodetre er fullt: hver indre node har to barn. Har en indre node bare ett barn, kan noden erstattes med barnet, og alle kodeordene under den blir én bit kortere. Et fullt binærtre med nn løvnoder har n−1n - 1 indre noder.

Dekodingen følger treet. Start i rota, les én bit om gangen, og gå til venstre barn for 0 og høyre barn for 1. I en løvnode skriver du ut tegnet og starter i rota igjen. Det tar konstant tid per bit.

Huffmans algoritme

Huffman bygger kodetreet nedenfra. Hvert tegn starter som et eget tre med én node, i en min-prioritetskø QQ på frekvens. I hver runde tas de to trærne med lavest frekvens, xx og yy, ut av køen. De blir barn av en ny node zz med frekvens x.freq+y.freqx.\mathit{freq} + y.\mathit{freq}, og zz settes inn i køen. Etter n−1n - 1 runder er ett tre igjen, og det er kodetreet.

Valget er grådig. Å slå sammen to trær legger én bit til kodeordet for hvert tegn i dem, så sammenslåingen øker B(T)B(T) med summen av frekvensene deres, og Huffman velger den billigste. Derfor er B(T)B(T) også summen av frekvensene i alle de indre nodene. Med to tegn er det én indre node, begge kodeordene har én bit, og B(T)B(T) er summen av de to frekvensene.

huffman.py
import heapq
from itertools import count

def huffman(C):  # C er en ordbok fra tegn til frekvens
  tie = count()  # løpenummer for like frekvenser, så trær sammenlignes aldri
  Q = [(freq, next(tie), c) for c, freq in C.items()]
  heapq.heapify(Q)
  for i in range(1, len(C)):
      fx, _, x = heapq.heappop(Q)
      fy, _, y = heapq.heappop(Q)
      z = (x, y)  # et tre er et tegn eller et par (venstre, høyre)
      heapq.heappush(Q, (fx + fy, next(tie), z))
  return Q[0][2]

def codes(T, prefix=""):
  if isinstance(T, str):
      return {T: prefix}
  return codes(T[0], prefix + "0") | codes(T[1], prefix + "1")

def decode(T, bits):
  text, x = "", T
  for b in bits:
      x = x[0] if b == "0" else x[1]
      if isinstance(x, str):
          text += x
          x = T
  return text
Huffman
0 / 0

Køen QQ tegnet som en skog, sortert etter frekvens med den laveste til venstre. Løvnodene er tegn med frekvens, og en indre node har summen av frekvensene under seg. De to trærne Extract-Min gir, er oransje, den nye noden zz er fylt, og i det ferdige treet står kodeordene under løvnodene.

Med en binær min-haug bygges køen i O(n)O(n) tid, og hver av de n−1n - 1 rundene gjør tre køoperasjoner på O(lg⁡n)O(\lg n). Til sammen er det O(nlg⁡n)O(n \lg n).

Hvorfor koden er optimal

Står et tegn ett nivå dypere, blir kodeordet én bit lengre, og den kodede teksten blir like mange bits lengre som tegnet har frekvens. De sjeldneste tegnene hører derfor hjemme nederst. Grådighetsegenskapen: la xx og yy være de to tegnene med lavest frekvens. Da finnes et optimalt kodetre der xx og yy er søsken på største dybde. Ta et optimalt tre TT, og la aa og bb være søsken på største dybde. Bytt plass på xx og aa, og kall det nye treet T1T_1. I B(T)B(T) står x.freq dT(x)+a.freq dT(a)x.\mathit{freq}\, d_T(x) + a.\mathit{freq}\, d_T(a), og i B(T1)B(T_1) står x.freq dT(a)+a.freq dT(x)x.\mathit{freq}\, d_T(a) + a.\mathit{freq}\, d_T(x). Alle andre ledd er like, så kostnaden synker med

B(T)−B(T1)=(a.freq−x.freq)⋅(dT(a)−dT(x))≥0B(T) - B(T_1) = (a.\mathit{freq} - x.\mathit{freq}) \allowbreak\cdot (d_T(a) - d_T(x)) \ge 0

fordi xx har lavest frekvens og aa står dypest. Byttet gjør ikke treet dyrere, og det samme gjelder for å bytte yy og bb. Treet er fortsatt optimalt, og xx og yy er søsken nederst, slik Huffman setter dem sammen.

Optimal delstruktur: erstatt xx og yy med ett nytt tegn zz med frekvens x.freq+y.freqx.\mathit{freq} + y.\mathit{freq}, og la T′T' være et kodetre for det nye alfabetet. Får zz barna xx og yy, blir kodeordene deres én bit lengre enn kodeordet til zz, og alle andre kodeord er like:

B(T)=B(T′)+x.freq+y.freqB(T) = B(T') + x.\mathit{freq} + y.\mathit{freq}

Tillegget avhenger ikke av T′T', så blant trærne der xx og yy er søsken, er TT optimalt når T′T' er det. Grådighetsegenskapen sier at et av disse trærne er optimalt blant alle, så TT er optimalt. Etter å ha slått sammen xx og yy løser Huffman nettopp det mindre problemet der zz er ett tegn.

Eksamensoppgave Hvilke trær er huffmantrær
Ordinær eksamen, desember 2024, oppgave 8

Du har oppgitt følgende frekvenser for tegnene a, b, c og d:

a.freq=8b.freq=4c.freq=2d.freq=4a.\mathit{freq} = 8 \allowbreak\qquad b.\mathit{freq} = 4 \allowbreak\qquad c.\mathit{freq} = 2 \allowbreak\qquad d.\mathit{freq} = 4

Med «huffmantre» menes et tre som kan konstrueres av Huffmans algoritme, hvis det er tilfeldig om en barnenode havner til venstre eller høyre. Det er minst ett korrekt huffmantre i figur 1, men det kan være flere.

Fem binære trær nummerert 1 til 5, med løvnodene a, b, c og d og kantene merket 0 og 1. Tre 1 har a og b nederst, c over dem og d øverst. Tre 2 har alle fire på dybde 2. Tre 3 har a øverst, b under og c og d nederst. Tre 4 har a øverst, d under og b og c nederst. Tre 5 har d øverst, a under og b og c nederst.
Figur 1. Figuren fra oppgavesettet.
Hvilke av trærne 1–5 i figur 1 er korrekte huffmantrær for disse tegnene, med disse frekvensene? Forklar kort. Flere svar er riktige. Kryss av alle som stemmer.

Sjekk deg selv

Hvilken grådig regel for aktivitetsutvalg gir alltid en optimal løsning?
Oppdatert 4. oktober 2026 Foreslå endring