Design, verifikation og analyse

Slides:



Advertisements
Lignende præsentationer
1 Problemkompleksitet 2 Problemers kompleksitet En ineffektiv algoritme: køretiden vokser eksponentielt med input- størrelsen Et problem, der ikke kan.
Advertisements

Logaritmefunktioner (skal bruges til at løse ligninger)
Sprog/billeder på Internettet
Udsagn (propositioner)
Algoritmer og Datastrukturer 1 Gerth Stølting Brodal.
Algoritmer og Datastrukturer 2 Del-og-kombiner [CLRS, kapitel , , 28
1 Rekursion og algoritmedesign. 2 Rekursion Rekursiv definition af X: X defineres i termer af sig selv. Rekursion er nyttig, når en generel version af.
Induktionsbevis AM INDUKTION – generalisering ud fra specialtilfælde Eks. I Fremskrivningsformlen ved en fast vækstrate r pr. trin. Startværdi =
1 Effektiv forrentning Kjeld Tyllesen PEØ, CBS Erhvervsøkonomi / Managerial Economics Kjeld Tyllesen, PEØ, CBS.
Anden information Bettina Dahl Søndergaard Lektor Hvad er svært ved beviser for gymnasieelever - og kan vi gøre noget ved det? Fredag den 18. marts 2011.
Algoritmer og Datastrukturer 1 Gerth Stølting Brodal.
Algoritmer og Datastrukturer 2 Gerth Stølting Brodal.
1 Nordjyllands Erhvervakademi Lektion 6 Opsamling på opgaver Mere om rekursion: –Del&Hersk –Sortering –Kompleksitet (effektivitet – ”Store O”) Abstrakte.
CAS i matematikundervisningen Middelfart
Validering af data (Access, del 7)
Økonometri 1: Dummy variable
1 Bevisteknikker. 2 Bevisteknikker (relevant både ved design og verifikation) Teorem: Der findes uendeligt mange primtal Bevis: Antag at der findes et.
1 UNION-FIND. 2 inddata: en følge af heltalspar (p, q); betydning: p er “forbundet med” q uddata: intet, hvis p og q er forbundet, ellers (p, q) Eksempel.
GP 10, 7/ Grundlæggende programmering Efterår 2001 Forelæsning 10 onsdag 7/ kl. 9:15 – 12:00.
Kvalitet i almindelighed og i relation til softwareudvikling.
1 Logaritmer Logaritmer optr æ der hyppigt i forbindelse med algoritmeanalyse. Definition: For ethvert B > 0 og N > 0 g æ lder, at log B N = X, hvis B.
FEN Rekursion og induktion1 Induktion og (især) rekursion Mange begreber defineres ud fra en basis og så en gentagen anvendelse af et antal regler.
1 Datalogi C Datastrukturer og algoritmer ved Keld Helsgaun.
FEN Prædikater/Seminar 11 Prædikatslogik eller Kvantificerede udtryk Prædikater udvider propositionslogikken på to måder: –Vi tillader variable.
Algoritmer og Datastrukturer 1 Gerth Stølting Brodal Aarhus Universitet.
2. gradsfunktioner.
1 Sortering I elementære metoder. 2 Plan Terminologi Elementære metoder til sortering -sortering ved udvælgelse -sortering ved indsættelse -Shellsort.
Anvendelser I Leg og spil.
Afledet funktion Her har jeg tegnet f(x) og f’(x)=g(x)
Algoritmer og Datastrukturer 1 Gerth Stølting Brodal Aarhus Universitet.
1 Vi ser nu på en general graf Men antager at alle afstande er heltallige (Det er ikke så restriktivt) Algoritmen leder efter den mindst mulige dækningsdistance.
Algoritmer og Datastrukturer 2 Del-og-kombiner [CLRS, kapitel 2.3, , problem 30.1.c] Gerth Stølting Brodal.
1 Algoritmik ved Keld Helsgaun. 2 Plan Algoritmebegrebet et simpelt eksempel Et eksempel på algoritmedesign.
Delprøve i kurset ”Calculus og indledende lineær algebra” 29. oktober 2014 Delprøve i M2CAL2 29. oktober A. B. A. 1x1 + 1x4 + 1x(-1) = 4 B. 1x4 =
Delprøve (reduceret) i kurset ”Calculus og indledende lineær algebra” den 7. maj 2014 Delprøve i M2CAL2, Betragt matricen til højre. Hvilket.
1 Algoritmik ved Keld Helsgaun. 2 Plan Hvad er algoritmik? Et eksempel Kursusbeskrivelse formål, indhold og form.
Matematik A på hhx v/fagkonsulent Marit Hvalsøe Schou.
Algoritmer og Datastrukturer 1 Gerth Stølting Brodal.
1 Design, analyse og verifikation. 2 Algoritmebegrebet Design Bevisteknikker Design ved hjælp at matematisk induktion Analyse O-notation Logaritmer Binær.
Fundamentale datastrukturer
1 Design, analyse og verifikation. 2 Design Bevisteknikker Design ved hjælp at matematisk induktion Analyse O-notation Logaritmer Binær søgning Verifikation.
Optimeringsteori Disposition: A. Et marked Den generelle formulering
FEN KbP/seminar1: ProgUdsagn1 Kontraktbaseret programmering Seminar 1 Programudsagn og programbeviser.
Optimeringsteori Disposition: A. Et marked Den generelle formulering
Eksempler på en beregningsprocess… Puslespil ved ombytninger Maximum delsum.
Kvantitative metoder 2: Den multiple regressionsmodel1 Kvantitative metoder 2 Den multiple regressionsmodel 26. februar 2007.
Økonometri 1: Den simple regressionsmodel Økonometri 1 Den simple regressionsmodel 14. september 2004.
Semantik, model teori Et (formalt) sprog har ingen mening indtil man interpreterer dets forskellige (korrekte) udtryksformer (vff’s) mhp. en bestemt situation.
Grundlæggende programmering Forår 2002
Algoritmer og Datastrukturer 2 Del-og-kombiner [CLRS, kapitel , , 28
Økonometri 1: Den multiple regressionsmodel1 Økonometri 1 Den multiple regressionsmodel 15. februar 2006.
Økonometri 1: Den simple regressionsmodel Økonometri 1 Den simple regressionsmodel 13. februar 2003.
Algoritmer og Datastrukturer 1 Gerth Stølting Brodal.
Algoritmer og Datastrukturer 1 Gerth Stølting Brodal Analyseværktøjer [CLRS, 1-3.1]
Økonometri 1: Den simple regressionsmodel Økonometri 1 Den simple regressionsmodel 14. september 2005.
REGULARITET & AUTOMATER Indhold
Algoritmer og Datastrukturer 2 Gerth Stølting Brodal.
Økonometri 1: Den multiple regressionsmodel1 Økonometri 1 Den multiple regressionsmodel 17. september 2004.
Algoritmer og Datastrukturer 1 Gerth Stølting Brodal Aarhus Universitet.
Konference om mundtlige prøver PRØV! Et program til de mundtlige prøver.
Kvadratisk optimering Lavet af Mikkel Iversen og Mathias Møllemus Svendsen HH3-ØA.
Den multiple regressionsmodel 21. september 2005
Algoritmer og Datastrukturer 2
Algoritmer og Datastrukturer 2
Eksponentiel notation
Præsentationens transcript:

Design, verifikation og analyse

Plan Design Metoder til problemløsning Matematisk induktion ------------------------------------------------------------ Del-og-hersk Rekursion ------------------------------------------------------------- Verifikation Grundregler Et eksempel Analyse O-notation

Metoder til algoritmedesign Algoritmedesign er en kreativ proces. Der findes ingen generel mekanisk metode (algoritme) til design af en algoritme for et forelagt problem. Derimod findes en række metoder, eller rettere “regler for tænkning”, som ofte fører til korrekte og effektive algoritmer. Nogle af disse metoder er baseret på matematisk bevisførelse. Dermed “indbygges” korrekthed i algoritmerne. Andre har mere karakter af gode råd.

Problemløsningsteknik (“How to solve it”, Pólya - 1944) 1. Forstå problemet. Hvad er det, der søges? Hvad er givet? Tegn en figur. Indfør passende betegnelser. 2. Læg en plan Har du set dette problem eller tilsvarende tidligere? Kan problemet omformuleres? Kan du løse en del af problemet? 3. Gennemfør planen Kontroller hvert trin. Kan du bevise hvert trin? 4. Se tilbage Kan du kontrollere resultatet? Kan du opnå resultatet på anden måde? Kan du anvende metoden på et andet problem?

Berømthedsproblemet 1. Forstå problemet. Blandt N personer defineres en berømthed som en person, som alle kender, men som ikke kender nogen andre. Identificer berømtheden, hvis denne eksisterer, ved kun at stille spørgsmål af formen: “Undskyld, kender du den person derhenne?”. Antallet af spørgsmål ønskes minimeret. [ Alle personer, også berømtheden, antages at svare korrekt ]. 1. Forstå problemet. Hvad er det, der søges? En person blandt N personer, som opfylder betingelsen for at være en berømthed. Hvis der ikke findes nogen berømthed, skal dette meddeles. Antag at personerne nummereres fra 1 til N. Så er det søgte: nummeret på en person. Fravær af berømthed kan angives med et ikke-eksisterende personnummer, f.eks. 0. Hvad er givet? Et 2-dimensionalt boolean array (logisk matrix), kender, der for hvert ordnet par af personer (a,b), 1 ≤ a ≤ N, 1 ≤ b ≤ N, angiver om a kender b (kender[a][b] er true, hvis og kun hvis a kender b).

Tegn en figur. kender • • En berømthed svarer til en knude, hvor antallet af udgående pile er 0, og antallet af indgående pile er N-1. En berømthed er karakteriseret ved at alle elementer i den tilsvarende række i kender er false, mens alle elementer i den tilsvarende søjle er true (idet der ses bort fra diago- nalelementerne). - true true true false true false - true false false true false false - false false false false false true - true false false false true false - true true false true false false - Indfør passende betegnelser. Er gjort.

2. Læg en plan Har du set dette problem eller tilsvarende tidligere? Nej, men jeg kan lave en meget simpel algoritme til løsning af problemet. Undersøg for hver person, om kender opfylder betingelsen for, at personen er en berømthed; berømthed = 0; for (a = 1; a <= N; a++) { falseAntal = trueAntal = 0; for (b = 1; b <= N; b++) if (b != a && !kender[a][b]) falseAntal++; if (b != a && kender[b][a]) trueAntal++; if (falseAntal == N-1 && trueAntal == N-1) berømthed = a; } Simpel, men ikke særlig effektiv. Berømtheden findes ved at stille N*2*(N-1) spørgsmål. Kan denne løsning forbedres?

for (a = 1; a <= N; a++) { for (b = 1; b <= N; b++) Ja, ved at undgå tællerne falseAntal og trueAntal og forlade løkkerne, så snart det er muligt. berømthed = 0; spørgen: for (a = 1; a <= N; a++) { for (b = 1; b <= N; b++) if (b != a && (kender[a][b] || !kender[b][a])) continue spørgen; berømthed = a; break; } Hvis der eksisterer en berømthed, stilles gennemsnitligt færre spørgsmål, men i værste tilfælde stilles N*2*(N-1) spørgsmål. Kan vi gøre det bedre?

Ja. Vi kan eliminere halvdelen af spørgsmålene Ja. Vi kan eliminere halvdelen af spørgsmålene. Hvis b > a, så vil spørgsmålet “!kender[b][a]?” blive besvaret senere, nemlig i form af svaret på spørgsmålet “kender[b][a]?”. Vi når frem til følgende algoritme. berømthed = 1; spørgen: for (a = 2; a <= N; a++) { forrige_berømthed = berømthed; if (berømthed != 0 && (kender[berømthed][a] || !kender[a][berømthed])) berømthed = 0; if (berømthed == 0) { for (b = 1; b < a; b++) if (b != forrige_berømthed && (kender[a][b] || !kender[b][a])) continue spørgen; berømthed = a; } I værste tilfælde stilles N*(N-1) spørgsmål. [ = ] Mon vi kan løse problemet mere effektivt?

Kan problemet omformuleres? Det kan være svært at identificere en berømthed. Det er måske lettere at identificere personer, som ikke er berømtheder (i det mindste er de i flertal). Problemet omformuleres: Find N-1 personer, der ikke er en berømthed, og påvis, at den tiloversblevne er en berømthed. Hvis kender[a][b], a ≠ b, kan det udelukkes, at a er en berømthed. I modsat fald kan det udelukkes, at b er en berømthed. En af dem kan elimineres med ét spørgsmål. Hvert spørgsmål reducerer problemet til et tilsvarende, men med en person færre. for (a = 1, b = 2, n = 3; n <= N+1; n++) if (kender[a][b]) a = n; else b = n; if (a == N+1) a = b; for (b = 1; b <= N; b++) if (b != a && (kender[a][b] || !kender[b][a])) break; berømthed = b > N ? a : 0; Antallet af spørgsmål er nu reduceret til 3*(N-1) i værste tilfælde.

3. Gennemfør planen Kontroller hvert trin. Kan du bevise hvert trin? Planen er gennemført. Et formelt bevis for algoritmens korrekthed ville være ideelt, men i det mindste er der argumenteret for dens korrekthed. 4. Se tilbage Kan du kontrollere resultatet? Ja, f.eks. empirisk ved systematisk programafprøvning. Et spørgsmål er, om effektiviteten er den bedst opnåelige. (Faktisk kan der på snedig vis spares log2N spørgsmål.)

Bevisteknikker (relevant både ved design og verifikation) Bevisførelse ved modstrid (indirekte bevis). Antag at det givne teorem er falsk. Konkluder at dette vil føre til en modstrid. Teorem. Der findes uendeligt mange primtal. Bevis: Antag at der findes et endeligt antal primtal, p1, p2 , ..., pk. Betragt nu tallet N = p1 . p2 ... pk+1. N er større end pk. Men ingen af de kendte primtal går op i N (resten ved division er 1). Så N må være et primtal. Vi har dermed en modstrid, hvilket beviser sætningen.

Matematisk induktion Lad T være et teorem, der skal bevises, og lad T være udtrykt i termer af heltalsparameteren n. Teoremet T gælder da for enhver værdi af n ≥ c, hvor c er en konstant, hvis følgende to betingelser er opfyldt: 1. Basistilfældet: T gælder for n = c, og 2. Induktionsskridtet: Hvis T gælder for n-1, så gælder T for n. Antagelsen i induktionsskridtet kaldes induktionshypotesen.

Eksempler på simpel induktion Teorem: Summen S(n) af de første n naturlige tal er n(n+1)/2. Bevis: (1) Basistilfældet. For n = 1 er S(1) =1, hvilket stemmer med formlen. (2) Induktionsskridtet. Antag at sætningen gælder for n-1, dvs. S(n-1) = (n-1)n/2. S(n) = S(n-1) + n = (n-1)n/2 + n = n(n+1)/2. Dermed gælder sætningen også for n.

Teorem: Ethvert beløb ≥ 4 kroner kan veksles i et antal 2-kroner og et antal 5-kroner. Bevis: (1) Basistilfældet. 4 kroner kan veksles ved hjælp af to 2-kroner. (2) Induktionsskridtet. Antag at n-1 kroner kan veksles. Vi kan vise, at denne veksling kan benyttes som udgangspunkt til at veksle n kroner. Enten indeholder vekslingen en 5-krone, eller også gør den ikke. I første tilfælde erstattes 5-kronen med tre 2-kroner. I andet tilfælde erstattes to 2-kroner med en 5-krone.

Stærk induktion Teoremet T gælder for enhver værdi af n ≥ c, hvor c er en konstant, hvis følgende to betingelser er opfyldt: 1. Basistilfældet: T gælder for n = c, og 2. Induktionsskridtet: Hvis T gælder for ethvert k, c ≤ k < n, så gælder T for n.

Teorem: Et binært træ med N interne knuder har N + 1 eksterne knuder. Bevis: (1) Basistilfældet. Teoremet gælder for N = 1. (2) Induktionsskridtet. Antag at teoremet gælder for ethvert binært træ med færre end N interne knuder. Et træ med N knuder består af en rod samt to undertræer med henholdsvis k og N - 1 - k interne knuder, hvor 0 ≤ k < N. Antallet af eksterne knuder bliver derfor (k+1) + (N-1-k+1) = N+1. k N-1-k

Induktion kan benyttes ved design af algoritmer Induktionsprincippet kan benyttes konstruktivt. Løsning af små problemer benyttes til at løse større problemer. (1) Start med en vilkårlig instans af problemet. (2) Prøv at løse dette under antagelse af, at det samme problem - men af mindre størrelse - er blevet løst.

Eksempel: Sortering af n tal i stigende rækkefølge Antag at vi kan sortere n-1 tal. Vi kan da opnå en sortering af n tal ved enten (1) først at sortere n-1 af tallene, og derefter indsætte det n´te tal på den rette plads (sortering ved indsættelse), eller (2) bestemme det mindste af de n tal og sætte det forrest, sortere de resterende tal, og derefter sætte dem bagefter dette forreste tal (sortering ved udvælgelse).

Eksempel: Den maksimale delsekvenssum Problem. Givet en sekvens (x1,x2, .., xn) af reelle tal. Find en delsekvens (xi,xi+1, .., xj) af konsekutive elementer, sådan at summen af dens elementer er størst mulig. Eksempel. For sekvensen (-2, 11, -4, -1, 13, -5, 2) er den maksimale delsekvens (11, -4, -1, 13) med summen 19. Hvis alle elementer er positive, er sekvensen selv maksimal. Hvis alle elementer er negative, er den maksimale delsekvens tom (idet den tomme delsekvens har summen 0).

En simpel algoritme xi xj Tidsforbruget er proportionalt med n3. globalMax = 0; for (i = 1; i <= n; i++) { for (j = i; j <= n; j++) { sum = 0; for (k = i; k <= j; k++) sum += x[k]; if (sum > globalMax) globalMax = sum; } xi xj i j Tidsforbruget er proportionalt med n3.

Forbedret algoritme Tidsforbruget er proportionalt med n2. globalMax = 0; for (i = 1; i <= n; i++) { sum = 0; for (j = i; j <= n; j++) { sum += x[j]; if (sum > globalMax) globalMax = sum; } Tidsforbruget er proportionalt med n2.

Lad S = (x1,x2, .., xn) og S’ = (x1,x2, .., xn-1). Induktionshypotese: Vi ved, hvordan den maksimale delsekvens findes i sekvenser af længde < n. Hvis n = 1, så kan den maksimale delsekvens let bestemmes. Hvis tallet er positivt, består den af tallet selv. Ellers er den tom. Lad S = (x1,x2, .., xn) og S’ = (x1,x2, .., xn-1). Lad S’M være den maksimale delsekvens for S’. x1 x2 xn xn-1 S S’ S’M Hvis S’M er tom, er den maksimale delsekvens for S tom, hvis xn er negativ, ellers lig med (xn).

Antag at S’M ikke er tom, dvs. S’M = (xi,xi+1, Antag at S’M ikke er tom, dvs. S’M = (xi,xi+1, .., xj), for 1 ≤ i ≤ j ≤n-1. Hvis j = n-1, udvides S’M med xn , hvis og kun hvis xn er positiv. Hvis j < n-1 er der to tilfælde: (1) Enten er S’M også maksimal for S. (2) Eller der er en anden delsekvens, der ikke er maksimal i S’, men som er maksimal i S, når xn tilføjes. Hvilket tilfælde, der er tale om, kan ikke afgøres på baggrund af den foreliggende information: S’M . Men da xn kun forlænger en delsekvens, der ender i xn-1 , dvs. er et suffix for S’, kan afgørelsen træffes, hvis vi kender det maksimale suffix S’E = (xi,xi+1, .., xn-1) for S’. Induktionshypotesen skærpes.

Vi når derved frem til følgende algoritme: Skærpet induktionshypotese: Vi ved, for enhver sekvens af længde < n, hvordan den maksimale delsekvens og det maksimale suffix findes. Vi når derved frem til følgende algoritme: globalMax = suffixMax = 0; for (i = 1; i <= n; i++) if (suffixMax + x[i] > globalMax) globalMax = suffixMax += x[i]; else suffixMax = suffixMax + x[i] > 0 ? suffixMax + x[i] : 0; Tidsforbruget er proportionalt med n.

Del-og-hersk Del-og-hersk er en vigtig teknik til algoritmisering. Teknikken er et eksempel på brugen af stærk induktion. (1) Del problemet op i mindre delproblemer. (2) Hersk ved at løse hvert delproblem. (3) Kombiner resultaterne til en løsning for det oprindelige problem. Hvis delproblemerne er mindre udgaver af det oprindelige problem, kan rekursion ofte benyttes med fordel.

Del-og-hersk ved rekursion Pseuodekode: solve(Problem p) { if (size(p) <= critical_size) solve_small_problem(p); else { divide(p); solve(subproblem[1]); solve(subproblem[2]); .... combine_solutions(); }

Potensopløftning Problem. Beregn P(x,n) = xn, hvor x er et reelt tal og n er et positivt heltal. Simpel løsning: P = x; for (i = 1; i < n; i++) P *= x; Antal multiplikationer: n-1.

multiplikationer kun med 1). Løsning ved del og hersk: Hvis n er lige, opløses problemet P(x,n) i to (ens) delproblemer P(x,n/2) og P(x,n/2), og løsningen bestemmes som P(x,n/2)*P(x,n/2). Ellers bestemmes løsningen som x*P(x,n-1). double P(double x, int n) { if (n == 1) return x; if (n % 2 == 0) { double L = P(x, n/2); return L*L; } return x * P(x, n-1); Det kan bevises, at antallet af multiplikationer er cirka log2n. (Hvis n fordobles øges antallet af multiplikationer kun med 1). Anvendelse: kryptologi, hvor x og n er meget store heltal.

Rekursion Rekursiv definition af X: X defineres i termer af sig selv. Rekursion er nyttig, når en generel version af X kan defineres i termer af simplere versioner af X. Et problem løses rekursivt ved (1) at nedbryde det i mindre delproblemer af samme slags, (2) fortsætte med dette indtil delproblemerne er så simple, at de umiddelbart kan løses, og (3) kombinere løsningerne af delproblemerne til en løsning af det oprindelige problem.

Tænk rekursivt for at opnå simple og præcise definitioner elegante løsninger på problemer, der ellers er svære at løse algoritmer, der er simple at analysere

Simpelhed og præcision (eksempel: Fakultetsfunktionen) Upræcis definition: n! = n * (n-1) * (n-2) * ... * 2 * 1 Præcis (rekursiv) defintion: 1 , hvis n = 1 n * (n-1)! , hvis n > 1 n! ={ int faculty(int n) { if (n == 1) return 1; return n * faculty(n-1); }

Rekursive datastrukturer: Lister En liste er enten tom eller en knude efterfulgt af en liste. class Node { Node next; int number; } Rekursiv metode til udskrivning af en liste: void printList(Node n) { if (n == null) return; IO.print(n.number + “ “); printList(n.next); }

Rekursive datastrukturer Binære træer Et binært træ er enten tomt eller en knude med et venstre og et højre binært træ (undertræ). class Node { Node left, right; int number; } Rekursiv metode til udskrivning af et binært træ: void printTree(Node t) { if (t == null) return; IO.print(t.number + “ “); printTree(t.left); printTree(t.right); }

Systematisk gennemgang af binære træer Preorder: void traverse(Node t) { if (t != z) { visit(t); traverse(t.l); traverse(t.r); } Inorder: void traverse(Node t) { if (t != z) { traverse(t.l); visit(t); traverse(t.r); } Postorder: void traverse(Node t) { if (t != z) { traverse(t.l); traverse(t.r); visit(t); }

Tårnene i Hanoi (fra et munkekloster i Tibet) from via to Problem. Flyt skiverne fra pinden from til pinden to, idet en større skive aldrig må placeres oven på en mindre skive. At flytte n skiver fra pinden from til pinden to kan foretages ved først at flytte de øverste n-1 skiver fra pinden from til pinden via. Dernæst flyttes den nederste skive fra pinden from til pinden to. Endelig flyttes de n-1 skiver fra pinden via til pinden to.

void move(int n, int from, int to, int via) { if (n == 0) return; move(n-1, from, via, to); IO.println(“Move “ + from + “ to “ + to); move(n-1, via, to, from); } move(3,1,3,2) Kaldtræ for move(3,1,3,2) move(2,1,2,3) move(2,2,3,1) move(1,1,3,2) move(1,3,2,1) move(1,2,1,3) move(1,1,3,2) move(0,1,2,3) move(0,3,1,2) move(0,3,2,1) move(0,1,2,3) move(0,2,3,1) move(0,3,2,1) move(0,1,2,3) move(0,2,3,1)

Effektivitetsanalyse Tidsforbruget er proportionalt med antallet af flytninger, F(n), hvor n angiver antallet af skiver. F(n) = F(n-1) + 1 + F(n-1) = 2*F(n-1) + 1 for n > 1 F(1) = 1 som har løsningen 2n - 1. Pladsforbruget er det maksimale antal uafsluttede kald af move, dvs. n.

Fundamentale regler for rekursion Basistilfælde: Hav altid mindst et tilfælde, der kan løses uden brug af rekursion. Gør fremskridt: Ethvert rekursivt kald bør komme nærmere på et basistilfælde. Tro på det: Antag altid, at et rekursivt kald virker som ønsket.

Fjernelse af rekursion (et eksempel på algoritmetransformation) Rekursion har omkostninger i tid og plads. Omkostningen i tid skyldes mekanismer for metodekald og parameteroverførsel. Omkostningen i plads er bestemt af det maksimale rekursionsniveau (det maksimale antal metodeaktiveringer, der eksisterer samtidigt). Enhver rekursiv algoritme kan mekanisk transformeres til en ikke-rekursiv algoritme (ved brug af eksplicit stak)

Fjernelse af halerekursion Iteration er at sætte operationer af samme art efter hinanden. Rekursion er at stikke operationer af samme art ind i hinanden. Hvis “indstikket” sker helt til slut i enhver operation, må dette være det samme som iteration. Rekursion Iteration void P(parameter x) { if (B(x)) S1; else { S2; P(f(x)); } Kald: P(a) variabel x = a; while (!B(x)) { S2; x = f(x); } S1;

Eksempel på fjernelse af rekursion (Preorder traversal) (1) void traverse(Node t) { if (t != z) { visit(t); traverse(t.l); traverse(t.r); } Benyt “goto” som kontrolstruktur. (2) void traverse(Node t) { if (t == z) goto X; visit(t); traverse(t.l); traverse(t.r); X: ; } Fjern halerekursion (sidste kald af traverse).

(3) void traverse(Node t) { L: if (t == z) goto X; visit(t); traverse(t.l); t = t.r; goto L; X: ; } Benyt eksplicit stak til at fjerne det resterende rekursive kald. (4) void traverse(Node t) { L: if (t == z) goto S; visit(t); stack.push(t); t = t.l; goto L; R: t = t.r; goto L; S: if (stack.empty()) goto X; t = stack.pop(); goto R; X: ; } Konstruktionen ‘t = t.r; goto L;’ kan flyttes og erstatte ‘goto R;’.

(5) void traverse(Node t) { L: if (t == z) goto S; visit(t); stack.push(t); t = t.l; goto L; S: if (stack.empty()) goto X; t = stack.pop(); t = t.r; goto L; X: ; } Sætningen t = t.r kan fjernes ved at stakke t.r i stedet for t. (6) void traverse(Node t) { L: if (t == z) goto S; visit(t); stack.push(t.r); t = t.l; goto L; S: if (stack.empty()) goto X; t = stack.pop(); goto L; X: ; } De to første goto’s fjernes ved at anvende en while-løkke.

(7) void traverse(Node t) { L: while (t != z) { visit(t); stack.push(t.r); t = t.l; } if (stack.empty()) goto X; t = stack.pop(); goto L; X: ; De sidste to goto’s fjernes ved at omforme den implicitte løkke, der er angivet ved etiketten L, til en while-løkke, idet kaldet push(t) indsættes i starten af traverse. (8) void traverse(Node t) { stack.push(t); while (!stack.empty()) { t = stack.pop(); while (t != z) { visit(t); stack.push(t.r); t = t.l; } Den indre while-løkke kan erstattes med en if-sætning (på bekostning af et ekstra kald af push).

(9) void traverse(Node t) { stack.push(t); while (!stack.empty()) { t = stack.pop(); if (t != z) { visit(t); stack.push(t.r); stack.push(t.l); } Sørg for aldrig at stakke tomme træer. Dermed kan testen t != z fjernes. (10) void traverse(Node t) { stack.push(t); while (!stack.empty()) { t = stack.pop(); visit(t); if (t.r != z) stack.push(t.r); if (t.l != z) stack.push(t.l); }

At ræsonnere om algoritmer Evnen til at drage logiske slutninger er nyttig ved • algoritmedesign • fejlfinding • forbedringer • verifikation (bevis for korrekthed)

Verifikation En algoritme A siges at være partielt korrekt, når der gælder følgende: Hvis A terminerer for et givet legalt input, så vil dens output være korrekt. En algoritme A siges at være korrekt (eller totalt korrekt), hvis A er partielt korrekt, og A terminerer for ethvert legalt input. Bevisførelse af partiel korrekthed foretages ved brug af påstande (assertions). En påstand er et logisk udsagn, der er knyttet til et punkt i algoritmen, og som er opfyldt, hver gang algoritmen når til dette punkt.

Påstande Før-betingelse (precondition) Et udsagn, som gælder før udførelsen af en eller flere sætninger. Efter-betingelse (postcondition) Et udsagn, som gælder efter udførelsen af en eller flere sætninger. Løkke-invariant (loop invariant) Et udsagn, som gælder umiddelbart før løkke-testen i en løkke.

Eksempler på påstande { m ≤ n } før-betingelse sort(a, m, n); { a[m] ≤ a[m+1] ≤ . . . ≤ a[n] } efter-betingelse i = m; while { a[m] ≤ a[m+1] ≤ . . . ≤ a[i-1] ^ a[m .. i-1] ≤ a[i .. n] } løkke-invariant (i < n) { min = i; for (j = i+1; j <= n; j++) if (a[j] < a[min]) min = j; x = a[i]; a[i] = a[min]; a[min] = x; i++; }

Regler for verifikation • Tildeling: { P } { w > 2 } v = w; { P } { v > 2 } w w v Valg: {P} if (B) { P ^ B } S1; else { P ^ ¬ B } S2;

Eksempel på verifikation (heltalsdivision) Givet algoritmen q = 0; r = x; while (r >= y) { r = r - y; q = q + 1; } hvor x, y, q og r er heltal, x ≥ 0 og y > 0. Bevis at algoritmen bestemmer kvotienten q og resten r ved heltalsdivision af x med y, dvs. r = x - q*y (0 ≤ r < y)

Bevisførelsen (1) Løkke-invarianten er opfyldt ved indgangen til løkken. (2) Hvis løkke-invarianten er opfyldt i en given iteration, så er den også opfyldt i den efterfølgende iteration. (3) Hvis algoritmen terminerer, så er dens efter-betingelse opfyldt. (4) Algoritmen terminerer.

{ x ≥ 0, y > 0 } algoritmens før-betingelse q = 0; r = x; while { r = x - qy ^ r ≥ 0 } løkke-invariant (r >= y) { r = r - y; q = q + 1; } { r = x - qy ^ 0 ≤ r < y } algoritmens efter-betingelse { r = x - qy ^ r ≥ y }  { r - y = x - (q+1)y ^ r - y ≥ 0 } { r = x - (q+1)y ^ r ≥ 0 }

Terminering Algoritmen terminerer. q = 0; r = x; while (r >= y) { r = r - y; q = q + 1; } Algoritmen terminerer. Bevis. Da y > 0, mindskes r ved hvert gennemløb af løkken. Løkketesten må derfor blive falsk efter et endeligt antal gennemløb.

Algoritmeanalyse Kvantitative analyser af algoritmers opførsel Beregning af algoritmers tids- og pladsforbrug. Nyttigt: (1) ved valg imellem eksisterende algoritmer (2) ved udvikling af nye algoritmer

O-notation O-notation benyttes til at angive en øvre grænse for en algoritmes ressourceforbrug (tid eller plads), idet der ses bort fra konstante faktorer. At en algoritmes tidsforbrug er O(f(n)), hvor n er et udtryk for problemets “størrelse”, betyder: (1) Intuitiv definition: Algoritmens tidsforbrug vokser i værste tilfælde som f(n), når blot n er tilstrækkelig stor. (2) Formel definition: Der eksisterer to konstanter, c og N0, således at algoritmens tidsforbrug er opadtil begrænset af cf(n) for alle n ≥ N0.

cf(n) g(n) N0 n Illustration af udsagnet “g(n) er O(f(n))” O-notationen fokuserer på den generelle kurveform (ser bort fra proportionalitetskonstanter). O-notationen muliggør vurderinger af algoritmeeffek- tivitet, som er uafhængige af det valgte programmerings- sprog, oversætter og maskinel.

Kurver for lg n, n og n2 n2 n lg n

Regneregler for O-notationen Hvis f(n) = dominerende led + ikke-dominerende led, så er O(f(n)) = O(dominerende led) Eksempler: O(n3 - 10000n2 + 10000) = O(n3) O(n3 + n log n) = O(n3) O(6n3) = O(n3) O(logbn) = O(log n) O(300) = O(1) Præcedensregler: O(1) < O(log n) < O(n) < O(n log n) < O(n2) < O(n3) < O(2n) < O(10n) < O(n!) O(1): konstant, O(logn): logaritmisk, O(n): lineær, O(n2): kvadratisk, O(n3): kubisk, O(nc): polynomiel, O(cn): eksponentiel (hvor c > 1)

Eksempler på analyse x *= i; Tidsforbrug: O(1) (konstant) for (i = 1; i <= n; i++) x *= i; Tidsforbrug: O(n) (lineært) for (i = 1; i <= n; i++) for (j = 1; j <= n; j++) x *= i+j; Tidsforbrug: O(n2) (kvadratisk) for (i = 1; i <= n; i++) for (j = 1; j <= i; j++) x *= i+j; Tidsforbrug: O(n(n+1)/2) = O(n2/2+n/2) = O(n2)

Eksempel på analyse (Den maksimale delsekvenssum) globalMax = 0; for (i = 1; i <= n; i++) { for (j = i; j <= n; j++) { sum = 0; for (k = i; k <= j; k++) sum += x[k]; if (sum > globalMax) globalMax = sum; } Tidsforbrug: O(n3) (kubisk)

Tidsforbrug: O(n2) (kvadratisk) globalMax = 0; for (i = 1; i <= n; i++) { sum = 0; for (j = i; j <= n; j++) { sum += x[j]; if (sum > globalMax) globalMax = sum; } Tidsforbrug: O(n2) (kvadratisk) globalMax = suffixMax = 0; for (i = 1; i <= n; i++) if (suffixMax + x[i] > globalMax) globalMax = suffixMax += x[i]; else suffixMax = suffixMax + x[i] > 0 ? suffixMax + x[i] : 0; Tidsforbrug: O(n) (lineært)

Hvad kan beregnes i praksis? En datamat udfører cirka 1010 operationer i timen. Størrelsen af de problemer, der kan løses på en time, afhænger af algoritmens kompleksitet. Kompleksitet Størrelse (= n) 10 9.5 log2n 210 ≈ 1010 n 1010 n2 105 n5 102 2n 33 n! 13

Kompleksitetsvurdering 3 tilfælde: • det bedste • det værste • det gennemsnitlige Eksempel - Quicksort: Bedste: O(n log n) Værste: O(n2) Gennemsnitlige: O(n log n) (hvor n er antallet af elementer, der skal sorteres) Sædvanligvis er vi interesseret i værste tilfælde, fordi: (1) vi ønsker en øvre grænse (en garanti), og (2) det er lettest at beregne.

O-notationens begrænsning O-notationen er kun brugbar for “store” værdier af n. For små værdier af n kan de ikke-dominerende led have en væsentlig betydning.

Ugeseddel 3 30. september - 6. oktober • Læs kapitel 8 i lærebogen (side 93-113) • Løs følgende opgaver 1. Opgave 5.6 eller 5.7. [5.7 er svær]. 2. Opgave 6.9. 3. Lad et binært træ være repræsenteret ved hjælp af objekter af klassen class Node { Node left, right; } hvor left og right repræsenterer henholdsvis roden i det venstre undertræ og roden i det højre undertræ (null, hvis et træ er tomt). Programmér en metode, int height(Node root), der returnerer højden af det binære, der har knuden root som rod.

4. En k-kombination af en mængde med n elementer er 4. En k-kombination af en mængde med n elementer er en delmængde af mængden, der netop indeholder k elementer. Nedenfor ses samtlige 3-kombinationer af mængden {1,2,3,4,5}. {1,2,3}, {1,2,4}, {1,2,5}, {1,3,4}, {1,3,5}, {1,4,5}, {2,3,4}, {2,3,5}, {2,4,5} ,{3,4,5} Programmér en metode, der udskriver samtlige k-kombinationer af tallene fra 1 til n. 5. Øvelse i algoritmedesign. Se de næste sider. Opgaven er lærerig, men ikke helt let. Løs den, hvis du har tid og lyst. En løsning vil blive offentliggjort på kursets hjemmeside torsdag den 21. oktober.

Skyline-problemet Opgaven går ud på at konstruere et Java-program, der kan hjælpe en arkitekt med at tegne omridset (engelsk: skyline) af by, givet husenes placering og højde. For at gøre problemet simpelt er alle bygningerne rektangulære, og de står på samme linje (byen er “flad”). En bygning er repræsenteret som (Li,Hi,R i), hvor Li og R i er venstre og højre koordinater, mens Hi er højden. Følgende figur viser bygningerne svarende til følgende input: (1,11,5), (2,6,7), (3,13,9), (12,7,16) , (14,3,25), (19,18,22), (23,13,29) Tallene i fede typer er højderne.

5 10 15 20 25 30 Nedenfor er vist løsningen på problemet, repræsenteret ved listen (1, 11, 3, 13, 9, 0, 12, 7, 16, 3, 19, 18, 22, 3, 23, 13, 29, 0) hvor tallene i fede typer igen er højder. 5 10 15 20 25 30

Programmet skal med andre ord fjerne de skjulte linjer. Inddata Input til programmet er tripler for bygningerne. Alle koordinater og højder er heltal mindre end 10000, og der vil være mindst 1 og højst 100 bygninger repræsenteret. Hver bygning er repræsenteret på hver sin linje. Tallene på hver linje er adskilt af et eller flere blanktegn, og triplerne er sorteret i stigende orden efter deres Li-koordinat. Uddata Output skal være en række tal v1 v2 .. vn. Når i er ulige skal vi betegne en Li-koordinat, mens det efterfølgende tal, vi+1, skal betegne en højde. Li-koordinaterne skal være ordnet i stigende rækkefølge. Eksempel på inddata 1 11 5 2 6 7 3 13 9 12 7 16 14 3 25 19 18 22 21 13 29 24 4 28 Eksempel på uddata 1 11 3 13 9 0 12 7 16 3 19 18 22 3 23 13 29 0

For interesserede De følgende slides indeholder materiale, der ikke vil blive gennemgået ved forelæsningen. Det drejer sig om: Beregning af polynomier Søgning i en labyrint Fraktaler Hilbertkurve Alternativ metode til fjernelse af rekursion Ω,  og o Eksempel på formel for rekursionsligning

Eksempel: Beregning af polynomier Problem. Givet en sekvens af reelle tal an, an-1,..., a1, a0 samt et reelt tal x. Beregn værdien af polynomiet Pn(x) = anxn + an-1xn-1 + ... + a1x + a0. Hvorledes kan vi reducere problemet? Første forsøg: Fjern an. Induktionshypotese: Vi ved, hvorledes polynomiet repræsenteret ved an-1,..., a1, a0 kan beregnes i x, dvs. vi ved, hvorledes Pn-1(x) kan beregnes.

Basistilfældet: n = 0. Beregning af a0 er simpel. Induktionsskridtet: Pn(x) = Pn-1(x) + anxn. Rekursivt: double P(int n) { if (n == 0) return a[0]; double xn = 1; for (int k = 1; k <= n; k++) xn *= x; return P(n-1) + a[n]*xn; } Iterativt: double P = a[0]; for (int i = 1; i <= n; i++) { double xi = 1; for (int k = 1; k <= i; k++) xi *= x; P += a[i]*xi; } Algoritmen er ikke særlig effektiv: n additioner, men 1 + 2 + ... + n = n(n+1)/2 multiplikationer.

Skærpelse af induktionshypotesen Skærpet induktionshypotese: Vi ved, hvorledes polynomiet repræsenteret ved an-1,..., a1, a0 kan beregnes i x, dvs. vi ved hvorledes Pn-1(x) kan beregnes, og vi ved hvorledes xn-1 kan beregnes. Mere restriktiv hypotese, men lettere at udvide. double P = a[0], xi = 1; for (int i = 1; i <= n; i++) { xi *= x; P += a[i]*xi; } Effektivitet: n additioner og 2n multiplikationer.

Yderligere forbedring Horners regel Andet forsøg: Fjern a0. Lad P’n-1(x) = anxn-1 + an-1xn-2 +... + a1. Induktionshypotese: Vi ved, hvorledes polynomiet repræsenteret ved an, an-1 ,.., a1 kan beregnes i x, dvs. vi ved, hvorledes P’n-1(x) kan beregnes. Pn(x) = P’n-1(x)*x + a0. Pn(x) = (( ... ((anx + an-1)x + a n-2 )x + ...)x + a1)x + a0 double P = a[n]; for (int i = n-1; i >= 0; i--) P = P*x + a[i]; Effektivitet: n additioner og n multiplikationer.

Søgning i en labyrint Problem Løsning Start WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW W W W W W W W W W W WWWWW W W W W W W W W WWWWWWWWWW W WW W WWWW WWW W WWWWWW W W W W WWWWWWWW W W W W W W WWWWWW W W W W W W W WWWWW W W WWWWWWWWWWWW WW WW W W WWWW W WWWW W W W W W WWWW W W WWWWWWWWWWWWWWWWWWWW W W W W W WW W W W WWWWWWWWWWW WWWWWWWW W WWWWWWWWWW WWWW WWWWW W W W WW W W W W W W Slut WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW ******W W W W W W W W W W WWWWW*W W W W W W W W WWWWWWWWWW W***** WW W WWWW WWW W WWWWWW W W W W*WWWWWWWW W W W W**** W********W WWWWWW W* W W W W**W*W**WWWWW** W W*WWWWWWWWWWWW WW WW*W*W*WWWW*** W WWWW W W********************W*W*W *WWWW W W WWWWWWWWWWWWWWWWWWWW W***W W****W W WW W W W WWWWWWWWWWW*WWWWWWWW W WWWWWWWWWW WWWW WWWWW W W*W***WW W W W W W ***W***** Løsning Slut Start

public class Program { static String problem[] = {"WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW", " W W W W W W W W W W", "WWWWW W W W W W W W W WWWWWWWWWW", "W WW W WWWW WWW W WWWWWW W W W", "W WWWWWWWW W W W W W W WWWWWW", "W W W W W W W WWWWW W", "W WWWWWWWWWWWW WW WW W W WWWW W WWWW W", "W W W W WWWW W W", "WWWWWWWWWWWWWWWWWWWW W W W W W WW W", "W W WWWWWWWWWWW WWWWWWWW", "W WWWWWWWWWW WWWW WWWWW W W W WW W", "W W W W W ", "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"}; static int startX = 1, startY = 0, endX = 11, endY = 40; static boolean solutionFound = false; static StringBuffer solution[] = new StringBuffer[problem.length];

static void visit(int x, int y) { solution[x].setCharAt(y,'*'); solutionFound = (x == endX && y == endY); for (int d = 1; d <= 4 && !solutionFound; d++) { int newx, newy; switch(d) { case 1: newy = y - 1; break; case 2: newx = x - 1; break; case 3: newy = y + 1; break; case 4: newx = x + 1; break; } if (newx >= 0 && newx < solution.length && newy >= 0 && newy < solution[0].length() && solution[newx].charAt(newy) == ' ') visit(newx, newy); if (!solutionFound) solution[x].setCharAt(y, ' '); public static void main(String[] args) { for (int x = 0; x < problem.length; x++) solution[x] = new StringBuffer(problem[x]); visit(startX, startY); if (solutionFound) for (int x = 0; x < solution.length; x++) IO.println(solution[x]);

Fraktaler

left right left right mid

import java.awt.*; import java.applet.Applet; public class Fractals extends Applet { final int totalWidth = 512; public void init() { resize(totalWidth, totalWidth); repaint(); } void fractal(int leftx, int lefty, int rightx, int righty) { int width = rightx - leftx; if (width <= 1) { Graphics g = getGraphics(); g.drawLine(leftx, lefty, rightx, righty); else { int midx = (leftx + rightx)/2, midy = (lefty + righty)/2 + (int) (Math.random()*width) - width/2; fractal(leftx, lefty, midx, midy); fractal(midx, midy, rightx, righty); public void paint(Graphics g) { fractal(0,totalWidth/2,totalWidth, totalWidth/2);

Hilbertkurve (Hilbert, 1891)

H1 H2 H3

A: D A A B B: C B B A C: B C C D D: A D D C void A(int i) { if (i == 0) return; D(i-1); left(); A(i-1); down(); A(i-1); right(); B(i-1); }

public class Hilbert extends Applet { final int h0 = 512, n = 5; int x, y, h; Graphics G; public void init() { resize(h0,h0); repaint(); } void left() { G.drawLine(x, y, x-h, y); x -= h; } void right() { G.drawLine(x, y, x+h, y); x += h; } void up() { G.drawLine(x, y, x, y+h); y += h; } void down() { G.drawLine(x, y, x, y-h); y -= h; } void A(int i) { ... } void B(int i) { ... } void C(int i) { ... } void D(int i) { ... } public void paint(Graphics g) { G = g; h = h0; int x0 = y0 = h/2; for (int i = 1; i <= n; i++) { h /= 2; x = x0 += h/2; y = y0 += h/2; A(i); }

Alternativ metode til fjernelse af rekursion class Frame { Node t; Frame prev; int PC = 1; Frame(Node t, Frame prev) { this.t = t; this.prev = prev; } traverse(Node t){ Frame top = new Frame(t, null); while (top != null) { switch(top.PC) { case 1: if (top.t == z) { top = top.prev; continue; } visit(top.t); case 2: top.PC = 3; top = new Frame(top.t.l, top); continue; case 3: top.PC = 4; top = new Frame(top.t.r, top); case 4: top = top.prev; }

Ω,  og o Hvor O(f(n)) kan benyttes til at angive en øvre grænse (≤) for vækst, kan Ω(f(n)) benyttes til at angive en nedre grænse (≥). g(n) = Ω(f(n)), hvis der findes konstanter, c og N0, således at g(n) ≥ cf(n), når n ≥ N0. g(n) = (f(n)), hvis væksten for g er den samme (=) som væksten for f. g(n) = (f(n)) , hvis og kun hvis g(n) = O(f(n)) og g(n) = Ω(f(n)) g(n) = o(f(n)), hvis væksten for g er mindre end (<) væksten for f. g(n) = o(f(n)) , hvis og kun hvis g(n) = O(f(n)) og g(n) ≠ (f(n))

Eksempel på formel for rekursionsligning Rekursionsligningen T(n) = aT(n/b) + cnk , hvor a og b er helstalskonstanter, og c og k er positive konstanter har løsningen O(nlogba) , hvis a > bk T(n) = O(nklogbn) , hvis a = bk O(nk) , hvis a < bk {