CMD + K

Algoritmer og datastrukturerGrådige algoritmerBegreper & formler12
7 min lesing1 video

Grådige algoritmer

En grådig algoritme bygger en løsning steg for steg og tar det lokalt beste valget hver gang. Dette gir optimum bare når problemet har grådig-valg-egenskapen og optimal delstruktur. Aktivitetsutvelgelse og Huffman-koding viser når grådighet virker — og hvorfor.

Læringsmål
  • 01Forklare hva en grådig algoritme er, og avgjøre med et eksempel om en grådig regel gir et optimalt svar
  • 02Beskrive optimal delstruktur og grådig-valg-egenskapen, og bruke et utbyttingsargument til å bevise at et grådig valg er trygt
  • 03Kjøre aktivitetsutvelgelse på et eksempel ved å velge tidligst slutt-tid, og forklare hvorfor den regelen er optimal
  • 04Bygge et Huffman-tre nedenfra fra frekvenser, lese av de prefiksfrie kodeordene, og forklare hvorfor de to minst hyppige tegnene slås sammen først

Å ta det beste valget akkurat nå

Tenk deg at du står i kassakøen og skal gi tilbake 47 kroner i mynter, og du vil bruke færrest mulig. Du tar den største mynten som ikke overskrider beløpet — en 20-kroning — og spør deretter det samme om resten: 27 kroner. Igjen en 20-kroning, så en 5-kroning, så to 1-kroninger. Du vurderte aldri alle kombinasjoner; du tok bare det lokalt beste valget hver gang og lot resten følge. Det er kjernen i en grådig algoritme: bygg løsningen steg for steg, og ta i hvert steg det valget som ser best ut der og da, uten å angre.

Det forbløffende er at denne nærsynte strategien noen ganger gir et globalt optimalt svar. Men ikke alltid. Med et tenkt myntsystem på 1, 5 og 8 kroner gir grådighet 13 = 8 + 5 (to mynter), men for 16 tar grådighet 8 + 8 = 16 (to mynter) der det også er optimalt — mens for 10 tar grådighet 8 + 1 + 1 (tre mynter) når 5 + 5 (to mynter) er bedre. Grådighet feilet. Hele dette kapittelet handler om å forstå når den nærsynte strategien er trygg, og hvordan vi beviser det.

To egenskaper må være på plass

Et grådig argument hviler på to ben. Det første er optimal delstruktur: en optimal løsning på hele problemet må inneholde en optimal løsning på det som gjenstår etter et valg. ƒgrådig krever optimal delstruktur Hvis vi tar et grådig valg og deretter løser den gjenstående delinstansen optimalt, skal de to bitene til sammen utgjøre en optimal helhet. Mangler denne strukturen, er det meningsløst å bygge løsningen stykkevis.

Det andre benet er grådig-valg-egenskapen. Den sier at det lokalt beste valget faktisk kan inngå i en optimal løsning. ƒgrådig-valg-egenskapen Merk «en»: vi krever ikke at enhver optimal løsning inneholder det grådige valget, bare at minst én gjør det. Da kan vi trygt ta valget og aldri se oss tilbake. Forskjellen fra dynamisk programmering er nettopp dette: en DP-algoritme vurderer flere valg i hvert steg og lar tilbakekoblingen avgjøre, mens grådighet binder seg til ett valg umiddelbart. Det er raskere, men virker bare når grådig-valg-egenskapen holder.

Utbyttingsargumentet: slik beviser vi at valget er trygt

Hvordan vet vi at det grådige valget er trygt? Vi bruker et utbyttingsargument. Idéen er enkel og kraftig: ta en hvilken som helst optimal løsning. Hvis den allerede inneholder det grådige valget, er vi ferdige. Hvis ikke, viser vi at vi kan bytte ut ett element i den optimale løsningen med det grådige valget, uten å gjøre løsningen dårligere. Etter byttet er løsningen fortsatt optimal, og nå inneholder den det grådige valget. Dermed finnes det alltid en optimal løsning som starter grådig.

Dette mønsteret går igjen i alle grådige bevis. Du antar en optimal løsning, peker på det grådige valget den eventuelt mangler, og argumenterer for at en byttehandling ikke kan tape noe. Vi skal se det konkret nå.

Aktivitetsutvelgelse: tidligst slutt vinner

Du har ett auditorium og en bunke forelesninger som hver har en start- og en slutt-tid. To forelesninger er kompatible hvis de ikke overlapper. Du vil få plass til flest mulig. Dette er aktivitetsutvelgelse, og den grådige regelen er overraskende enkel: blant aktivitetene som fortsatt er kompatible, velg alltid den som blir ferdig først. ƒaktivitetsutvelgelse

0a1a2a3a4a5a8a1 [1,4] · t=1 · 3 enhetera1 [1,4]a2 [3,5] · t=3 · 2 enhetera2 [3,…a3 [0,6] · t=0 · 6 enhetera3 [0,6]a4 [5,7] · t=5 · 2 enhetera4 [5,…a5 [3,9] · t=3 · 6 enhetera5 [3,9]a8 [8,11] · t=8 · 3 enhetera8 [8,11]Grønn = valgt (tidligst slutt blant kompatible), grå = forkastet
FIGAktivitetsutvelgelse: velg tidligst slutt

Hvorfor tidligst slutt, og ikke for eksempel kortest varighet eller tidligst start? Fordi den aktiviteten som slutter først, etterlater mest tid til alt det andre. Den «spiser opp» minst mulig av tidslinja. Utbyttingsargumentet gjør dette presist: la være aktiviteten med tidligst slutt-tid, og la være en optimal løsning. Hvis ikke er i , ser vi på den aktiviteten i som slutter først — kall den . Siden slutter minst like tidlig som , kan vi bytte ut med i . Den nye løsningen har like mange aktiviteter og er fortsatt gyldig, fordi slutter tidligere og dermed ikke kan kollidere med resten. Altså finnes det en optimal løsning som inneholder — grådig-valg-egenskapen holder.

I figuren over sorterer vi etter slutt-tid og plukker (slutter ved 4). Da forkastes alt som overlapper . Neste kompatible med tidligst slutt er (start 5, slutt 7), og til sist (start 8). Resultatet er tre aktiviteter. Algoritmen i 0-indeksert stil:

AKTIVITETSUTVALG(start[], slutt[])     // antar sortert på slutt stigende
    valgt = [0]                         // ta aktiviteten som slutter først
    forrige = 0
    for i = 1 to n - 1
        if start[i] >= slutt[forrige]   // kompatibel med sist valgte
            valgt.append(i)
            forrige = i
    return valgt

Når aktivitetene først er sortert på slutt-tid, gjør løkka bare ett gjennomløp. Selve utvalget er ; sorteringen på forhånd koster og dominerer.

Huffman-koding: grådighet på bits

Det neste eksempelet er datakomprimering. Du skal lagre en tekst med færrest mulig bits. En fast kode gir hvert tegn like mange bits, men det er sløsing hvis noen tegn er mye vanligere enn andre. Bedre å gi hyppige tegn korte kodeord og sjeldne tegn lange. Det er nettopp det huffman-koding gjør.

Men variabel lengde skaper et problem: hvor slutter ett kodeord og hvor begynner det neste? Løsningen er en prefiksfri kode kode — ingen kodeord er en innledning til et annet. Er 0 et kodeord, kan ikke noe annet kodeord starte med 0. Da kan dekoderen lese bit for bit og vite nøyaktig når et tegn er ferdig, uten skilletegn. Prefiksfrie koder svarer til binærtrær der hvert tegn sitter i et blad og kodeordet er stien fra rota: venstre er 0, høyre er 1. Kostnaden vi minimerer er den vektede stilengden, summen av hver frekvens ganget med dybden i treet. ƒhuffman-koding

Det grådige grepet er å bygge treet nedenfra. Gjentatte ganger: ta de to symbolene med lavest frekvens og slå dem sammen til én ny node med summen av frekvensene. Gjenta til alt henger sammen i ett tre. De minst hyppige tegnene havner dermed dypest — lengst kodeord — akkurat som vi vil.

20A:812D:57B:34C:2E:2Bladnoder (uthevet) er tegn med frekvens; A=0, D=10, B=110, C=1110, E=1111
FIGHuffman-tre bygd fra frekvenser

Anta frekvensene A:8, B:3, C:2, D:5, E:2. Vi tar de to minste, C:2 og E:2, og slår dem til en node med vekt 4. Nå er de minste B:3 og denne 4-noden — slå dem til 7. Deretter D:5 og 7 til 12, og til slutt A:8 og 12 til rota 20. Les kodene fra rota: A er nærmest og får 0, mens C og E sitter dypest og får 1110 og 1111. Den vanligste bokstaven fikk ett bit, de sjeldneste fire — som ønsket.

Hvorfor er det grådige byttet trygt her? Fordi i et optimalt tre må de to minst hyppige tegnene sitte på den dypeste nivået, som søsken. Hvis et hyppigere tegn satt dypere enn et sjeldnere, kunne vi byttet dem og redusert den vektede stilengden — et utbyttingsargument igjen. Derfor taper vi aldri på å slå sammen de to minste først. Med en prioritetskø (binærhaug) over frekvensene koster hver sammenslåing , og vi gjør av dem, så hele byggingen er .

Når grådighet ikke holder

Det er fristende å tro at grådighet alltid virker fordi den er så enkel, men grådig vs. dynamisk programmering er et viktig skille. Begge bygger på optimal delstruktur. Forskjellen er at grådighet binder seg til ett valg uten å vurdere konsekvensene nedover, mens dynamisk programmering holder flere muligheter åpne og lar dem konkurrere. Myntveksling med uvennlige myntverdier, og det såkalte 0/1-ryggsekkproblemet, er klassiske eksempler der grådighet gir feil svar mens DP gir riktig. Når du møter et nytt optimaliseringsproblem, er det ikke nok at en grådig regel ser plausibel ut — du må kunne bevise grådig-valg-egenskapen med et utbyttingsargument. Klarer du ikke det, er det ofte fordi den ikke holder, og da må du gå til dynamisk programmering.

Lærdommen fra dette kapittelet er todelt. Grådighet er et skarpt verktøy: når det passer, gir det enkle og raske algoritmer, slik vi så for aktivitetsutvelgelse og Huffman. Men verktøyet er smalt. De to egenskapene — optimal delstruktur og grådig-valg — er ikke pynt; de er beviskravene som skiller en korrekt grådig algoritme fra en som tilfeldigvis ser riktig ut på små eksempler.

Mine notater

Skriv egne notater for dette kapittelet. De lagres automatisk og dukker opp i «Mine notater»-oversikten paa fag-siden.

Laster…