All good things. Fjortende forelesning

Like dokumenter
All good things. Fjortende forelesning

Algdat-ninja på 60 minutter: Et galskapsprosjekt. Magnus Lie Hetland

Dijkstras algoritme. Her finnes det også (minst) en riktig rekkefølge for Relax, men den må vi oppdage litt etter hvert.

Dijkstras algoritme. Her finnes det også (minst) en riktig rekkefølge for Relax, men den må vi oppdage litt etter hvert.

Algdat Redux. Fjortende forelesning. Repetisjon av utvalgte emner.

Alle mot alle. Åttende forelesning. (eller eller Bellman-Ford, eller BFS, alt ettersom) fra alle noder.

Fra A til B. Syvende forelesning

Lineær sortering. Radix sort

Studentnummer: Side 1 av 1. Løsningsforslag, Eksamen i TDT4120 Algoritmer og datastrukturer August 2005

Avsluttende eksamen i TDT4120 Algoritmer og datastrukturer

Go with the. Niende forelesning. Mye matematikk i boka her ikke så komplisert, men mye å holde styr på.

Go with the. Niende forelesning. Mye matematikk i boka her ikke så komplisert, men mye å holde styr på.

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Go with the. Niende forelesning. Mye matematikk i boka her ikke så komplisert, men mye å holde styr på.

O, what a tangled. Fjerde forelesning. Robot-eksemplet som ikke ble gjennomgått sist blir frivillig selvstudium (ut fra foilene :-)

Teoriøving 7 + litt om Ford-Fulkerson. Magnus Lie Hetland

Eksamen i tdt4120 Algoritmer og datastrukturer

ALGORITMER OG DATASTRUKTURER

O(V 2 ) bwfs(v, i=1) λ[v] = i for each neighbor u of v if 0 < λ[u] < i. bwfs(u, i+1) if λ[u] = 0

Algdat Eksamensforelesning. Nils Barlaug

ALGORITMER OG DATASTRUKTURER

Løsningsforslag for eksamen i fag SIF8010 Algoritmer og datastrukturer Lørdag 9. august 2003, kl

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

ALGORITMER OG DATASTRUKTURER

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Eksamen i fag SIF8010 Algoritmer og datastrukturer Lørdag 9. august 2003, kl

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Notater til INF2220 Eksamen

Løsningsforslag for eksamen i fag SIF8010 Algoritmer og Datastrukturer Tirsdag 18. Desember 2000, kl

Ekstra ark kan legges ved om nødvendig, men det er meningen at svarene skal få plass i rutene på oppgavearkene. Lange svar teller ikke positivt.

ALGORITMER OG DATASTRUKTURER

Kontinuasjonseksamen i fag SIF8010 Algoritmer og Datastrukturer Torsdag 9. August 2001, kl

LØSNINGSFORSLAG, EKSAMEN I ALGORITMER OG DATASTRUKTURER (IT1105)

Avsluttende eksamen i TDT4120 Algoritmer og datastrukturer

Pensum: fra boken (H-03)+ forelesninger

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

INF Algoritmer og datastrukturer

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Pensum: fra boken (H-03)+ forelesninger

IN Algoritmer og datastrukturer

Live life and be merry

Avsluttende eksamen i TDT4120 Algoritmer og datastrukturer

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Algdat Oppsummering, eksamen-ting. Jim Frode Hoff

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

n/b log b n = (lg n) a log b n = n log b a

Minimum Spenntrær - Kruskal & Prim

Innhold. Innledning 1

Øvingsforelesning Korteste vei: Alle til alle

deeegimnoorrrsstt Sjette forelesning

Øvingsforelesning 4. Topologisk sortering, Strongly Connected Components og Minimale spenntrær. Magnus Botnan

Løsningsforslag for eksamen i fag TDT4120 Algoritmer og datastrukturer Tirsdag 9. desember 2003, kl

GRAFER. Korteste vei i en vektet graf uten negative kanter. Korteste vei, en-til-alle, for: Minimale spenntrær

Løsningsforslag til eksamen i fag SIF8010 Algoritmer og Datastrukturer Tirsdag 14. Desember 1999, kl

INF Algoritmer og datastrukturer

Avsluttende eksamen i TDT4120 Algoritmer og datastrukturer

Grunnleggende Grafteori

Dijkstras algoritme Spørsmål

INF Algoritmer og datastrukturer

Maks Flyt og NPkompletthet

INF Algoritmer og datastrukturer

Algdat - Øvingsforelesning. Maks flyt

SIF8010 ALGORITMER OG DATASTRUKTURER

Søkeproblemet. Gitt en datastruktur med n elementer: Finnes et bestemt element (eller en bestemt verdi) x lagret i datastrukturen eller ikke?

Alg. Dat. Øvingsforelesning 3. Grafer, BFS, DFS og hashing. Børge Rødsjø

Algdat - øvingsforelesning

IN Algoritmer og datastrukturer

deeegimnoorrrsstt Sjette forelesning

Avanserte flytalgoritmer

O, what a tangled. Fjerde forelesning. O, what a tangled web we weave / When first we practice to deceive! Sir Walter Scott, *Marmion*

INF Algoritmer og datastrukturer

Alg. Dat. Øvingsforelesning 3. Grafer, BFS, DFS og hashing

INF Algoritmer og datastrukturer

INF2220: Time 12 - Sortering

Vi skal se på grafalgoritmer for:

Eksamen i fag SIF8010 Algoritmer og Datastrukturer Tirsdag 18. Desember 2000, kl

Avsluttende eksamen i TDT4120 Algoritmer og datastrukturer

INF1020 Algoritmer og datastrukturer GRAFER

Pensum: 3. utg av Cormen et al. Øvingstime: I morgen, 14:15

ALGORITMER OG DATASTRUKTURER

IN Algoritmer og datastrukturer

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Agenda. 1 Sortering, heap og select. 2 Binære trær. 3 Grafer, BFS og DFS. 4 Spenntrær og Korteste vei. 5 Maks flyt. 6 Dynamisk programmering

Datastrukturer for rask søking

Øvingsforelesning 3: Splitt og hersk. Daniel Solberg

Eksamen i fag SIF8010 Algoritmer og Datastrukturer Tirsdag 14. Desember 1999, kl

Korteste vei i en vektet graf uten negative kanter

Spenntrær, oppsummert: Kruskal: Traverserer ikke. Plukker kanter i hytt og vær Prim: Legger alltid til den noden som er nærmest treet

Øvingsforelesning 12 Maks flyt

GRAFER. Noen grafdefinisjoner. Korteste vei i en uvektet graf V 2 V 1 V 5 V 3 V 4 V 6

En litt annen måte å forklare traversering på. Traversering

INF Algoritmer og datastrukturer

INF Algoritmer og datastrukturer

Avsluttende eksamen i IT1105/TDT4120 Algoritmer og datastrukturer

INF2220: Forelesning 2

45011 Algoritmer og datastrukturer Løsningsforslag eksamen 13. januar 1992

INF Algoritmer og datastrukturer

INF Algoritmer og datastrukturer

Transkript:

All good things Fjortende forelesning 1

Reduksjons- Eksempler 2

Clique til Independent Set 3

Partition til Bin Packing 4

Partition til Subset Sum 5

CNF-SAT til Dir. Ham. Cycle 6

Dir. Ham. Cycle til Ham. Cycle 7

Sjekkliste Sjekklisten er basert på kap. 10 i «The Algorithm Design Manual» av Steven S. Skiena. 8

A. Forstår jeg virkelig problemet? B. Kan jeg finne en enkel algoritme? C. Kan jeg bruke en pensum-algoritme? D. Kan jeg løse spesialtilfeller? E. Hvilke designmetoder er mest relevante? 9

A. Forstår jeg virkelig problemet? B. Kan jeg finne en enkel algoritme? C. Kan jeg bruke en pensum-algoritme? D. Kan jeg løse spesialtilfeller? E. Hvilke designmetoder er mest relevante? 10

Hva består input av, helt presist? Hva er ønsket output, helt presist? Kan jeg konstruere et input-eksempel som jeg kan løse for hånd? Hva skjer da? Hva slags strukturer har jeg å jobbe med? Sekvenser, strenger, grafer, tall, mengder? 11

A. Forstår jeg virkelig problemet? B. Kan jeg finne en enkel algoritme? C. Kan jeg bruke en pensum-algoritme? D. Kan jeg løse spesialtilfeller? E. Hvilke designmetoder er mest relevante? 12

Kan jeg finne en korrekt brute force-løsning? Hvordan vet jeg at den er korrekt? Kan jeg bruke en enkel regel gjentatte ganger? Alltid velg den største/minste? Når fungerer det, og når fungerer det ikke? 13

A. Forstår jeg virkelig problemet? B. Kan jeg finne en enkel algoritme? C. Kan jeg bruke en pensum-algoritme? D. Kan jeg løse spesialtilfeller? E. Hvilke designmetoder er mest relevante? 14

Finnes det en algoritme som passer helt? Finnes det noen som nesten passer? Hva slags reduksjoner kan jeg få til? Jobb systematisk med de algoritmene du kan! 15

A. Forstår jeg virkelig problemet? B. Kan jeg finne en enkel algoritme? C. Kan jeg bruke en pensum-algoritme? D. Kan jeg løse spesialtilfeller? E. Hvilke designmetoder er mest relevante? 16

Hva om jeg ignorerer deler av problemet? Hva om jeg setter noen parametre til trivielle verdier som 0 eller 1? Kan jeg forenkle problemet til noe jeg kan løse effektivt? Hvorfor kan ikke denne spesial-løsningen generaliseres? Er problemet et spesialtilfelle av noe kjent? 17

A. Forstår jeg virkelig problemet? B. Kan jeg finne en enkel algoritme? C. Kan jeg bruke en pensum-algoritme? D. Kan jeg løse spesialtilfeller? E. Hvilke designmetoder er mest relevante? 18

Hjelper det å sortere? Kan jeg dele i to, kanskje med binærsøk? Store/små, Høyre/ venstre? Splitt-og-hersk? Kan jeg finne en rekkefølge på elementer/delproblemer? Kan jeg bruke det til DP? Noe som gjøres ofte (f.eks. søk)? Kan jeg bruke en datastruktur til speed-up? Søketre/hashtabell/heap? Kan jeg formuelere problemet som et lineært program? Ligner problemet på et NPC-/ NPH-problem? Kan jeg redusere fra et slikt problem? 19

A. Forstår jeg virkelig problemet? B. Kan jeg finne en enkel algoritme? C. Kan jeg bruke en pensum-algoritme? D. Kan jeg løse spesialtilfeller? E. Hvilke designmetoder er mest relevante? F. Ikke få panikk! 20

Reservestoff fra forrige gang 21

Eksempel på grådighet: Velg det som er optimalt sett helt med lokale øyne. Det viktigste er da å vise at det blir korrekt (med induksjon og/eller bevis ved selvmotsigelse). Minimale spenntrær 22

Spenntrær Har V 1 kanter Har ingen sykler Er ikke nødvendigvis unike 23

Vi bygger oss et sett med kanter. Begynner med en tom mengde, og legger til én og én kant. Invariant: Foreløpig løsning er et subsett av et MST. Trenger ikke være sammenhengende. Når vi har V-1 kanter *må* det jo være riktig. 24

«Trygg» betyr bare at vi ikke bryter invarianten. Så A er et ekte subsett av et MST helt til det faktisk *er* et MST. 1. A er en tom mengde Hvordan finner vi trygge kanter? 2. Så lenge A ikke er et spenntre: a) Finn en kant som er trygg for A b)legg kanten til i A Induksjon 25

Viktig! Anta at A ikke har noen kanter over «snittet» på figuren. Den letteste kanten er da trygg. (Vi kan ha flere.) Vises lett ved selvmotsigelse. Hvorfor kan det bli galt hvis A allerede krysser snittet? 26

A er en skog Hver trygg kant slår sammen to trær Vi trenger V 1 iterasjoner 27

Trivia: Union-find-strukturen er *supereffektiv*. Den er et eksempel på en av de få kjøretidene i pensum som er raskere enn logaritmisk, men likevel (i teorien) langsommere enn konstant. ( I teorien, fordi det vil være omtrent fysisk umulig for den å komme over 4 ) Se etter Inverse Ackermann i boka eller på nett :-) «I hytt og vær» Se på dekomponering/ reduksjon/rekursjon/ induksjon som perspektiver her Går igjennom kantene i sortert rekkefølge (etter vekt), og hopper over ulovlige kanter (de som gir sykler). Liten ekstra vanskelighet: Hvordan avgjør vi om en kant skaper en sykel? Vi må ha en lur datastruktur som tar vare på trærne i skogen så langt. Kruskals algoritme Union-find: Beskrevet mer i detalj i læreboka. Hovedprinsipp: Alle trær har en peker til sitt «super-tre»/union. 28

Finn MST Sorter kanter Bruk lovlige O(E lg V) Kruskals algoritme 29

Minner om DFS/BFS, men har en annen type «kø»/ valgmekanisme: «Jevnt og fint» Ta alltid noden som det er billigst å koble til treet du har så langt. Her har vi altså hele tiden bare ett tre i A. Traversering Prims algoritme 30

Her er snittet «rundt» treet. 31

Finn MST Traversering Neste: Kortest Raskest i praksis O(V lg V + E) Akkurat det er ikke pensum, men jeg har sett studier som tyder på det :-) (Med vanlig binær heap.) Prims algoritme 32

Variabelskifte 33

Flytter oss til «en enklere verden» Skift funksjon og variabel; går «opp i opp» T(n) = S(m) F.eks. m = lg n, S(m) = T(2 m ) 34

T (n) = 2T ( n) + lg n [m = lg n] T (2 m ) = 2T (2 m/2 ) + m [S(m) = T (2 m )] S(m) = 2S(m/2) + m = O(m lg m) T (n) = O(lg n lg lg n) 35

T (n) = 2T ( n) + lg n [m = lg n] T (2 m ) = 2T (2 m/2 ) + m [S(m) = T (2 m )] S(m) = 2S(m/2) + m = O(m lg m) T (n) = O(lg n lg lg n) Legg merke til at alle venstresidene (og høyresidene, for den saks skyld) er like. 36

Her finnes det også (minst) en riktig rekkefølge for Relax, men den må vi oppdage litt etter hvert. Tenk vann som sprer seg i rør: Vi behandler krysningspunktene i den rekkefølgen de fylles. Det må gi oss riktige svar. Altså litt som DAG-SP, men ikke topologisk sortert vi ordner (på magisk vis) etter faktisk avstand. (Hvis vi bare tar med kanter i de korteste stiene så er grafen topologisk sortert ) Dijkstras algoritme 37

I stedet for topologisk rekkefølge: Etter stigende avstand fra s Som BFS men prioritetskø (med d[v]) i stedet for FIFO-kø Takler ikke negative kanter! 38

For spinkle grafer: Bruker binær haug som prioritetskø Må kunne endre nøkler (dvs. d[v]) underveis Må ha kobling mellom mellom noder og haug-innslag Kan evt. legge inn noder flere ganger i stedet 39

Initialiser grafen Så lenge det finnes uferdige noder: Velg u med lavest d[u] For alle kanter (u, v): Relax(u, v) 40

Hvis nodene besøkes etter stigende avstand: Kantene i korteste veier bli relaxet i riktig rekkefølge Topologisk sortering av kantene som «teller» Men Hvordan vet vi at lavest d[v] faktisk er nærmest? 41

Bokas variant av korrekthetsbeviset. Merk: Det er snakk om nok et bevis ved kontradiksjon. Vi antar at d[u] ikke er korrekt i det en node u legges til, og viser at vi får en selvmotsigelse. Den neste er kanskje forelesningens vanskeligste slide. 42

Anta at u er den første som ikke har riktig d-verdi når den legges til. Da må x ha hatt riktig d-verdi, og siden (x, y) er relaxet, så må y også ha riktig d-verdi i det u legges til. d[y] = (s, y) (s, u) d[u] Vi vet her at y forekommer før u på den korteste stien (Merk at x og y godt kan sammenfalle med s her.) Husk også at d er et overestimat av δ. Dette virker bare hvis vi ikke har negative kanter; ellers kan vi ta «snarveier» og vi vet ikke lenger hvilken node som er nærmest. 43 d[u] Men Siden u ble valgt før y så vet vi at d[u] d[y]. d[y] d[y] = (s, y) = (s, u) = d[u] Sandwitch!

Underliggende antagelse: u er den første med galt men minimalt avstandsestimat (blant de hittil ubesøkte). Vi har y med korrekt d d[y] = δ(s, y) Dette viste vi ved å se at det må finnes en y med en forgjenger x rett innenfor og som altså har fått kjørt relax på sin forgjengerkant i den korteste stien, og dermed har korrekt d. u ligger senere på korteste vei δ(s, y) δ(s, u) d[u] Det er denne ulikheten som ikke gjelder hvis vi har negative kanter. Da kan en node tidligere på den korteste stien likevel ligge lenger unna startnoden. men besøkes likevel (feilaktig) d[u] d[y] 44 Vi får altså både d[y] d[u] og d[u] d[y], dvs. d[y] = d[u], med de faktiske avstandene klemt imellom. Med andre ord: Begge avstandene er korrekte likevel og vi har en selvmotsigelse.

(Min egen variant av korrekthetsbeviset litt enklere (IMO) enn det i boka.) Hypotetisk: Vi ordner noder etter faktisk avstand. Vi har positive kanter, så bakoverkantene vil være irrelevante (selv om vi jo ikke vet hvilke de er). Med andre ord har vi en (skjult, ukjent) DAG. Vi ønsker å besøke nodene i avstandsrekkefølge *uten å kjenne til* denne rekkefølgen (eller DAG-en). Induksjon to the rescue Dette beviset antar unike/ forskjellige vekter! 0 3 7??? Vi har besøkt de k 1 første nodene, og relaxet kantene ut. Disse nodene har nå riktig avstandsestimat og det har også den neste i rekka (selv om vi ikke vet hvilken det er ennå). Det er akkurat som i DAG-shortestpath. 45 Betrakt den neste i sortert rekkefølge. Den har korrekt estimat. Alle de gjenværende har større avstand, og minst like store estimater. Dermed må den med lavest estimat være den neste, og vi har løst problemet for k.

Kjøretid avhengig av prioritetskøen. Hver Relax (det vil si, hver kant) kan måtte bruke Decrease-Key som koster O(lg V). Hver Extract-Min (dvs. for hver node) koster også O(lg V). Vi har altså: O(E lg V) + O(V lg V). Hvis alle kan nås fra starten vil E dominere, og vi får O((E+V) lg V) = O(E lg V). Kjøretid: O(E lg V) * * Hvis alle noder kan nås fra s 46

Korteste vei Én til alle Ingen neg. kanter Besøk nærmeste Relax til alle naboer Effektiv Spes. på spinkle G Kravstor O(E lg V) Dette er jo en gjenganger. Jo sterkere krav vi stiller, jo mer effektive er algoritmene. Dijkstras algoritme 47

Floyd-Warshall 48

k er muligens med Delproblem med parameter k: Kan kun gå via de k første nodene. En slik sti kan deles i to mindre delproblemer (med lavere parameter). Alle mellomliggende noder i {1 k 1} 49

! w =, ( ) " ( ) ( ) = ( ) #. +, ( ) ( ) = w " ( ) # = 50

Kubisk kjøretid, naturligvis. (, ) ( ) ( ) ( ) ( ( ), ( ) + ( ) ) Transitiv closure: Egentlig akkurat det samme men sjekker om det *finnes* en sti, i stedet for å beregne hvor *lang* den er. (Bruker logiske i stedet for aritmetiske operasjoner.) 51

k = 0 0 8 5 3 0 2 0 52

k = 1 0 8 5 3 0 8 2 0 53 0 8 5 3 0 2 0

k = 2 0 8 5 3 0 8 5 2 0 54 0 8 5 3 0 8 2 0

k = 3 0 7 5 3 0 8 5 2 0 55 0 8 5 3 0 8 5 2 0

Flyt 56

Et veldig enkelt eksempel på flyt. Hvor mange «uavhengige» stier har vi fra venstre til høyre? Eller: Hvor mange «enheter» kan vi pumpe igjennom, hvis hver kant takler én enhet? 57

58 Som for matching, prøver vi oss. Vi må begynne til venstre (i kildenoden) og ende til høyre (i sluknoden). Her har vi en «augmenting path» med bare ledige kanter.

59 Her fant vi jammen enda en forøkende sti med bare ledige kanter og nå er det fullt.

Matcheproblemet kan også løses så direkte hvis vi har flalks. Men Hva om vi har litt mindre flaks? Da må vi gå i en slags «sikk-sakk» her også. 60

Først en forøkende sti med bare ledige kanter. Men hva gjør vi nå? 61

Vi kan gå *baklengs* over opptatte kanter og oppheve dem akkurat som i matcheproblemet. En slik «bakover-oppheving» tilsvarer en slags krysskobling: Vi lager en ny start og en ny slutt, og spleiser dem sammen med en eksisterende sti (intuitivt). Logikken er egentlig akkurat som for matching. Vi kan fjerne (oppheve/gå baklengs gjennom) en innkommende «full» kant, men da må flyten til den kanten sendes et annet sted nemlig i fremover i en annen kant. Matematisk er det ekvivalent å *øke* flyten *fremover* eller å *redusere* flyten *bakover*. I en flytforøkende sti må hver kant gjøre én av delene. Merk at vi kan gå flere bakoverskritt eller fremoverskritt i rekkefølge (dvs. ikke strengt annenhver, som i «sikk-sakk». 62

63 Svaret blir det samme. Antallet enheter vi får igjennom tilsvarer antall opptatte kanter ut fra kilden (eller inn til sluket).

Det er to mulige tolkninger av dette: Vi «opphever» 5 av de 7 som går mot venstre ved å sende 5 til mot høyre. De 7 mot venstre tilsvarer 7 mot høyre, som kan økes opp mot 0. 4/9 7/8 Vi kan øke med 5 fra venstre til høyre Hva foregår «egentlig»? Ved å øke flyten inn i midtnoden fra venstre og å redusere flyten inn i noden fra høyre med samme mengde har noden samme flyt-sum, så vi ødelegger ingenting. 64

Eksempel Bruker ikke BFS her w 0/2 x 0/3 0/3 s 0/2 0/1 0/3 0/1 0/2 0/3 0/2 t y 0/3 z 65

Eksempel w 0/2 x 0/3 0/3 s 0/2 0/1 0/3 0/1 0/2 0/3 0/2 t y 0/3 z Alle kanter i stien går fremover, og minimumskapasiteten er 2. 66

Eksempel Her er flyten økt med den maksimale ledige kapasiteten. w 2/2 x 0/3 0/3 s 2/2 0/1 2/3 0/1 2/2 0/3 2/2 t y 0/3 z 67

Eksempel Ny sti denne gangen med noen baklengs-kanter. I disse ser vi ikke etter ledig kapasitet, men flyt som kan kanselleres. w 2/2 x 0/3 0/3 s 2/2 0/1 2/3 0/1 2/2 0/3 2/2 t y 0/3 z Blant forover-kantene er minste ledige kapasitet 3. Blant bakover-kantene er minste flyt 2. Minimum blir altså 2. 68

Eksempel w 2/2 x 2/3 2/3 s 2/2 0/1 0/3 0/1 0/2 0/3 2/2 t y 2/3 z Igjen er flyten langs stien økt med det maksimale mulige (2). Flyt i fremoverkanter økes flyt i bakoverkanter reduseres. Totalt økes flyten fra s til t uten at vi bryter noen regler. 69

Eksempel w 2/2 x 2/3 2/3 s 2/2 0/1 0/3 0/1 0/2 0/3 2/2 t y 2/3 z Ikke mulig å finne noen flere flytforøkende stier, så vi er ferdige. 70

Eksempel w 2/2 x 2/3 2/3 s 2/2 0/1 0/3 0/1 0/2 0/3 2/2 t y 2/3 z Her er et minimalt snitt, med kapasitet lik maksflyten (4). Det er ikke mulig å presse mer flyt igjennom dette snittet. 71

Antagelig ikke forelest i dag, men jeg tar det med her, så dere kan lese på det :-) Algdat-ninja på 60 minutter: Et galskapsprosjekt Magnus Lie Hetland 15. november, 2002 72

Advarsel: Tettpakkede og overfladiske foiler forut! 73 1

Algtdat i 6 punkter 1. Grunnbegreper og basisverktøy 2. Rekursjon og induksjon 3. Graftraversering og avhengighetsgrafer 4. Splitt og hersk 5. Dynamisk programmering og grådighet 6. Iterative algoritmer 74 2

1. Grunnbegreper og basisverktøy Kjøretid, Algoritmer og Problemer Problemklasser: P, NP, og NPC Prioritetskøer Oppslagstabeller 75 3

Kjøretid, Algoritmer og Problemer En algoritme løser et problem. Kjøretiden til algoritmen er avhengig av problem-instansen. Forskjellige instans-typer kan gi forskjellige kjøretider (best-case, worst-case, average-case). Gitt en instans-type er kjøretiden kun avhengig av problemstørrelse. Vi skriver da T(n). Vi kan beskrive T(n) med en grov øvre (O) eller nedre ( ) grense. For eksempel kan vi skrive T(n) O(n 2 ) hvis n 2 er en øvre grense for T(n). uttrykker både øvre og nedre grense samtidig. Merk: Ingen direkte sammenheng mellom f.eks. worst-case og O. Likevel: Hvis vi beskriver en vilkårlig problem-instans må den øvre grensen gjelde også for worst-case. (Tilsvarende for og.) Spesialtilfelle: Når vi snakker om Quicksort er det ofte implisitt at vi snakker om average-case (O(n log n)) og ikke worst-case (O(n 2 )). 76 4

Problemklasser: P, NP, og NPC P, NP og NPC beskriver problemklasser, ikke algoritmer. P NP og NPC NP. P-problemer kan løses i polynomisk tid (P for Polynomial). Løsningen til et NP-problem kan testes i polynomisk tid (N for Nondeterministic). En eventuell løsning på et NPC-problem kan brukes til å løse et hvilket som helst NP-problem med et polynomisk kjøretidstillegg (C for Complete). Altså: Hvis NPC P så P = NP. Med andre ord, hvis et NPC-problem kan løses i polynomisk tid, så kan alle NP-problemer løses i polynomisk tid. Er P = NP? Vi vet ikke, men det virker svært usannsynlig. 77 5

Prioritetskøer Man kan effektivt finne/fjerne minste (evt. største) element i en prioritetskø. En implementasjon er hauger (heaper). Den lar deg sette inn elementer og fjerne minste element i O(log n) tid. Haug-egenskapen: Foreldre er alltid mindre (evt. større) enn sine barn. Dette er mindre strengt enn enn søketre-egenskaper, og dermed billigere å opprettholde. Enkle søketrær gir også samme kjøretid for average-case, men kan lett bli ubalanserte, og har O(n) som worst-case-kjøretid for extract-min. Heaper er automatisk balanserte. 78 6

Oppslagstabeller Oppslagstabeller (dictionaries, mappings) lar deg knytte en vilkårlig nøkkel til en vilkårlig verdi, som f.eks. i en telefonkatalog. En implementasjon er hashtabeller. En hashtabell baserer seg på en hashfunksjon, som fra en nøkkel k beregner en indeks h(k) i en vanlig tabell (array). Det er ikke plass til alle mulige nøkler i denne tabellen, så det kan oppstå kollisjoner (nøkler med samme hashverdi). Hashfunksjonen er konstruert for å unngå dette i størst mulig grad, men hash-algoritmen må likevel kunne takle kollisjoner. Mulig løsning: Ved kollisjon, lagre alle nøkler med samme hash-verdi i en liste på den angitte posisjonen i hash-tabellen. Hashfunksjoner beregner ofte tilsynelatende tilfeldige hashverdier, men for f.eks. heltallsnøkler kan det fungere med h(k) = k mod n, der n er antall plasser i tabellen. 79 7

2. Rekursjon og induksjon Enkel induksjon Enkel rekursjon Rekurrensligninger og iterasjonsmetoden 80 8

Enkel induksjon Vil bevise p i for alle i N. 1. Bevis p 1. 2. Bevis p i 1 p i for alle i > 1. Og det var det... For en vilkårlig i har vi da: p 1 p 2 p i 2 p i 1 p i 81 9

Enkel rekursjon Rekursjon: En funksjon definert (direkte eller indirekte) ut fra seg selv. Eksempel: En funksjon som beregner i=1 n i. f (1) = 1 f (n) = f (n 1) + n Utsagnet p n blir her at f (n) er korrekt. 1. Vi ser at p 1 holder (summen av 1 er 1). 2. Hvis p n 1 holder så er f (n 1) = i=1 n 1. Vi har da at f (n) = f (n 1) + n = i=1 n, dvs. at p n holder. Dermed er f (n) korrekt for alle n. Rekursjon er (mer eller mindre) omvendt induksjon. 82 10

Rekurrensligninger og iterasjonsmetoden Kjøretider kan ofte være definert rekursivt, fordi algoritmen vi beskriver er rekursiv. F.eks. (binærsøk): T(1) = (1) T(n) = T(n/2) + (1) Enkleste løsningsmetode (ikke i læreboka): Iterativ substitusjon. Kjør de rekursive kallene selv, og let etter et mønster: T(n) = T(n/2) + (1) = (T((n/2)/2) + (1)) + (1) = T(n/4) + 2 (1) =. T(n/8) + 3 (1) = T(n/2 i ) + (i) Variabelen i kan velges fritt. Vi velger den som lar oss sette inn T(1), dvs. rekursjonsdybden. Vi setter n/2 i = 1 og får i = log 2 n. Med andre ord, T(n) = 1 + log 2 n (1) = (log n). 83 11

3. Graftraversering og avhengighetsgrafer Grafer: Eksplisitte og implisitte Traversering: DFS, BFS og venner Topologisk sortering Eksempel: Prims algoritme Avhengighetsgrafer: Dyp innsikt 84 12

Grafer: Eksplisitte og implisitte En (urettet) graf er et nettverk som består av noder, koblet sammen av kanter. Kantene i en rettet graf har retning. I en vektet graf har hver kant en vekt. I et flytnettverk har hver kant en kapasitet. En rettet asyklisk graf (DAG, Directed Acyclic Graph) er en rettet graf uten rettede sykler (man kan ikke følge kantenes retning i en sykel). Et tre er en rettet eller urettet graf uten sykler (man kan ikke følge kantene i en sykel uansett retning). En sti er en serie med noder der hvert par med etterfølgende noder er bundet sammen av en kant. I en rettet sti går alle kantene i samme retning ( fremover ). I en sammenhengende graf er alle noder forbundet med en (urettet) sti. Grafer kan være eksplisitte (representert ved nabomatriser, nabolister e.l.) eller implisitte (del av problemstrukturen, men ikke representert som datastruktur). 85 13

Traversering: DFS, BFS og venner Når vi traverserer en graf har vi kun lov til å gå langs kantene (i riktig retning). Ved full traversering besøker vi alle nodene (potensielt flere ganger). Ved søk besøker vi bare de vi trenger for å finne noden vi leter etter. Vi har en huskeliste over noder vi skal besøke. Til å begynne med inneholder denne bare startnoden. Så lenge denne huskelisten ikke er tom så krysser vi av en node og besøker denne. For hver node vi besøker oppdager vi kanskje noen nye noder (naboer). Vi legger disse til i huskelisten vår og fortsetter. Hvis huskelisten er en kø (FIFO) får vi bredde-først-søk (BFS). Hvis huskelisten er en stakk (LIFO) får vi dybde-først-søk (DFS). DFS kan enkelt implementeres med rekursjon. Hvis huskelisten er en mer komplisert prioritetskø får vi straks interessante algoritmer som Prims og Dijkstras. 86 14

Topologisk sortering Kantene i en rettet graf kan ses på som avhengigheter. Vi vil gjerne kunne ordne nodene i en rekkefølge slik at hver node kun er avhengig av de tidligere nodene. Nodene er da topologisk sortert. (Ofte mange mulige rekkefølger.) Dette er kun mulig hvis grafen er en DAG. Ved å skrive ut nodene etter den rekkefølgen et dybde-først-søk forlater dem i (såkalt post-order traversal) får vi en topologisk sortering, siden hver skrives ut etter alle nodene den avhenger av. En enkel rekursiv traversering kan altså gi oss en topologisk sortering. Hvis vi ikke husker hvilke noder vi allerede har besøkt kan vi risikere at noen noder blir med flere ganger; vi har da ikke en normal topologisk sortering, men nodene vil uansett skrives ut i en lovlig rekkefølge. 87 15

Her skal det stå subsett av kanter Eksempel: Prims algoritme Spenntre: Et subsett av nodene i en graf som utgjør et tre og som knytter sammen alle noder i grafen. Minimalt spenntre: Spenntre som har minimal vekt-sum over kantene. Prims algoritme finner et minimalt spenntre ved hjelp av enkel graf-traversering. Huskelisten er en prioritetskø der prioriteten til hver ubesøkt node er vekten på den korteste kanten som forbinder den med spenntreet. 88 16

Avhengighetsgrafer: Dyp innsikt Et problem kan nesten alltid deles opp i delproblemer. For et gitt problem har man ofte mange delproblemer som igjen kan deles opp i delproblemer, og imellom disse delproblemene har man avhengigheter. Disse avhengighetene kan representeres med en rettet graf. Hvis problemet ikke har sykliske avhengigheter (som leder til uendelig rekursjon) kan det representeres med en DAG. For veldig enkle problemer er avhengighetsgrafen rett og slett en sti: Hvert problem har kun ett delproblem. Den rekursive sum-funksjonen har for eksempel følgende avhengighetsgraf: f (1) f (2) f (n 1) f (n) For å beregne f (n) må alle delproblemene til venstre beregnes. Dette kan gjøres enten iterativt (fra venstre mot høyre) eller rekursivt ( tilsynelatende fra høyre mot venstre). 89 17

Avhengighetsgrafer: Dyp innsikt (2) For mer kompliserte avhengighetsgrafer: Finn en topologisk sortering, og beregn delproblemene i denne rekkefølgen. Husk: En topologisk sortering kan alltid finnes ved hjelp av et rekursivt dybde-først-søk. (Begrepet avhengighetsgraf er ikke pensum.) 90 18

4. Splitt og hersk Når avhengighetsgrafen er et tre Typiske kjøretider Eksempel: Binærsøk og søketrær Eksempel: Quicksort Eksempel: Randomized select Eksempel: Mergesort 91 19

Når avhengighetsgrafen er et tre For mange problemer er avhengighetsgrafen et tre (der alle kantene peker oppover ). Det vil si at vi kan dele problemet opp i delproblemer som er uavhengige av hverandre. Slike problemer kan ofte løses effektivt med rekursjon. Algoritmer som utnytter denne problemstrukturen kalles ofte splitt-og-hersk-algoritmer (divide and conquer algorithms). 92 20

Typiske kjøretider Rekurrensligningene for splitt-og-hersk-algoritmer har et typisk mønster. Hvis vi antar at problemet hele tiden deles i to like deler vil vi få rekurrensligninger av typen T(n) = T(n/2) + f (n) hvis kun den ene halvparten må behandles (binærsøk), eller T(n) = 2T(n/2) + f (n) hvis begge halvparter må behandles. Funksonen f (n) er avhengig av hvor mye arbeid som må utføres før/etter de rekursive kallene. 93 21

Eksempel: Binærsøk og søketrær Problem: Søk etter element i sortert tabell. Delproblemer: Søk i første og andre halvdel. Avhengighetsgrafen er altså et binærtre. Ved å undersøke det midterste elementet i det tabellsegmentet vi søker i kan vi ekskludere ett av de to delproblemene. Arbeidet for hvert delproblem er (1). Vi får dermed: T(1) = 1 T(n) = T(n/2) + (1) Iterasjonsmetoden gir oss uttrykket T(n) = T(n/2 i ) + (i). Vi setter n/2 i = 1 og får i = log 2 n. Med andre ord: T(n) = (log n) Hvis vi gjør om avhengighetsgrafen til et eksplisitt tre så får vi et binært søketre. Ved å dele problemet i flere deler enn 2 (og å dele etter ulike kriterier) kan vi få andre søketrær, som B-trær. Søketrær har gjerne andre operasjoner enn søk knyttet til seg, for å holde balansen. 94 22

Eksempel: Quicksort Problem: Sorter elementene i en tabell. Delproblemer: Sorter de små og store elementene hver for seg. Avhengighetsgrafen er altså et binærtre. Her må vi løse begge delproblemene før vi kan løse hovedproblemet. Å dele elementene i små (venstre) og store (høyre) koster oss (n) (partition). Under noen forenklende antagelser (f.eks. best-case-datasett) får vi følgende rekurrens: T(1) = (1) T(n) = 2T(n/2) + (n) Denne rekurrensen kan beregnes enten med iterasjonsmetoden (litt prakk, men det går) eller Masterteoremet. Resultatet er: T(n) = (n log n) (Average-case gir kjøretid O(n log n), men er vanskelig å beregne.) 95 23

Eksempel: Randomized select Problem: Finn det k-te største elementet i en usortert tabell. (Ved å sette k = n/2 finner vi medianen.) Igjen kan vi lage oss flere delproblemer ved å dele opp tabellen. Vi søker etter elementet enten blant de små eller de store elementene, avhengig av hvor mange små og store elementer vi har. For å dele inn i små og store elementer bruker vi igjen partition. Vi har altså to delproblemer, hvorav vi kun trenger å løse det ene. For å konstruere disse delproblemene må vi utfører (n) operasjoner (partition). Vi får da følgende rekurrens for best-case: T(1) = 1 T(n) = T(n/2) + (n) Iterasjonsmetoden gir oss uttrykket T(n/2 n ) + i j=1 (n/2 j 1 ). For i = log 2 n får vi T(n) = (n). Dette er også average-case. Worst-case er (n 2 ). I pensum finnes også en select-algoritme med worst-case kjøretid (n). 96 24

Eksempel: Mergesort Problem: Sorter elementene i en tabell. Delproblem: Sorter venstre og høyre halvdel. Avhengighetsgrafen vår er igjen et binærtre der vi må behandle begge halvdeler. For å kombinere løsningene til delproblemene må vi kjøre en flette-operasjon (merge) som har kjøretid (n). Siden vi alltid deler på midten vil worst-case for Mergesort bli akkurat det samme som best-case for Quicksort. 97 25

5. Dynamisk programmering... Når avhengighetsgrafen er en DAG Eksempel: Fibonacci-tallene Snu problemet på hodet Typiske kjøretider Eksempel: korteste vei i en DAG Eksempel: Dijkstras algoritme Eksempel: LCS Eksempel: Floyd-Warshall 98 26

... og grådighet Når vi har flaks med avhengighetsgrafen Eksempel: Huffman-koding Eksempel: Kruskals algoritme 99 27

Når avhengighetsgrafen er en DAG Det er ikke alle problem som er snille nok til at splitt-og-hersk-taktikker fungerer. Enkelte ganger vil delproblemene avhenge av samme delproblem (på et lavere nivå). Vi sier da at vi har overlappende delproblemer, og avhengighetsgrafen blir en generell DAG. Selv om antall delproblemer er polynomisk vil et naivt dybde-først-angrep sannsynligvis beregne delproblemene flere ganger antagelig et eksponensielt antall. 100 28

Eksempel: Fibonacci-tallene Et enkelt eksempel på dette er Fibonacci-tallene: f (0) = f (1) = 1 f (n) = f (n 1) + f (n 2) En ren rekursiv beregning vil gi her en eksponensiell kjøretid: int fib(int n) { if (n==0 n==1) return 1; return fib(n-1) + fib(n-2); } 101 29

Eksempel: Fibonacci-tallene (2) Ved å lagre alle delproblem som allerede er beregnet kan vi sørge for at hvert delproblem kun blir beregnet én gang. Det er det vi gjør i dynamisk programmering. En direkte rekursiv implementasjon av dynamisk programmering kalles memoisering (uten r!). int d[1000]; // Vilkårlig størrelse d[0] = d[1] = 1; int fib(int n) { if (d[n]==0) // Ikke beregnet d[n] = fib(n-1) + fib(n-2); return d[n]; } Denne implementasjonen vil gi lineær kjøretid. 102 30

Snu problemet på hodet Hvis du har tenkt ut en rekursiv løsning på problemet ditt, og har funnet ut en måte å lagre delproblem på, så trenger du ikke nødvendigvis beregne dem rekursivt. En rekursiv dybde-først-beregning vil gi deg en korrekt topologisk ordning av delproblemene, men husk at en hvilken som helst topologisk ordning vil gi korrekt svar. Ofte kan du beregne delproblemene iterativt. For Fibonacci-tallene kan vi beregne fra f (0) og oppover: int d[1000]; // Vilkårlig størrelse d[0] = d[1] = 1; int fib(int n) { for (int i=2; i<=n; ++i) d[i] = d[i-1] + d[i-2]; return d[n]; } En smartere løsning kunne klare seg med å lagre de to forrige verdiene. 103 31

Typiske kjøretider Det er vanskeligere å si noe generelt om kjøretider for algoritmer basert på dynamisk programmering. For å beregne kjøretiden for en gitt algoritme må du finnet ut hvor mange delproblemer du har, hvor mange kanter du har i avhengighetsgrafen, og hvor mye hver kant/node koster å beregne. For Fibonacci-tallene har vi (n) delproblem, og hvert delproblem har bare to avhengigheter. Hvert delproblem og hver avhengighet tar (1) tid å beregne, så den totale kjøretiden blir (n). 104 32

Eksempel: Korteste vei i en DAG Finn den korteste veien mellom to noder i en DAG. Anta at startnoden ikke har noen inn-kanter og at sluttnoden ikke har noen ut-kanter. Dette er et klassisk eksempel på dynamisk programmering, og her er vi så heldige at selve problemet gir oss avhengighetsgrafen eksplisitt. Delproblemene er nodene, og for å finne avstanden fra starten til en gitt node må vi ha avstandene til alle foreldrenodene (de med kanter inn til noden). Løsning: Gjør en topologisk sortering og beregn avstandene fra start til slutt. Avstanden d[i] til en node i er minimum over d[ j] + w( j, i) for alle noder j < i som har en kant til i. 105 33

Eksempel: Dijkstras algoritme Dijkstras algoritme går ett skritt videre enn DAG-shortest-path: Den tillater sykler i grafen (forutsatt at vi ikke har negative kantvekter). Også her kan vi si at hver node (hvert delproblem) er avhengig av alle foreldrenodene (de med kanter inn til noden). Problemet er at vi ikke kan løse et problem med en syklisk avhengighetsgraf direkte. Dijkstras algoritme løser dette på en genial måte: En node kan jo bare være avhengig av de nodene med lavere avstandsverdi (siden vi ikke har negative kanter). Hvis vi beregner dem i rekkefølge etter denne avstanden vil vi få riktig svar. Algoritmen baserer seg på en viktig egenskap: Hvis vi har beregnet de k nærmeste nodene, og hele tiden har kjørt relax på alle nyoppdagede noder, så vil noden med lavest avstandsestimat ha korrekt estimat. 106 34

Eksempel: Dijkstras algoritme (2) Med andre ord: Vi kjører en traversering der huskelisten er en prioritetskø, og prioriteten er avstandsmålet. Vi må sørge for å holde avstandsmålet oppdatert med relax (og dermed oppdatere prioritetskøen). Hvis prioritetskøen er en binær heap (og grafen er sammenhengende) blir kjøretiden O(E log V). 107 35

Eksempel: LCS For to sekvenser A og B, finn den lengste subsekvensen som forekommer i begge sekvensene. En subsekvens er et subsett av elementene elementer i samme rekkefølge som i originalen, ikke trenger ligge ved siden av hverandre. (F.eks. er (2, 4) en subsekvens av (1, 2, 3, 4, 5).) Som vanlig gjelder det å finne en fornuftig avhengighetsgraf. Hva blir delproblemene? Hvordan avhenger de av hverandre? Det er dette som er det vanskelige spørsmålet når man bruker dynamisk programmering. En intuisjon kan være å begynne å regne fra venstre, men vi har jo to sekvenser... Vi definerer et delproblem til å bestå av to prefix (A[1:i], B[1: j]), og kan da vise at løsningen kun avhenger av tre andre delproblem: (A[1:i 1], B[1: j]), (A[1:i], B[1: j 1]), og (A[1:i 1], B[1: j 1]). (Se læreboka for detaljer.) 108 36

Eksempel: LCS (2) Siden alle problemer har 3 delproblemer der problemstørrelsen kun er redusert med en konstant, så vil en naiv rekursiv løsning være eksponensiell. Men det er helt tydelig at det er stort overlapp mellom delproblemene. Hvis A og B har lengde m og n respektivt, så er det totale antall delproblem polynomisk, nemlig n m. Vi kan altså sette opp avhengighetsgrafen som noder i et rutenett med koordinater (i, j) og kanter fra (i 1, j), (i, j 1), og (i 1, j 1) til (i, j). Ved å plassere del-løsningene i en todimensjonal tabell kan vi beregne dem enten ved å iterere fra bunnen, (0, 0), eller ved rekursjon (memoisering). 109 37

Eksempel: Floyd-Warshall Beregn korteste avstand mellom alle par med noder i en graf. Igjen: Hvordan finner vi delproblemer, og hvordan henger de sammen? Floyd-Warshall har en smart løsning: Nummerer nodene fra 1 til n og la delproblem k være de korteste veiene som bare har lov til å gå via nodene 1... k. Det smarte er som følger: Hvis vi har de korteste avstandene der kun nodene 1... k 1 er mellomledd så er det en smal sak å beregne de korteste avstandene der også k er tillatt. d (k) w i j k = 0 i j = (k 1) (k 1) (k 1) min d, d + d k 1 i j ik k j Ved å bruke en tredimensjonal tabell (d[i,j,k]) kan dette beregnes med memoisering. Mer effektivt er det å bruke en todimensjonal tabell (d[i,j]) og gjøre unna én og én verdi for k iterativt. 110 38

Når vi har flaks med avhengighetsgrafen Av og til kan du ha flaks med avhengighetsgrafen. I disse tilfellene kan du finne delproblemer og avhengigheter som for dynamisk programmering, men istedenfor å løse alle delproblemer så kan du velge kun ett: Det som har best løsning. Det ligger som regel en DP-algoritme under enhver grådig algoritme. Hensikten med grådighet er å slippe å beregne alle delproblemene ved å velge det riktige delproblemet direkte. 111 39

Eksempel: Huffman-koding Gitt et sett med tegnfrekvenser for et dokument, finn en binær koding med variabel lengde slik at den totale lengden på det kodede dokumentet blir minimal. Disse kodene kan representeres som stier i et binærtre (0=venstre, 1=høyre) der tegnene er løvnoder. Huffman-algoritmen finner kodene på grådig vis: Konstruer en skog (sett av trær) der hver bokstav er et tre. Kombiner de to trærne med lavest total frekvens helt til du bare står igjen med ett tre. Vi kan se på hvert delproblem som en kombinasjon av to trær. Hvis vi ser på frekvensen til det nye treet som kostnaden ved et mulig valg, velger Huffman-algoritmen altså det delproblemet som har lavest kostnad. (Korrekthetsbeviset står i boka.) 112 40

Eksempel: Kruskals algoritme Kruskals algoritme løser min-spenntre-problemet på en måte som ligner Prims. Prims velger hele tiden den minste kanten som ligger inntil de nodene som allerede er med i spenntreet, og som ikke skaper en løkke. Kruskals algoritme velger rett og slett den minste kanten (hvor som helst i grafen) som ikke skaper en løkke i spenntreet. Begge algoritmene kan ses på som grådige. For hvert trinn velger de den lokalt beste løsningen. (Det generelle korrekthetsbeviset for MST-algoritmer står i boka og er verdt å få med seg.) 113 41

6. Iterative algoritmer Når avhengighetsgrafen er litt diffus Eksempel: Bellman-Ford Eksempel: Ford-Fulkerson/Edmonds-Karp 114 42

Når avhengighetsgrafen er litt diffus Det er ikke alltid vi klarer å få en avhengighetsgraf som egner seg får splitt-og-hersk-algoritmer eller dynamisk programmering. Men det hender at vi likevel kan takle problemet bit for bit, og vise at vi til slutt vil ha løst hele problemet. 115 43

Eksempel: Bellman-Ford Finn korteste vei fra én node til alle andre. Fungerer også med negative kantvekter; hvis det finnes negative løkker vil algoritmen si ifra om at ingen løsning finnes. Idéen er enkel: Kjøre relax langs alle kanter helt til du er sikker på at alle noder må ha riktig avstandsestimat. Hvor mange ganger må du gjøre det? Etter én gang vil alle naboene til startnoden ha riktig estimat. Etter to ganger vil naboene deres ha riktig estimat, etc. Hvis du har maks uflaks kan grafen være en sti, og du må da kjøre relax V 1 ganger for å nå frem til sluttnoden. Til slutt sjekker algoritmen om det finnes noen noder som kan få forbedret avstanden sin med enda et kall til relax hvis det finnes, så er det en negativ sykel i grafen. Kjøretiden blir O(VE). 116 44

Eksempel: Ford-Fulkerson/Edmonds-Karp Finn maksimal flyt i et flyt-nettverk. Her gjelder det å fylle på mer og mer flyt til det ikke går an å fylle på mer. Ford-Fulkerson-metoden (generell metode mer enn en algoritme) sier følgende: Hvis vi gang på gang leter opp en sti fra kilde til sluk som har ledig kapasitet, og skyver så mye flyt igjennom, vil vi til slutt få maksimal flyt. (Ganske logisk, egentlig.) Edmonds-Karp-algoritmen bruker Ford-Fulkerson-metoden, og velger alltid den korteste veien med ledig kapaitet (bruker BFS). Dette gir bedre kjøretid enn vilkårlig valg av sti. Hovedpoenget er at delproblemene våre er biter av den maksimale flyten (ledig kapasitet). Vi leter opp disse bitene og løser dem ved å kjøre på mer flyt. 117 45

Avsluttende ninjatriks 1 (av 2) Binærsøk kan brukes til mer enn å søke i tabeller... Her skal det stå 1 m Anta at du har en algoritme A(x) som kan sjekke om parameteren x er for stor eller for liten. Anta også at x kan ta verdier i en endelig ordnet mengde, f.eks. 1... n. Hvis algoritmen A(x) har kjøretid T(n) så vil du med binærsøk kunne finne riktig verdi for x med kjøretid T(n) log m. Eksempel: Anta at du har en algoritme A(G, x) som for en gitt graf G kan avgjøre om alle noder kan nå hverandre med maks x steg. Anta at denne algoritmen har kjøretid (V 2 ). Da kan du lett vinne den laveste lovlige verdien for x i kjøretid (V 2 log V). En faktor log n er som regel ganske liten. Men dette problemet kan løses mer effektivt; og det leder oss videre til... 118 46

Spørsmål fra salen? 119

Avsluttende ninjatriks 2 (av 2) Jo mer du vet om problem-instansen (datasettet) ditt, jo bedre kan du løse problemet. Eksempel: Generell sortering er (n log n), men hvis du vet at elementene er heltall i området 1... k (der k ikke er så alt for stor) kan du bruke tellesortering (eller radiks-sortering), og sortere i (nk) tid. Generelle algoritmer (f.eks. lineær programmering) trengs, og de kan brukes til å løse det meste, men er som regel dårligere enn skreddersydde algoritmer. Derfor er det viktig at dere lærer å lage deres egne algoritmer! Stå på, og lykke til på eksamen, Magnus. 120 47