Præsentation er lastning. Vent venligst

Præsentation er lastning. Vent venligst

Design, verifikation og analyse

Lignende præsentationer


Præsentationer af emnet: "Design, verifikation og analyse"— Præsentationens transcript:

1 Design, verifikation og analyse

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

3 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.

4 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?

5 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).

6 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.

7 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?

8 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?

9 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?

10 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.

11 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.)

12 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 . p 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.

13 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.

14 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.

15 Teorem: Ethvert beløb ≥ 4 kroner kan veksles i et antal 2-kroner og et antal 5-kroner.
Bevis: (1) Basistilfældet 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.

16 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.

17 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 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

18 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.

19 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).

20 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).

21 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.

22 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.

23 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).

24 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.

25 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.

26 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.

27 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(); }

28 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.

29 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.

30 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.

31 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

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

33 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); }

34 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); }

35 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); }

36 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.

37 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)

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

39 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.

40 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)

41 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;

42 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).

43 (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;’.

44 (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.

45 (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).

46 (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); }

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

48 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.

49 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.

50 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++; }

51 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;

52 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)

53 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.

54 { 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 }

55 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.

56 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

57 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.

58 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.

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

60 Regneregler for O-notationen
Hvis f(n) = dominerende led + ikke-dominerende led, så er O(f(n)) = O(dominerende led) Eksempler: O(n n ) = 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)

61 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)

62 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)

63 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)

64 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) log2n ≈ 1010 n 1010 n2 105 n5 102 2n 33 n! 13

65 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.

66 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.

67 Ugeseddel 3 30. september - 6. oktober
• Læs kapitel 8 i lærebogen (side ) • 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.

68 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.

69 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.

70 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

71 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 Eksempel på uddata

72 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

73 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 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.

74 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 n = n(n+1)/2 multiplikationer.

75 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.

76 Yderligere forbedring Horners regel
Andet forsøg: Fjern a0. Lad P’n-1(x) = anxn-1 + an-1xn 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.

77 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

78 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];

79 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]);

80 Fraktaler

81 left right left right mid

82 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);

83 Hilbertkurve (Hilbert, 1891)

84 H1 H2 H3

85 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); }

86 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); }

87 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; }

88 Ω,  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))

89 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 {


Download ppt "Design, verifikation og analyse"

Lignende præsentationer


Annoncer fra Google