AlgDat notater. Einar Baumann. 2. desember 2012



Like dokumenter
Løsningsforslag - Korteste vei

Korteste Vei II. Lars Vidar Magnusson Kapittel 24 Bellman-Ford algoritmen Dijkstra algoritmen

Avanserte flytalgoritmer

Avsluttende eksamen i TDT4120 Algoritmer og datastrukturer

Algdat - Øvingsforelesning. Maks flyt

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

Lars Vidar Magnusson

Korteste vei i en vektet graf uten negative kanter

Algdat Eksamensforelesning. Nils Barlaug

Korteste Vei I. Lars Vidar Magnusson Kapittel 24 Hvordan finne korteste vei Egenskaper ved korteste vei

Heapsort. Lars Vidar Magnusson Kapittel 6 Heaps Heapsort Prioritetskøer

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Et eksempel: Åtterspillet

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

INF2220: Time 12 - Sortering

Anvendelser av grafer

Eksamen i tdt4120 Algoritmer og datastrukturer

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Heap* En heap er et komplett binært tre: En heap er også et monotont binært tre:

Algoritmer og datastrukturer Kapittel 9 - Delkapittel 9.1

Korteste vei problemet (seksjon 15.3)

Grunnleggende Grafteori

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Algoritmer og datastrukturer Assignment 11 Side 1 av 5

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

deeegimnoorrrsstt Sjette forelesning

NITH PG4200 Algoritmer og datastrukturer Løsningsforslag Eksamen 4.juni 2013

Algdat - øvingsforelesning

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

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

IN Algoritmer og datastrukturer

Pensum: fra boken (H-03)+ forelesninger

Øvingsforelesning 6. Sorteringsalgoritmer. Kristian Veøy

Pensum: fra boken (H-03)+ forelesninger

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

Algoritmer og datastrukturer Kapittel 9 - Delkapittel 9.2

Notater til INF2220 Eksamen

Øvingsforelesning 6. Sorteringsalgoritmer. Martin Kirkholt Melhus Basert på foiler av Kristian Veøy 30/09/14 1

Grunnleggende Grafalgoritmer

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Innhold. Innledning 1

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

Vann i rør Ford Fulkerson method

Løsningsforslag for utvalgte oppgaver fra kapittel 9

Lars Vidar Magnusson

Diagnosekart for oblig 2, INF3/4130 h07

Øvingsforelesning 12 Maks flyt

Avsluttende eksamen i TDT4120 Algoritmer og datastrukturer

MAT1140: Kort sammendrag av grafteorien

Løsnings forslag i java In115, Våren 1996

Sortering i Lineær Tid

Øvingsforelesning 9. Flytnettverk, maksimum flyt og maksimum bipartitt matching. Jon Marius Venstad

Binær heap. En heap er et komplett binært tre:

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.

INF Algoritmer og datastrukturer

Øvingsforelesning Korteste vei: Alle til alle

Rekursiv programmering

Dynamisk programmering

Oppgave 1 a. INF1020 Algoritmer og datastrukturer. Oppgave 1 b

IN Algoritmer og datastrukturer

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

ALGORITMER OG DATASTRUKTURER

Avsluttende eksamen i TDT4120 Algoritmer og datastrukturer

Grunnleggende Grafalgoritmer II

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

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

Fra A til B. Syvende forelesning

Algoritmer og Datastrukturer

EKSAMEN med løsningsforslag

Lineær sortering. Radix sort

Definisjon. I et binært tre har hver node enten 0, 1 eller 2 barn

Avsluttende eksamen i TDT4120 Algoritmer og datastrukturer

EKSAMEN. Dato: 18. mai 2017 Eksamenstid: 09:00 13:00

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Heap og prioritetskø. Marjory the Trash Heap fra Fraggle Rock

EKSAMEN. Dato: 28. mai 2018 Eksamenstid: 09:00 13:00

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

Dijkstras algoritme Spørsmål

Grunnleggende Datastrukturer

Vi skal se på grafalgoritmer for:

Løsningsforslag - Floyd-Warshall

Lars Vidar Magnusson Kapittel 13 Rød-Svarte (Red-Black) trær Rotasjoner Insetting Sletting

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

INF Algoritmer og datastrukturer

Algoritmer og datastrukturer Kapittel 1 - Delkapittel 1.8

Dynamisk programmering Undervises av Stein Krogdahl

Ny/utsatt EKSAMEN. Dato: 5. januar 2018 Eksamenstid: 09:00 13:00

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

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

Dynamisk programmering

KORTESTE STI. Vektede Grafer. Korteste Sti. Dijkstra s Algoritme. Vektet Urettet Graf

INF Algoritmer og datastrukturer

PG4200 Algoritmer og datastrukturer Forelesning 10

UNIVERSITETET I OSLO

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

IN Algoritmer og datastrukturer

Grådige algoritmer. Lars Vidar Magnusson Kapittel 16. Aktivitetvelgingsproblemet Huffmankoder

INF Algoritmer og datastrukturer

ALGORITMER OG DATASTRUKTURER

Transkript:

AlgDat notater Einar Baumann 2. desember 2012

Innhold 1 Sorteringsalgoritmer 5 1.1 Insertion sort................................... 5 1.1.1 Prinsipp.................................. 5 1.1.2 Pseudokode................................ 5 1.2 Heapsort...................................... 5 1.2.1 Prinsipp.................................. 5 1.2.2 Pseudokode................................ 6 1.3 Counting sort................................... 6 1.3.1 Prinsipp.................................. 6 1.3.2 Pseudokode................................ 6 1.4 Radix sort..................................... 7 1.4.1 Prinsipp.................................. 7 1.4.1.1 Sortering av tall med b-bit................... 8 1.4.2 Pseudokode................................ 8 1.5 Bucket sort.................................... 8 1.5.1 Prinsipp.................................. 8 1.5.2 Pseudokode................................ 9 2 Korteste vei algoritmer 10 2.1 Generelt om korteste sti algoritmer........................ 10 2.1.1 En til alle / single source......................... 10 2.1.2 Alle til alle / all pairs........................... 10 2.1.3 Tilfeller der korteste sti ikke finnes.................... 10 2.2 Relaxation..................................... 10 2.2.1 Pseudokode................................ 11 2.3 Dijkstras algoritme................................ 11 2.3.1 Prinsipp.................................. 11 2.3.2 Analyse.................................. 12 2.3.3 Pseudokode................................ 13 2.4 Bellman-Ford................................... 13 2.4.1 Pseudokode................................ 13 3 Maksimal flyt 14 3.1 Ford-Fulkersons metode............................. 15 3.1.1 Residualnettverk............................. 15 3.1.2 Minimale snitt.............................. 16 2

3.1.3 Ford-Fulkersons algoritme........................ 16 3.1.4 Edmonds-Karp algoritmen........................ 17 4 Dynamisk programmering 18 4.1 Implementeringsmetoder............................. 18 4.1.1 Topp-ned med memoisering....................... 18 4.1.2 Bunn-opp................................. 18 4.2 Kutting av staver................................. 19 4.3 Matrisemultiplikasjon............................... 20 4.4 Longest common subsequence.......................... 21 4.4.1 Finne LCS for hånd vha. tabell...................... 23 4.4.2 Redusering av plassbehov........................ 23 5 Datastrukturer 25 5.1 Stack (LIFO)................................... 25 5.1.1 Prinsipp.................................. 25 5.1.2 Operasjoner................................ 25 5.2 Queue (FIFO)................................... 25 5.2.1 Prinsipp.................................. 26 5.2.2 Operasjoner................................ 26 5.3 Linked list..................................... 26 5.3.1 Prinsipp.................................. 26 5.3.2 Operasjoner................................ 26 5.4 Representasjoner av rotede trær.......................... 27 5.4.1 Binære trær................................ 27 5.4.2 n-ære trær................................. 27 5.5 Heap (max).................................... 27 5.5.1 Prinsipp.................................. 28 5.5.1.1 Bygge en heap......................... 28 5.5.1.2 Vedlikeholde en heap..................... 29 5.5.2 Operasjoner................................ 29 5.6 Prioritetskø (max)................................. 30 5.6.1 Prinsipp.................................. 30 5.6.2 Operasjoner................................ 30 5.7 Binære søketrær.................................. 31 5.7.1 Prinsipp.................................. 31 5.7.1.1 Traversering.......................... 31 5.7.1.2 Søking............................. 31 5.7.1.3 Minimum og maksimum................... 32 5.7.1.4 Innsetting........................... 32 5.7.1.5 Sletting............................. 32 5.7.2 Operasjoner................................ 32 5.7.3 Optimale binære søketrær........................ 34 5.7.3.1 Optimal substruktur...................... 35 3

5.7.3.2 Rekursiv løsning........................ 35 5.7.3.3 Beregning av forventet søkekostnad til et optimalt binært søketre 36 6 Kjøretid 37 6.1 Kjøretid fra rekurrensrte............................. 37 7 Mastermetoden 38 8 NP-komplette problemer 40 8.1 Clique-problemet................................. 41 8.2 Vertex-cover problemet.............................. 41 8.3 Hamiltonsykel problemet............................. 41 8.4 Traveling salesman problemet (TSP)....................... 42 8.5 Subset-sum problemet.............................. 42 4

1 Sorteringsalgoritmer 1.1 Insertion sort 1.1.1 Prinsipp Insertion sort brukes for å sortere en sekvens med tall. Sekvensen tas inn som en rekke ("array"). Insertion sort er effektivt når man vil sortere et lite antall elementer. Algortimen fungerer ved at man starter med nest laveste indeks. Elementet i denne posisjonen sammenlignes med elementet i første posisjon, hvis det er lavere byttes de om. Man flytter deretter "startpunktet" mot høyre og sjekker hele tiden mot venstre. Rekken til venstre for startpunkttet vil alltid være sortert. 1.1.2 Pseudokode 1 I n s e r t i o n S o r t (A) 2 f o r j = 2 t o A. l e n g t h 3 key = A[ j ] 4 i = j 1 5 w h i l e i > 0 and A[ i ] > key 6 A[ i + 1] = A[ i ] 7 i = i 1 8 A[ i + 1] = key 1.2 Heapsort Kjøretid Antagelser og Begrensninger Egnet for O(nlgn) - Det meste. Bedre WC kjøretid (den er optimal), men i praksis tregere enn quicksort. 1.2.1 Prinsipp Starter med å bygge en max-heap (se Seksjon 5.5). Fordi det største elementet i en max-heap er lagret i rota A[1] kan man starte med å sette den på slutten, altså bytte om på A[1] og A[n]. Deretter fjerner vi node n fra heapen (men ikke fra arrayet); det gjøres ved å dekrementere 5

A.heap-size. Nå kan det være at elementet i A[1] bryter heap-egenskapen, men det fikses ved å kjøre Max-heapify(A,1). Dette gjentas til A.heap size = 2. 1.2.2 Pseudokode 1 H e a p s o r t (A) 2 Build Max Heap (A) 3 f o r i = A. l e n g t h downto 2 4 exchange A[ 1 ] with A[ i ] 5 A. heap s i z e = A. heap s i z e 1 6 Max Heapify (A, 1 ) 1.3 Counting sort Kjøretid Antagelser og Begrensninger Egnet for Θ(n + k). Dersom k = O(n) (det er da man i praksis bruker algoritmen) er kjøretiden Θ(n). Antar at hvert av inputelement n er et heltall i området mellom 0 og k. Sortering av heltall innenfor et område av rimelig størrelse. 1.3.1 Prinsipp Antar at hvert inputelement n er et heltall i området mellom 0 og k. Counting sort avgjør for hvert element x antallet elementer som er minder enn x; denne informasjonen brukes videre til å plassere x direkte inn i den korrekte posisjonen i output arrayet. F.eks. hvos man finner ut at det er 17 elementer som er mindre enn x, så vil x plasseres i posisjon 18. Denne fremgangsmåten modifiseres litt for å ta hensyn til at det kan finnes flere elementer med samme verdi; Hver gang man setter inn et element dekrementeres antallet elementer med verdi mindre eller lik dette elementet, slik at dersom denne verdien dukker opp igjen, så vil det nye elementet havne rett foran det forige elementet med den verdien i output arrayet. Dette gjør også at algoritmen er stabil. Counting sort bruker tre arrayer; input arrayet A; arrayet B som inneholder det ferdig sorterte outputet; og arrayet C som brukes som midlertidig arbeidsminne. 1.3.2 Pseudokode 1 Counting S o r t (A, B, k ) 2 l e t C [ 0.. k ] be a new a r r a y 3 f o r i = 0 t o k 4 C[ i ] = 0 5 f o r j = 1 t o A. l e n g t h 6

6 C[A[ j ] ] = C[A[ j ] ] + 1 7 / / C[ i ] i n n e h o l d e r nå a n t a l l e t e l e m e n t e r l i k i 8 f o r i = 1 t o k 9 C[ i ] = C[ i ] + C[ i 1] 10 / / C[ i ] i n n e h o l d e r nå a n t a l l e t e l e m e n t e r som e r mindre e l l e r l i k i 11 f o r j = A. l e n g t h downto 1 12 B[C[A[ j ] ] ] = A[ j ] 13 C[A[ j ] ] = C[A[ j ] ] 1 1.4 Radix sort Kjøretid Antagelser og Begrensninger Egnet for Gitt n tall med d siffer der hvert siffer kan ha k forskjellige verdier: Θ(d (n + k)) dersom den stabile sorteringen er Θ(n + k). Gitt n b-bit tall og et positivt heltall r b: Θ((b/r)(n + 2 r )) dersom den stabile sorteringen er Θ(n + k) for input i intervallet 0 til k. Antar at hvert av inputelement n er et heltall i området mellom 0 og k med d siffer. Lister med store heltall, sortere en liste med datoer (dagmånedår). 1.4.1 Prinsipp Radix sort sorterer tall ved å se på det minst signifikante sifferet først. Deretter sorteres det på det nest minst signifikante sifferet osv. For tallet 1234 ville man altså først sett på 4, så 3 osv. For å sortere på hvert siffer brukes en vilken som helst stabil sorteringsalgoritme, f.eks. counting sort. Radix-Sort antar at hvert element i arrayet A har d siffer, der siffer 1 er sifferet av lavest orden og siffer d er høyest orden. 7

1.4.1.1 Sortering av tall med b-bit Gitt n b-bit tall og et positivt heltall r b: Θ((b/r)(n + 2 r )) dersom den stabile sorteringen er Θ(n + k) for input i intervallet 0 til k. Dette er fordi at vi for en verdi r b kan si at nøkkelen har d = b/r siffer med r bit hver. Hvert siffer er et heltall i intervallet mellom 0 og 2 r 1, slik at vi kan bruke counting sort med k = 2 r 1. Da kan man f.eks. se det som at et 32-bits ord har fire 8-bits siffer, slik at b = 32, r = 8, k = 2 r 1 = 255 og d = b/r = 4. 1.4.2 Pseudokode 1 Radix S o r t (A, d ) 2 f o r i = 1 t o d 3 use a s t a b l e s o r t t o s o r t a r r a y A on d i g i t i 1.5 Bucket sort Kjøretid Antagelser og Begrensninger Egnet for AC: O(n) Input er genrert av en tilfeldig prosess som fordeler elementene uniformt og uavhengig over intervallet [0,1). Input med uniform og uavhengig fordeling av elementer. Man oppnår også lineær tid så lenge input har følgende egenskap: summen av kvadratene av bøttestørrelsene er lineær i med totale antallet elementer. 1.5.1 Prinsipp Bucket sort deler intervallet [0,1) opp i n like store subintervaller, «bøtter». Deretter fordeles de n input-tallene i bøttene. Siden tallene er uniformt og uavhengig fordelt over intervallet regner vi med at det ikke havner mange tall i hver bøtte, så vi bruker insertion sort for å sortere tallene i hver bøtte. Til slutt går vi gjennom bøttene i rekkefølge og lister opp tallene i hver. En illustrasjon følger: Input array: A.78.17.39.26.72.94.21.12.23.68 1 2 3 4 5 6 7 8 9 10 8

Input fordelt i bøtter og sortert: B 0-1.12.17 2.21.23.26 3.39 4-5 - 6.68 7.72.78 8-9.94 1.5.2 Pseudokode 1 Bucket S o r t (A) 2 l e t B [ 0.. n 1] be a new a r r a y 3 n = A. l e n g t h 4 f o r i = 0 t o n 1 5 make B[ i ] an empty l i s t 6 f o r i = 1 t o n 7 i n s e r t A[ i ] i n t o l i s t B[ f l o o r ( na [ i ] ) ] 8 f o r i = 0 t o n 1 9 s o r t l i s t B[ i ] w ith i n s e r t i o n s o r t 10 c o n n e c t t h e l i s t s B [ 0 ], B [ 1 ],..., B[ n 1] i n o r d e r 9

2 Korteste vei algoritmer 2.1 Generelt om korteste sti algoritmer 2.1.1 En til alle / single source Alle har rettede kanter. Uvektede kanter: BFS - O(V + E) Ikke-negative kantvekter: Dijkstra - O(E +V lgv ) Generelt: Bellman-Ford - O(V E) V E er minimum kvadratisk i en connected graph fordi E V 1. DAG: Topologisk sortering og én kjøring av Bellman-Ford i rekkefølgen gitt av top. sortertingen - O(V + E). 2.1.2 Alle til alle / all pairs Alle har rettede kanter. Kjører generelt den for en-til-alle samtidig fra alle kanter. Uvektede kanter: V BFS - O(V E) Ikke-negative kanter: V Dijkstra - O ( V E +V 2 lgv ) Generelt: Floyd-Warshall - O ( V 3) Kan også bruke V Bellman-Ford - O ( V 2 E ) DAG: Topologisk sortering og én kjøring av Bellman-Ford i rekkefølgen gitt av top. sortertingen gjort fra alle noder - O ( V 2 +V E ). 2.1.3 Tilfeller der korteste sti ikke finnes Hvis grafen inneholder negative sykler Hvis det ikke finnes en sti fra u til v. 2.2 Relaxation Mange algoritmer bruker en teknikk som kalle relaxation. For hver node v V, tar vi vare på et foreløpig estimat (en øvre grense) for lengden fra startnoden s til ndoen v v.d. Vi kaller v.d et korteste-sti estimat. Korteste-sti estimater og forgjengere initialiseres med Initialize-Single-Source(G,s) 10

Når man relaxer en kant (u,v) sjekker man om man kan forbedre det foreløpige estimatet for den korteste stien fra til v ved å gå gjennom u, isåfall oppdaterer vi v.d og v.π. Dette gjøres med Relax(u,v,w). 2.2.1 Pseudokode 1 I n i t i a l i z e S i n g l e Source (G, s ) 2 f o r each v e r t e x v i n G.V 3 v. d = i n f 4 v. p i = NIL 5 s. d = 0 1 Relax ( u, v,w) 2 i f v. d > u. d + w( u, v ) 3 v. d = u. d + w( u, v ) 4 v. p i = u 2.3 Dijkstras algoritme Kjøretid Med min-prioritetskø med binær-heap: O((V + E) lgv ). Med min-prioritetskø med Fibonacci heap: O(V lgv + E). Antagelser og Takler ikke negative kanter. Begrensninger Egnet for Raskeste algoritme for grafter med kun positive kantvekter. (DAG-SP er raskere dersom man ikke har noen sykler, den takler i tillegg negative kanter). 2.3.1 Prinsipp Dijkstras algoritme bruker et sett S med kanter hvis endelige korteste-sti vekter fra kilden s allerede er bestemt. Algortimen velger gjentatte ganger noden u V S med det minste korteste-sti estimatet, legger u til i S, og relaxer alle kanter ut fra u. I implementeringen Dijkstra(G,w,s) under brukes en min-prioritetskø Q med noder som har d som nøkler. En kjøring av Dijkstras er illustrert under. 11

Fordi Dikstras alltid velger velger å legge den letteste eller nærmeste noden i V S til i S, sier vi at den er en grådig algoritme. At den er grådig er også grunnen til at den ikke fungerer med negative kantvekter; en sti som starter med en tung kant, men har etterfølgende kanter med negativ vekt, kan være billigere enn en sti som starter med en lett kant. Teorem (Korrektheten til Dijkstras algoritme). Dijkstras algoritme, kjørt på en vektet, rettet graf G(V,E) med positiv kantfunksjon w og kilde s terminerer med u.d = δ (s,u) for alle noder u V. Bevis. 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. 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-shortest-path. Betrakt den neste i sortert rekkefølge. Den har korrekt estimat. Alle de g jenvæ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. Korollar. Dijkstras algoritme, kjørt på en vektet, rettet graf G(V, E) med positiv kantfunksjon w og kilde s terminerer med forgjenger subgraf G π som et korteste-sti tre med rot i s. 2.3.2 Analyse Dijkstras algoritme bevarer min-prioritetskøen Q ved å kalle tre prioritetskø operasjoner: Insert (implisitt i linje 4), Extract-Min (linje 6), Decrease-Key (implisitt i Relax, linje 9). Algortimen kaller både Insert og Extract-Min en gang per node. Fordi hver node u V legges til i S nøyaktig én gang, vil for-løkken se på hvert hjørne i Ad j [u] nøyaktig én i løpen av en kjøring av algoritmen. Siden totalt antall kanter i alle nabolistene er E vil denne løkken iterere totalt E ganger, og dermed gjøre Decrease-Key maksimalt E ganger totalt. 12

Kjøretiden til algoritmen avhenger av hvordan i implementerer min-prioritetskjøen. Hvis grafen har tilstrekkelig få kanter kan vi bruke en binær min-heap. Det tar O(V ) tid å bygge heapen; hver Extract-Min operasjon tar da O(lgV ) tid, og dette må som nevnt gjøres V ganger; og hver Decrease-Key operasjon tar O(lgV ), som må gjentas maksimalt E ganger. Total kjøretid er altså O((V + E)lgV ), eller O(E lgv ) dersom alle noder kan nås fra kilden s. 2.3.3 Pseudokode 1 D i j k s t r a (G, w, s ) 2 I n i t i a l i z e S i n g l e Source (G, s ) 3 S = EMPTY 4 Q = G.V 5 w h i l e Q!= EMPTY 6 u = E x t r a c t Min (Q) 7 S = S UNION u 8 f o r each v e r t e x v i n G. Adj [ u ] 9 Relax ( u, v,w) 2.4 Bellman-Ford Kjøretid Med min-prioritetskø med binær-heap: O(V E) Antagelser og - Begrensninger Egnet for Grafer med negative kanter. 2.4.1 Pseudokode 1 Bellman Ford (G, w, s ) 2 I n i t i a l i z e S i n g l e Source (G, s ) 3 f o r i = 1 t o G.V 1 4 f o r each edge ( u, v ) i n G. E 5 Relax ( u, v,w) 6 f o r each edge ( u, v ) i n G. E 7 i f v. d < u. d + w( u, v ) 8 r e t u r n FALSE 9 r e t u r n TRUE 13

3 Maksimal flyt Symbol Betydning s Kilde t Avløp c(u, v) Kapasitet fra hjørne u til hjørne v. f (u,v) Flyt fra hjørne u til hjørne v. 0 f (u,v) c(u,v) f Total strøm ut fra kilden minus total strøm inn til kilden. f = v V f (s,v) v V f (v,s) Flyten fra et hjørne til et annet må være større enn null. Flyten fra et hjørne til et annet kan ikke være større enn kapasiteten. Total flyt inn i et hjørne må være lik total flyt ut fra samme hjørne, bortsett fra for s og t. Hvis en kant er i nettverket, så er ikke den antiparallelle kanten det. (Dette er et krav Cormen stiller for å forenkle ting. Det er strengt tatt ikke nødvendig.) Antiparallelle kanter håndteres i Cormen ved at man setter inn en ekstra node. Dette er vist i figurene under. a 2 3 b a 2 3 3 c b Flere kilder eller avløp kan håndteres ved å lage en superkile og et superavløp, der kantene fra s og til t har uendelig kapasitet. 14

s s1 s2 s3 s4 s1 s2 s3 s4 t1 t2 t1 t2 t 3.1 Ford-Fulkersons metode Ford-Fulkersons metode øker iterativt verdien til strømmen. Man starter med f (u,v) = 0 (u,v) V. Ved hver iterasjon økes strømverdien f ved å finne en forøkende sti. 1 i n i t i a l i z e flow f t o 0 2 w h i l e t h e r e e x i s t s an augmenting p a t h p i n t h e r e s i d i a l network G_f 3 augment flow f a l o n g p 4 r e t u r n f 3.1.1 Residualnettverk Gitt et stømnettverk G og en strøm f, så er residualnettverket G f nettverket der strømmen er trukket fra kapasitetene til kantene (slik at man finner residualkapasiteten) og kanter der all kapasitet er brukt er fjernet. G f kan også inneholde kanter som ikke er i G; dersom man ønsker å redusere strømmen på en kant (u,v) i residualnettverket, så kan man sette opp en kant (v,u) med kapasitet c f (v,u) = c f (u,v), for på den måten å sende strøm tilbake. Formelt er residualkapasiteten c f til flytnettverket G = (V,E) definert som c(u,v) f (u,v), hvis (u,v) E c f (u,v) = f (v,u), hvis (v,u) E 0, ellers 15

Fordi vi har sagt at (u,v) E = (v,u) / E gjelder nøyaktig ett tilfelle for alle par av hjørner. Som et eksempel, hvis c(u,v) = 16 og f (u,v) = 11, så kan vi øke f (u,v) med opp til c f (u,v) = 5. Vi kan også la en algoritme returnere opp til 11 strømningsenheter fra v til u, slik at c f (v,u) = 11. Residualnettverket til G indusert av f er G f = (V,E f ). Hvis f er en strøm i G og f er en strøm i residualnettverket G f, definerer vi forøkningen av strømmen f med f, f f, til å være funksjonen ( f f ) { f (u,v) + f (u,v) f (v,u), hvis (u,v) E (u,v) = 0, ellers Man kan tenke på det som at når man øker strømmen over (v,u), så dytter man strømmen som fra før av går over (u,v) tilbake. Dette kalles kansellering. 3.1.2 Minimale snitt Et minimalt snitt for et nettverk er et snitt som gir minimal kapasitet over alle snitt i nettverket. Følgende lemma viser at for enhver gitt strøm f vil nettostrømmen over ethvert snitt være den samme og lik f, verdien til strømmen. Lemma 1. La f være en strøm i strømnettveket G med kilder s og t, og la (S,T ) være et hvilket som helst snitt av G. Nettostrømmen over (S,T ) vil da være f (S,T ) = f, verdien til strømmen. Korollar 1. Verdien til en hvilken som helst strøm f i et strømningsnettverk G er begrenset av kapasiteten til et hvilket som helst snitt av G. Følgende teorem sier at verdien til max-strømmen er lik kapasiteten til et minimalt snitt. Teorem 1 (Max-flow min-cut teoremet). Hvis f er en strøm i et strømningsnettverk G = (V,E) med en kilde s og utløp t vil følgende betingelser være ekvivalente. 1. f er en max-strøm i G. 2. Residualnettverket G f inneholder ingen forøkende stier. 3. f = c(s,t ) for et vilkårlig snitt (S,T ) av G. 3.1.3 Ford-Fulkersons algoritme For hver iterasjon i Ford-Fulkersons metode finner vi en forøkende sti p og bruker den til å modifisere flyten f. 1 Ford F u l k e r s o n (G, s, t ) 2 f o r each edge ( u, v ) i G. E 3 ( u, v ). f = 0 4 w h i l e t h e r e e x i s t s a p a t h p from s t o t i n t h e r e s i d u a l network G_f 5 c _ f ( p ) = min{ c _ f ( u, v ) : ( u, v ) e r i p} 16

6 f o r each edge ( u, v ) i n p 7 i f ( u, v ) i n E 8 ( u, v ). f = ( u, v ). f + c _ f ( p ) 9 e l s e ( v, u ). f = ( v, u ). f c _ f ( p ) Kjøretiden til algoritmen avhenger av hvordan vi finner den forøkende stien p på linje 3. Hvis vi f.eks. finner den vha. BFS vil algoritmen ha polynomisk kjøretid. Hvis f angir max-flyten i det transformerte nettverket, vil en enkel implementering av Ford- Fulkerson kjøre while-løkken maksimalt f ganger, ettersom flyten øker med minst én enhet pr. iterasjon. Kjøretiden med BFS eller DFS for å finne p blir da O(E f ). (BFS/DFS har kjøretid O(E), resten O( f ). 3.1.4 Edmonds-Karp algoritmen Edmonds-Karp algoritmen velger den korteste stien fra s til t i residualnettverket. Den korteste stien finnes med BFS. Den er ellers lik Ford-Fulkerson. Kjøretiden er O ( V E 2). 17

4 Dynamisk programmering Den generelle fremgangsmåten for å løse et problem med dynamisk programmering er som følger: 1. Beskriv strukturen til en optimal løsning. 2. Definer verdien til en optimal løsning rekursivt (avhengig av skrittet før). 3. Beregn verdien til en optimal løsning bunn-topp. 4. Bygg opp en optimal løsning fra de beregnede datene. Dynamisk programmering bruker mere minne for å spare regnetid. En algoritme der dynamisk programmering er brukt kjører på polynomisk tid dersom antallet forskjellige proglemer er polynomisk i inputstørrelsen og hvert problem kan løses på polynomisk tid. Uformelt kan man si at kjøretiden til DP algoritmer avhenger av produktet av to faktorer; antallet subproblemer totalt og antallet valg vi ser på for hvert subproblem. 4.1 Implementeringsmetoder Det finnes to ekvivalente måter for å implementere dynamisk programmering; topp-ned med memoisering og bunn-opp. De to metodene gir algoritmer med samme asymptotiske kjøretid, bortsett fra i spesielle tilfeller der topp-ned metoden ikke undersøker alle mulige subproblemer. Bunn-opp metoden gir gjerne bedre konstantledd 4.1.1 Topp-ned med memoisering Prosedyren skrives her rekursivt på vanlig måte, men resultatet av hvert underproblem lagres, vanligvis i et array eller i en hash-tabell. Prosedyren sjekker først om den har regnet ut det gjeldende problemet; dersom den har det returneres det tidligere beregnede svaret. Dersom den ikke har det, beregnes det og lagres. 4.1.2 Bunn-opp Denne metoden avhenger gjerne av at man har et naturlig begrep for størrelsen til et subproblem, slik at man for å løse et subproblem bare trenger å løse mindre subproblemer. Man sorterer da problemene etter størrelse og løser det miste problemet først. Når vi da skal løse et gitt problem 18

har vi allerede løst alle de mindre subproblemene det avhenger av, og har lagret løsnignene på de. 4.2 Kutting av staver Gitt en tenkt bedrift som kjøper inn lange staver og kutter de opp i kortere staver som selges videre. Hvert snitt er gratis. Prisen p i som bedriften tar for en stav med lengde i er gitt i følgende tabell: Lengde i 1 2 3 4 5 6 7 8 9 10 Pris p i 1 5 8 9 10 17 17 20 24 30 Vi sier at 7 = 2 + 2 + 3 betyr at en stav på lengde 7 deles opp i to deler med lengde 2 og én del med lengde 3. Hvis en optimal løsning deler staven opp i k deler for en 1 k n så er en optomal dekomponering n = i 1 + i 2 + + i k som gir inntjeningen r n = p i1 + p i2 + + p ik. For problemet vårt kan den opptomale inntjeningen r i for i = 1,2,...,10 ved å se på de korresponderende optimale dekomponeringene r 1 = 1 fra løsningen 1 = 1 (ingen kutt) r 2 = 5 fra løsningen 2 = 2 (ingen kutt) r 3 = 8 fra løsningen 3 = 3 (ingen kutt) r 4 = 10 fra løsningen 4 = 2 + 2 r 5 = 13 fra løsningen 5 = 2 + 3 r 6 = 17 fra løsningen 6 = 6 (ingen kutt) r 7 = 18 fra løsningen 7 = 1 + 6 eller 7 = 2 + 2 + 3 r 8 = 22 fra løsningen 8 = 2 + 6 r 9 = 25 fra løsningen 9 = 3 + 6 r 10 = 30 fra løsningen 10 = 10 (ingen kutt) Man kan genrelt finne den optimale løsningen ved å se på den optimale løsningen for kortere staver. r n = max 1 i n (p i + r n 1 ) Dersom man vil løse dette problemet med dynamisk programmering er det lettest p gjøre det på bunn-opp metoden. Følgende algoritme tar inn et array r [i] der 0 i n gir salgsprisen for en stang med lengde i. 1 Bottom Up Cut Rod ( p, n ) 2 l e t r [ 0.. n ] be a new a r r a y 3 r [ 0 ] = 0 4 f o r j = 1 t o n 5 q = i n f 6 f o r i = 1 t o j 7 q = max ( q, p [ i ] + r [ j i ] ) 19

8 r [ j ] = q 9 r e t u r n r [ n ] Denne algoritmen gir verdiene til en optimal løsning, men den gir ikke den faktiske løsningen; en liste over del-størrelser. Dette kan fikses ved å få algoritmen til å ta vare på valgene som ble gjort for å oppnå verdiene i den optimale løsnignen i tillegg til verdiene. Følgende algoritme gir for hver stangstørrelse j både maks inntjening r j og den optimale størrelsen på den første delen man kutter av, s j. 1 Extended Bottom Up Cut Rod ( p, n ) 2 l e t r [ 0.. n ] be a new a r r a y 3 r [ 0 ] = 0 4 f o r j = 1 t o n 5 q = i n f 6 f o r i = 1 t o j 7 i f q < p [ i ] + r [ j i ] 8 q = p [ i ] + r [ j i ] 9 s [ j ] = i 10 r [ j ] = q 11 r e t u r n r and s 4.3 Matrisemultiplikasjon Hvilken rekkefølge man velger å multiplisere sammen matriser kan ha stor innvirkning på hvor lang tid det tar. For å illustrere dette ser vi på de tre matrisene A 1,A 2,A 3, med dimensjoner hhv. 10 100,100 5,5 50. Hvis vi multipliserer i rekkefølgen ((A 1 A 2 )A 3 ), utfører vi 10 100 5 = 5000 skalarmultiplikasjoner for å beregne A 1 A 2, pluss ytterligere 10 5 50 = 2500 for å multiplisere resultatet av A 1 A 2 med A 3 totalt 7500 multiplikasjoner. Hvis vi i stedet velger (A 1 (A 2 A 3 )), må vi utføre 100 5 50+10 100 50 = 75000 multiplikasjoner. Å bruke den første rekkefølgen er altså ti ganger raskere. Antallet måter å plassere parantesene på er Ω(2 n ), så å sjekke alle er svært ineffektivt. Skal her bruke dynamisk programmering til å løse problemet. Innfører notasjonen A i.. j der i j, som er det evaluerte produktet A i A i+1 A j. Videre skrives dimmensjonene til matrise A i som p i p i 1. m[i, j] er det minste antallet skalarmultiplikasjoner som må til for å beregne matrisen A i.. j. Vi kan bruke optimal substruktur; det optimale antallet beregninger for A i.. j er lik antallet beregninger for A i..k pluss antallet beregner for A k+1.. j pluss antallet beregninger for å multipisere de sammen. Vi vet også at i k < l 1, så vi trenger bare å sjekke disse verdiene. Altså: { 0, hvis i = j m[i, j] = { } min i k< j m[i,k] + m[k + 1, j] + pi 1 p k p j hvis i < j der p i 1 angir antallet rader i den første matrisen, p k angir antallet kolonner i matrise k, og p j gir antallet kolonner i matrise j. 20

m[i, j] gir oss kostnadene til de optimale løsningene på subproblemene, men vi får ikke all informasjonen vi trenger for å finne løsningen. For å gjøre dette definerer vi s[i, j] til å være verdien av k der vi splitter A i.. j i en optimal parentetisering av subproblemet. Vi beregner her den optimale løsningen med en tabulær bunn-opp med Matrix-Chain-Order. 1 Matrix Chain Order ( p ) 2 n = p. l e n g t h 1 3 l e t m[ 1.. n, 1.. n ] and s [ 1.. n 1, 2.. n ] be new t a b l e s 4 f o r i = 1 t o n 5 m[ i, i ] = 0 6 f o r l = 2 t o n / / l i s t h e c u r r e n t c h a i n l e n g t h 7 f o r i = 1 t o n l + 1 8 j = i + l 1 9 m[ i, j ] = i n f 10 f o r k = i t o j 1 11 q = m[ i, k ] + m[ k + 1, j ] + p_i 1 p_k p _ j 12 i f q < m[ i, j ] 13 m[ i, j ] = q 14 s [ i, j ] = k 15 r e t u r n m and s Input for prosedyren er en sekvens p = p 0, p 1,... p n, der p.length = n + 1. En hjelpetabell m[1..n,1..n] til lagring av verdiene fra m[i, j] og en til hjelpetabell s[1..n 1,2..n] som inneholder en oversikt over hvilken verdi av k man oppno den optimale verdien for m[i, j]. Tabellen s brukes videre til å danne den optimale løsningen. Prosedyren fyller ut tabellen m på en måte som tilsvarer å finne optimal parentetisering av matrisekjeder av økende lengde. Tabell m bruker kun hoveddiagonalen og det øvre triangelet, tabell s bruker kun det øvre triagelet. Algoritmen beregner først m[i,i] for i = 1,2,...,n, altså minimumskostnadene for kjeder av lengde l = 1, som altså er null. Deretter beregnes m[i,i + 1] for i = 1,2,...,n, altså minimumskostnadene for alle kjder av lengder l = 2 i første iterasjon av andre for-loop. Andre gang den går igjennom den andre for-løkken berenges minimumskostnad for alle kjeder av lengde l = 3, osv. Man ser enkelt at kjøretiden er O ( n 3). 4.4 Longest common subsequence Merknad 1. Se 57 min inn i Lecture 15 (MIT). En LCS C for to strenger A og B er en streng av bestemt lengde, der alle elementene i C finnes i både A og B. Elementene i C trenger ikke være inntil hverandre i A og B, men de må opptre i samme rekkefølge i A og B som i C. Mer generelt er en subsekvens av en gitt sekvens den samme sekvensen der 0 eller flere elementer er utelatt. I LCS-problemet har vi gitt to sekvenser X = x 1,x 2,...,x m,y = y 1,y 1,...,y n, og vi vil finne en LCS av X og Y. Dette løses best med DP. 21

Definisjon 1 (Prefiks). De naturlige klassene med subproblemer tilsvarer par med prefikser av de to inputsekvensene. Gitt en sekvens X = x 1,x 2,...,x m er det i-ende prefikset til X for i = 0,1,...,m definert som X i = (x 1,x 2,...,x i ). For eksempel, hvis X = A,B,C,D,E,F,G, så er X 4 = A,B,C,D, og X 0 er en tom sekvens. Teorem 2. La X = x 1,x 2,...,x m og Y = y 1,y 2,...,y n være sekvenser og la Z = z 1,z 2,...,z k være en LCS av X og Y. Da har man at 1. Hvis x m = y n, så er z k = x m = y m, og Z k 1 er en LCS av X m 1 og Y n 1. 2. Hvis x m y, så impliserer z k x m at Z er et subset av X m 1 og Y. 3. Hvis x m y, så impliserer z k y n at Z er et subset av X og Y n 1. Teoremet sier oss at en LCS av to sekvenser inneholder LCS er for alle prefikser av de to sekvensene. LCS-problemet har derfor optimal substruktur. Lengden til en LCS av X i og Y j er gitt fra følgende rekursive formel: 0, hvis i = 1 eller j = 0 c[i, j] = c[i 1, j 1] + 1, hvis i, j > 0 og x i = y i max(c[i, j 1],c[i 1, j]), hvis i, j > 0 og x i y j Fordi problemet bare har Θ(mn) forskjellige subproblemer vil det her være mye å tjene på å bruke DP. Prosedyren LCS-Length tar to sekvenser X og Y som input. Den lagrer verdiene. for c[i, j] i en tabell c[0..m,0..n]. Tabellen inn fylles ut én rad av gangen, fra venstre mot høyre. I tillegg brukes en tabell b[1..m,1..n] for å hjelpe til med å bygge opp en optimal løsning. 1 m = X. l e n g t h 2 n = Y. l e n g t h 3 l e t b [ 1.. m, 1.. n ] and c [ 0.. m, 0.. n ] be new t a b l e s 4 f o r i = 1 t o m 5 c [ i, 0 ] = 0 6 f o r j = 0 t o n 7 c [ 0, j ] = 0 8 f o r i = 1 t o m 9 f o r j = 1 t o n 10 i f x _ i == y _ i 11 c [ i, j ] = c [ i 1, j 1] + 1 12 b [ i, j ] = "NW" 13 e l s e i f c [ i 1, j ] >= c [ i, j 1] 14 c [ i, j ] = c [ i 1, j ] 15 b [ i, j ] = "N" 16 e l s e c [ i, j ] = c [ i, j 1] 17 b [ i, j ] = "W" 18 r e t u r n c and b I koden brukes himmelretninger i stedet for piler. NW =, N =, W =. 22

4.4.1 Finne LCS for hånd vha. tabell Når man gjør dette for hånd kan man bruke en svart enkel fremgangsmåte. En beskrivelse følger: Vi vil finne en LCS av X = B,D,C,A,B,A og Y = A,B,C,B,D,A,B. For å gjøre dette setter man først opp følgende tabell j 0 1 2 3 4 5 6 i y j B D C A B A 0 x i 0 0 0 0 0 0 0 1 A 0 2 B 0 3 C 0 4 B 0 5 D 0 6 A 0 7 B 0 Når man videre fyller ut gjør man én av to ting i hver celle: Dersom x i = y j tar man tallet som ligger opp til venstre for cellen man ser på, adderer 1 og fører dette inn. Samtidig skriver man en inn i cellen. Dersom x i y i fører man inn det største av tallet rett til venstre for cellen og tallet rett over cellen. Samtidig fører man inn en dersom tallene er like eller tallet over er størst, eller en en dersom tallt til venstre er størst. Ferdig utfyllt tabell er vist under. j 0 1 2 3 4 5 6 i y j B D C A B A 0 x i 0 0 0 0 0 0 0 1 A 0 0 0 0 1 1 1 2 B 0 1 1 1 1 2 2 3 C 0 1 1 2 2 2 2 4 B 0 1 1 2 2 3 3 5 D 0 1 2 2 2 3 3 6 A 0 1 2 2 3 3 4 7 B 0 1 2 2 3 4 4 Tegnforklaring: Når man har en i celle b[i, j] betyr det at x i = y j er et element i LCS. Når man skal rekonstruere LCS fra tabellen starter man nede i høyre hjørne og følger pilene og skriver ned elementet hver gang man kommer til en. LCS blir her altså Z = B,C,B,A. 4.4.2 Redusering av plassbehov Mengden lagringsplass som trengs for algoritmen kan reduseres på to måter 1. Tabellen b er egentlig ikke nødvendig. LCS kan beregnes ut ifra c. Dette vil ikke redusere det asymptotiske plassbehovet. 23

2. Prosedyren trenger egentlig bare to rader i tabellen c av gangen; raden som beregnes og forige rad. Dette kan vi kun gjøre dersom vi bare trenger lengden til en LCS ikke dersom vi trenger selve sekvensen. Dette vil redusere det asymptotiske plassbehovet. 24

5 Datastrukturer 5.1 Stack (LIFO) Kjøretid for operasjoner Egnet for Oppslag (pop): O(1) Innsetting (push): O(1) Rekursjon, DFS 5.1.1 Prinsipp En stack opererer ved at det siste elementet som ble satt inn er det første som tas ut. 5.1.2 Operasjoner 1 Stack empty ( S ) 2 i f S. t o p == 0 3 r e t u r n True 4 e l s e r e t u r n F a l s e 1 Push ( S, x ) 2 S. t o p = S. t o p + 1 3 S [ S. t o p ] = x 1 Pop ( S ) 2 i f Stack empty ( S ) 3 e r r o r " u n d e r f l o w " 4 e l s e S. t o p = S. t o p 1 5 r e t u r n S [ S. t o p + 1] 5.2 Queue (FIFO) Kjøretid for operasjoner Egnet for Oppslag / fjerning (dequeue): O(1) Innsetting (enqueue): O(1) BFS 25

5.2.1 Prinsipp En queue opererer ved at det første elementet som settes inn er det første som tas ut. 5.2.2 Operasjoner 1 Enqueue (Q, x ) 2 Q[Q. t a i l ] = x 3 i f Q. t a i l == Q. l e n g t h 4 Q. t a i l = 1 5 e l s e Q. t a i l = Q. t a i l + 1 1 Dequeue (Q) 2 x = Q[Q. head ] 3 i f Q. head == Q. l e n g t h 4 Q. head = 1 5 e l s e Q. head = Q. head + 1 6 r e t u r n x 5.3 Linked list Kjøretid for operasjoner Egnet for Oppslag (list-search): Θ(n) Innsetting (list-insert): O(1) Sletting (list-delete): O(1), men Θ(n) dersom vi vil slette et element med en gitt nøkkel. Dynamiske sett, fordi det er lett å sette inn elementer ved å endre pekere. 5.3.1 Prinsipp I lenkede lister er objektene ordnet i en lineær rekke, der hvert objekt har en peker til neste objekt. En dobbelt lenket liste har pekere til både neste og forige element. En «sentinel» kan brukes for å forenkle koden til operasjonene. Den kan gjøre en vanlig dobbelt lenket liste om til en sirkulær, dobbelt linket liste. Sentinellen har en peker til første objekt (sentinel.next) og en peker til siste objekt (sentinel.prev). 5.3.2 Operasjoner 1 L i s t S e a r c h ( L, k ) 2 x = L. head 3 w h i l e x!= NIL and x. key!= k 26

4 x = x. n e x t 5 r e t u r n x 1 L i s t I n s e r t ( L, x ) 2 x. n e x t = L. head 3 i f L. head!= NIL 4 L. head. p rev = x 5 L. head = x 6 x. prev = NIL 1 L i s t D e l e t e ( L, x ) 2 i f x. p r ev!= NIL 3 x. prev. n e x t = x. n e x t 4 e l s e L. head = x. n e x t 5 i f x. n e x t!= NIL 6 x. n e x t. prev = x. prev 5.4 Representasjoner av rotede trær 5.4.1 Binære trær Et binært tre kan representeres ved én rot som har pekere til venstre og høyre barn; hvert av disse barna har så pekere videre til sitt høyre og venstre barn. I tillegg har hver node forelderen. Hver node x kan altså ha attributtene x.left, x.right og x.parent. Rota har x.parent = NIL, mens en node som mangler venstre barn har x.left = NIL. Rota til hele treet pekes til med atributten T.root. Hvis T.root = NIL så er treet tomt. 5.4.2 n-ære trær For trær med flere enn to barn, men fortsatt rimelig få, kan man bruke f.eks. x.child1, x.child2 osv. for å representere barna. Dette fungerer ikke lenger godt når antallet barn er svært høyt (men fortsatt endelig), ettersom det vil bruke svært mye minne. Man kan da sette det opp slik at hver node x har en peker til sitt venstre barn, x.left-child, som har en peker videre til sitt høyre søsken, x.right-sibling. Hvert barn har fortsatt en peker tilbake til sin forelder. 5.5 Heap (max) Kjøretid for operasjoner Egnet for Bygging (build-max-heap): O(n) «Vedlikehold» (max-heapify): O(lgn) Heapsort, prioritetskø 27

5.5.1 Prinsipp En (binær) heap kan ses på som et nesten komplett binærtre. Det må være fullt på alle nivåer untatt muligens det nederste, som fylles fra venstre mot høyre. Arrayet A som representerer treet har to egenskaper; A.length som gir antallet elementer i A; og A.heap-size som angir hvor mange elementer fra heapen som er lagret inne i A. Altså: selv om A[1...A.length] inneholder tall, så er det kun A[1...A.heap-size] som inneholder elementer fra heapen. Rota til treet er A[1] og forelder og barn til noden i A[i] kan lett beregnes. Det finnes to typer heaper: max-heap og min-heap, som tilfredsstiller hver sin heap egenskap. For max-heap er det A[parent (i)] A[i]; det største elementet er altså i rote. En min-heap er organisert motsatt, altså med det minste elementet i rota og A[parent (i)] A[i]. Høyden til en heap med n elementer er Θ(lgn), og operasjonene til en heap kjører på tid proporsjonal med høyden, altså O(lgn) tid. 5.5.1.1 Bygge en heap En max-heap kan bygges fra bunnen og opp ved å kjøre Max-Heapify for å konvertere et array A[1..n], der n = A.length. Elementene i subarraye A[( n/2 + 1)..n] er løvnoder, så hver av de er en 1-element heap til å begynne med. Build-Max-Heap går gjennom alle de interne nodene og kjører Max-Heapify på hver av dem. 28

5.5.1.2 Vedlikeholde en heap Antar at binærtreene som går ut fra Left(i) og Right(i) er max-heaps, men at A[i] kan være mindre enn barna sine. Max-Heapify flytter verdien i A[i] nedover til subtreet med rot i index i oppfyller max-heap egenskapen. I hvert skritt byttes verdien med index i med det med største av barna sine. 5.5.2 Operasjoner 1 P a r e n t ( i ) 2 r e t u r n f l o o r ( i / 2 ) 1 L e f t ( i ) 2 r e t u r n 2 i 1 R i g h t ( i ) 2 r e t u r n 2 i + 1 1 Max Heapify (A, i ) 2 l = L e f t ( i ) 3 r = R i g h t ( i ) 4 i f l <= A. heap s i z e and A[ l ] > A[ i ] 5 l a r g e s t = l 6 e l s e l a r g e s t = i 7 i f r <= A. heap s i z e and A[ r ] > A[ l a r g e s t ] 8 l a r g e s t = r 9 i f l a r g e s t!= i 10 exchange A[ i ] with A[ l a r g e s t ] 11 Max Heapify (A, l a r g e s t ) 1 Build Max Heap (A) 2 A. heap s i z e = A. l e n g t h 3 f o r i = f l o o r (A. l e n g t h / 2) downto 1 4 Max Heapify (A, i ) 29

5.6 Prioritetskø (max) Kjøretid for operasjoner Egnet for Bygging (build-max-heap): O(n) «Vedlikehold» (max-heapify): O(lgn) Oppslag (heap-maximum): O(1) Uthenting/sletting (heap-extract-max): O(lgn) Øk verdi (heap-increase-key): O(lgn) Innsetting (max-heap-insert): O(lgn) Kjøreplan for jobber på en datamaskin 5.6.1 Prinsipp En prioritetskø bruker en heap til å opprettholde en kø for et sett med S elementer, der det neste elementet som skal behandles ligger i første posisjon.. Når man skal hente ut elementet med høyest prioritet kjøres Heap-Maximum, og når man er ferdig med det kjøres Heap-Extract-Max. Nye jobber kan settes inn med Max-heap-insert. Dersom man ønsker å øke verdien til et element med index i gjøres dette med Heap-increase-key(i); dette kan bryte med max-heap-egenskapen. Derfor traverserer den stien mot rota for å finne en passende plass for å sette den nye verdien inn (insertion sort). Mens den traverserer ser den hele tiden på forelderen, og bytter plass med den dersom den (forelderen) har lavere verdi og terminerer dersom den har høyere verdi. Hvis man skal sette inn en verdi brukes Max-Heap-Insert. Da utvides først max-heapen ved å legge til en løvnode med verdien, deretter kalles Heap-Increase-Key for å sette den nye noden til korrekt verdi og flytte den til riktig plass. 5.6.2 Operasjoner 1 Heap Maximum (A) 2 r e t u r n A[ 1 ] 1 Heap E x t r a c t Max (A) 2 i f A. heap s i z e < 1 3 e r r o r " heap u n d e r f l o w " 4 max = A[ 1 ] 5 A[ 1 ] = A[A. heap s i z e ] 6 A. heap s i z e = A. heap s i z e 1 7 Max Heapify (A, 1 ) 8 r e t u r n max 1 Heap I n c r e a s e Key (A, i, key ) 2 i f key < A[ i ] 3 e r r o r " new key i s s m a l l e r t h a n c u r r e n t key " 30

4 A[ i ] = key 5 w h i l e i > 1 and A[ P a r e n t ( i ) ] < A[ i ] 6 exchange A[ i ] with A[ P a r e n t ( i ) ] 7 i = P a r e n t ( i ) 1 Max Heap I n s e r t (A, key ) 2 A. heap s i z e = A. heap s i z e + 1 3 A[A. heap s i z e ] = i n f 4 Heap I n c r e a s e Key (A, A. heap s i z e, key ) 5.7 Binære søketrær Kjøretid for operasjoner Egnet for Generelt proporsjonalt med høyden på treet, O(h); Θ(lgn) for et fullt binært tre, men WC er O(n) fordi man kan ende med en kjede med n noder. AC er O(lgn). Traversersering (inorder-tree-walk): Θ(n) Ordbok, prioritetskø 5.7.1 Prinsipp Et binært søketre kan representeres som en lenket datastruktur der hver node er et objekt som inneholder key, left, right, og parent. Dersom en node mangler et barn eller forelder er verdien der NIL. Rota har NIL som parent. Nøklene i et binært søketre tilfredsstiller egenskapen for binære søketrær: La x være en node i det binære søketreet. Hvis y er en node i det venstre subtreet til x, så er y.key x.key. Hvis y er en node i det høyre subtreet til x, så er y.key x.key. 5.7.1.1 Traversering Egenskapen over gjør at vi kan printe alle nøklene i et binært tre i sortert rekkefølge med en enkel rekursiv algoritme, Inorder-Tree-Walk. Algoritmen kalles det fordi den printer ut rota til et subtre mellom verdiene i venstre og høyre subtre. 5.7.1.2 Søking Søking utføres med Iterative-Tree-Search og tar inn en peker x til en rot og en nøkkel k som det returneres en peker til dersom den eksisterer. Kjøretiden er proposrjonal med høyden på treet, altså O(h). 31

5.7.1.3 Minimum og maksimum Minumum kan finnes ved å følge pekeren til venstre barn fra rota til vi møter NIL. Tree-Minimum gir en peker til min-elementet i undertreet med rot i noden x. Dersom rota ikke har et venstre barn garanterer egenskapen for binære søketrær at x er det minste elementet i subtreet med rot i x. Tilsvarende gjelder for maksumum og Tree-Maximum. Begge har kjøretid O(h). 5.7.1.4 Innsetting For å sette inn elementer brukes Tree-Insert. Algoritmen tar en node z med z.key = v, z.le ft = z.right = NIL. Treet T modifiseres sammen med barn-atributtene til z, slik at den nye noden passer inn (som en løvnode). Prosedyren kjører på O(h) tid. 5.7.1.5 Sletting Når man skal slette en node z kan det oppstå tre mulige tilfeller: 1. Hvis z ikke har noen barn kan vi bare fjerne noden. 2. Hvis z kun har ett barn tar barnet z sin plass. 3. Hvis z har to barn finner vi etterkommeren y til z (noden med minst nøkkel større enn z.key), den må det minste elementet i høyre subtre. Etterkommeren y tar plassen til z. Dette tilfellet er vanskelig fordi det har betydning om y er z sitt høyre barn. a) Dersom y er z sitt høyre barn erstatter y z. b) Ellers byttes y med sitt eget høyre barn, deretter byttes z med y. For å flytte på subtrær brukes subrutinen Transplant. Den bytter ut ett subtre med et annet subtre. Når Tranplant bytter ut subtreet med node u som rot med subtreet som har node v som rot, så blir node u sin forelder satt som forelderen til node v, og v blir satt som barnet til u sin forelder. Tree-Delete kjører på tiden O(h). Merknad 2. Etterkommere og forgjengere er utelatt. 5.7.2 Operasjoner 1 I n o r d e r Tree Walk ( x ) 2 i f x!= NIL 3 I n o r d e r Tree Walk ( x. l e f t ) 4 p r i n t ( x. key ) 5 I n o r d e r Tree Walk ( x. r i g h t ) 1 I t e r a t i v e Tree S e a r c h ( x, k ) 2 w h i l e x!= NIL or k!= x. key 3 i f k < x. key 32

4 x = x. l e f t 5 e l s e x = x. r i g h t 6 r e t u r n x 1 Tree Minimum ( x ) 2 w h i l e x. l e f t!= NIL 3 x = x. l e f t 4 r e t u r n x 1 Tree Maximum ( x ) 2 w h i l e x. r i g h t!= NIL 3 x = x. r i g h t 4 r e t u r n x 1 Tree I n s e r t ( T, z ) 2 y = NIL 3 x = T. r o o t 4 w h i l e x!= NIL 5 y = x 6 i f z. key < x. key 7 x = x. l e f t 8 e l s e x = x. r i g h t 9 z. p = y 10 i f y == NIL 11 T. r o o t = z 12 e l s e i f z. key < y. key 13 y. l e f t = z 14 e l s e y. r i g h t = z 1 T r a n s p l a n t ( T, u, v ) 2 i f u. p == NIL 3 T. r o o t = v 4 e l s e i f u == u. p. l e f t 5 u. p. l e f t = v 6 e l s e u. p. r i g h t = v 7 i f v!= NIL 8 v. p = u. p 1 Tree D e l e t e ( T, z ) 2 i f z. l e f t == NIL 3 T r a n s p l a n t ( T, z, z. r i g h t ) 4 e l s e i f z. r i g h t == NIL 5 T r a n s p l a n t ( T, z, z. l e f t ) 33

6 e l s e y = Tree Minumum ( z. r i g h t ) 7 i f y. p!= z 8 T r a n s p l a n t ( T, y, y. r i g h t ) 9 y. r i g h t = z. r i g h t 10 y. r i g h t. p = y 11 T r a n s p l a n t ( T, z, y ) 12 y. l e f t = z. l e f t 13 y. l e f t. p = y 5.7.3 Optimale binære søketrær Et optimalt binært søketre (OBST) minimerer antallet noder vi må besøke før vi finner elementet vi leter etter. Dette gjøres ved å plassere elementene det er høyest sannsynlighet for at vi søker på nærmere rota. Dynamisk programmering brukes for å kostruere et OBST. For å lage et (OBST) trenger vi en sekvens K = k 1,k 2,...,k n, der hver hver k i har sannsynlighet p i for å bli søkt på. Noen søk kan være på verdier / K, derfor har vi n + 1 blindnøkler d 0,d 1...d n som representerer verdeir som ikke finnes i K. Hver blindnøkkel d i har også en sannsynlighet q i for at et søk vil lede til den. Man ser fra figuren over at hver nøkkel k i er interne noder mens blindnøklene d i er løvnoder. Hvert søk ender enten ved en nøkkel k i (vellykket) eller ved en blindnøkkel (ikke vellykket). Vi har altså at n i=1 p i + n i=0 q i = 1 Vi sier at kostnaden E til et søk er lik antallet noder som blir undersøkt, i.e. dybden til noden søket stopper ved, pluss 1. Vi vil her altså konstruere et tre som minimerer forventet E. Tabellen under viser sannsynlighetene for hver av nodene i treet a) over. 34

Node Dybde Sannsynlighet Bidrag ((dybde i + 1) p i ) k 1 1 0.15 0.30 k 2 0 0.10 0.10 k 3 2 0.05 0.15 k 4 1 0.10 0.20 k 5 2 0.20 0.60 d 0 2 0.05 0.15 d 1 2 0.10 0.30 d 2 3 0.05 0.20 d 3 3 0.05 0.20 d 4 3 0.05 0.20 d 5 3 0.10 0.40 Total 2.80 Vi ser at treet i b) ville hatt forventet kostnad 2.75 mot 2.80 for a). Vi ser altså at, avhengig av sannsynlighetene til nøklene, så er ikke nødvendigvis OBST et tre med minimal høyde, og vi kan ikke alltid plassere noden med høyest sannsynlighet øverst (med k 5 som rot ville alle OBST fått forventet kostnad 2.80). 5.7.3.1 Optimal substruktur Gitt nøklene k i,...,k j. Én av disse nøklene k t der i r j er rota til et optimalt subtre som inneholder resten av nøklene i k i,...,k j. Det vestre subtreet til k r inneholder nøklene k i,...,k r 1 og blindnøklene d i 1,...,d r 1, og det høyre subtreet inneholder nøklene k r+1,...,k j og blindnøklene k r,...,k j. Så lenge vi undersøker alle mulig subtre-rotkandidater k r der i r j og bestemmer alle optimale binære søketrær som inneholder k i,...,k r 1 og k r+1,...,k j, så vil vi garantert finne OBST. Tomme subtrær Anta at vi i et subtre med nøklene k i,...,k j velger k i som rot. I.h.t. argumentet over, så vil det venstre subtreet i dette tilfellet inneholde nøklene k i,...,k i 1. Dette tolkes som en sekvens som ikke inneholder nøkler, men vi sier fortsatt at det inneholder én blindnøkkel. Tilsvarende gjelder dersom vi velger k j som rot; høyre subtre vil da inneholde k j+1,...,k j, altså kun én blindnøkkel. 5.7.3.2 Rekursiv løsning Definerer e[i, j] som den forventede kostnaden for søk i et tre som inneholder k i,...,k j. Vi vil til slutt beregne e[1,n]. Det letteste tilfellet oppstår dersom j = i 1 (altså et tomt tre); da har vi kun blindnøkkelen d i 1 og forvented søkekostnad e[i,i 1] = q i 1. Når j i må vi velge en rot k r fra intervallet k i,...,k j og lage optimale søketrær for venstre og høyre fot. Den forventede kostnaden til et subtre er gitt fra w(i, j) = j i=1 p i + j q l l=i 1 35

Den rekursive formelen for søkekostnaden til et optimalt binært søketre med rot i k r er { q i 1, hvis j = i 1 e[i, j] = min i r j {e[i,r 1] + e[r + 1, j] + w(i, j)}, hvis i j Vi definerer root [i, j] for 1 i j n til å være indeksen r slik at k r er rota til OBST med nøklene k i,...,k j. 5.7.3.3 Beregning av forventet søkekostnad til et optimalt binært søketre De beregnede verdiene for e[i, j] lagres i en tabell e[1..n + 1,0..n]. Den første indeksen må gå til n + 1 og ikke n fordi vi må beregne e[n + 1,n] for å kunne ha et subtre som kun inneholder blindnøkkelen d n. Tilsvarende starter den andre indeksen på 0 for å kunne få d 0 fra e[1,0]. I tillegg brueks en tabell root [i, j] til å lagre røttene til subtreene som inneholder nøklene k i,...,k j. Vi bruker også en tabell w[1..n + 1,0..n] til å lagre de beregnede verdiene for w(i, j). For basetilfellet beregner vi w[i,i 1] = q i 1 for 1 i n + 1. For j i beregner vi w[i, j] = w[i, j 1] + p j + q j Følgende algoritme tar sannsynlighetene p i og q i og størrelsen n som input, og returnerer tabellene e og root. 1 Optimal BST ( p, q, n ) 2 l e t e [ 1.. n + 1, 0.. n ], w [ 1.. n + 1, 0.. n ], 3 and r o o t [ 1.. n, 1.. n ] be new t a b l e s 4 f o r i = 1 t o n + 1 5 e [ i, i 1] = q_i 1 6 w[ i, i 1] = q_i 1 7 f o r l = 1 t o n 8 f o r i = 1 t o n l + 1 9 j = i + l 1 10 e [ i, j ] = i n f 11 w[ i, j ] = w[ i, j 1] + p _ j + q _ j 12 f o r r = i t o j 13 t = e [ i, r 1] + e [ r + 1, j ] + w[ i, j ] 14 i f t < e [ i, j ] 15 e [ i, j ] = t 16 r o o t [ i, j ] = r 17 r e t u r n e and r o o t 36

6 Kjøretid 6.1 Kjøretid fra rekurrensrte Skal analysere kjøretiden til følgende algoritme, der operasjonen gjornoe() har kjøretide Θ(1). 1 d e f f u n k s j o n 3 ( n ) : 2 gjornoe ( ) 3 i f n > 0 : 4 f u n k s j o n 3 ( n 1) 5 f u n k s j o n 3 ( n 1) 6 f u n k s j o n 3 ( n 1) En illustrasjon med 3 som input er vist i figuren under. Man ser at antallet løvnoder m er gitt fra m = n 3. Med tre som input gir det m = 3 3 = 27. Høyden h er gitt fra h = log 3 m. Med tre som input gir det h = log 3 27 = 3. Kjøretiden er gitt fra løvnoder multiplisert med høyden, altså O(m h) = O(mlog 3 m) = O ( n 3 log 3 n 3) = O ( n 3 3log 3 n ) = O ( n 3 log 3 n ) 37

7 Mastermetoden Mastermetoden (teoremet) kan kun brukes på rekurenser på formen ( n T (n) = at + f (n) (7.1) b) der størrelsene kan tolkes på følgende måte Antall barn fra hver node: a Størrelse per barn : n/b Høyde : log b n = Θ(lgn) Antall løvnoder: a log b n = n log b a Arbeid utført av rota : f (n) og hver løvnode utfører en konstant mengde arbeid. Hva som dominerer totalen avhenger av forholdet mellom f (n) og antallet løvnoder. Metoden har tre forskjellige tilfeller. Tilfelle 1: Løvnodene dominerer Skjer når f (n) = O ( n log b a ε) (7.2) Eller sagt på en annen måte: Gitt rotarbeid f (n) = O(n x ) Gitt antall løvnoder = n log b a = n y Hvis x < y vil løvnodene dominere. Kostnaden når løvnodene dominerer blir Θ ( n log b a) (7.3) Tilfelle 2: Dødt løp Skjer når f (n) = Θ ( n log b a) (7.4) Dette betyr at det utføres samme mengde arbeid i hvert nivå. Kostnaden når det er dødt løp blir Θ(n log b a lgn) (7.5) 38

Tilfelle 3: Rota dominerer Skjer når f (n) = Ω ( n log b a+ε) (7.6) Eller sagt på en annen måte: Gitt rotarbeid f (n) = O(n x ) Gitt antall løvnoder = n log b a = n y Hvis y < x vil løvnodene dominere. Kostnaden når rota dominerer blir Θ( f (n)) (7.7) Merk: I dette tilfellet må f (n) være regulær. Dvs. at det må finnes en c < 1 slik at a f (n/b) c f (n) for store nok vedier av n (dette vil intuitivt si at f krymper nedover i treet). Man trenger ikke sjekke dette hvis f er et polynom, altså n k. 39

8 NP-komplette problemer Decision problemer kan deles opp i kompleksitetsgrupper: NP En ikke-deterministisk tilstandsautomat kan på polynomisk tid sjekke om svaret ja er korrekt. P Man har funnet algoritmer som løser problemet på polynomisk tid. NPC Man kjenner ikke til noen algoritme som kan løse problemet effektivt, men man kan sjekke om et svar er korrekt på polynomisk tid. NPC overlapper i tillegg med NP-harde problemer. Dette er problemer som er minst like vanskelige som NPC-problemer, men de er optimaliseringsproblmer, ikke decision problemer. Det er ukjent om egentlig P = NP (altså at alle problemer kan løses på polynomisk tid), dermed ville NPC = NP = P. Men man tror at P NP. En illustrasjon av gruppene er vist under. 40