Transcript
Page 1: Rekursion og algoritmedesign

1

Rekursion og algoritmedesign

Page 2: Rekursion og algoritmedesign

2

• RekursionMatematisk induktionRekursion

Simple eksempler

Kryptering

Plan

• DesignmetoderDel-og-herskDynamisk programmeringBaksporing

Page 3: Rekursion og algoritmedesign

3

Tre centrale begreber

(1) SyntaksGrammatik (udseende)

(2) SemantikBetydning (virkning)

(3) PragmatikAnvendelse (nytte)

Page 4: Rekursion og algoritmedesign

4

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.

Page 5: Rekursion og algoritmedesign

5

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.

Matematisk induktion

Antagelsen i induktionsskridtet kaldes induktionshypotesen.

Page 6: Rekursion og algoritmedesign

6

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.

Page 7: Rekursion og algoritmedesign

7

Induktionsprincippet kan benyttes konstruktivt. Løsning af små problemer benyttes til at løse større problemer.

Induktion kan benyttes ved design af algoritmer

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

Page 8: Rekursion og algoritmedesign

8

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

Page 9: Rekursion og algoritmedesign

9

Eksempel: Binær søgning

For at finde et element i et sorteret array med n elementer:

Opdel arrayet i to (næsten) lige store dele.

Afgør i hvilken af de to dele, elementet skal findes.

Søg videre på samme måde (rekursivt) i denne del.

int binarySearch(Comparable[] a, Comparable key, int low, int high) throws ItemNotFound { if (low > high) throw new ItemNotFound(); int mid = (low + high)/2; if (a[mid].compares(key) < 0) return binarySearch(a, key, mid + 1, high); if (a[mid].compares(key) > 0) return binarySearch(a, key, low, mid - 1); return mid; }

Page 10: Rekursion og algoritmedesign

10

Benyt en driver-rutine til at forenkle brug

private int binarySearch(Comparable[] a, Comparable key, int low, int high) throws ItemNotFound { ...}

public int binarySearch(Comparable[] a, Comparable key) throws ItemNotFound { return binarySearch(a, key, 0, a.length - 1); }

Page 11: Rekursion og algoritmedesign

11

Del-og-hersk er en vigtig teknik til algoritmisering. Teknikken er et eksempel på brugen af stærk induktion.

Del-og-hersk

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

Page 12: Rekursion og algoritmedesign

12

Del-og-hersk

ved rekursion

Pseuodekode:

solve(Problem p) {

if (size(p) <= critical_size)

solve_small_problem(p);

else {

subproblem = divide(p);

solve(subproblem[1]);

solve(subproblem[2]);

....

combine_solutions();

}

}

Page 13: Rekursion og algoritmedesign

13

Problem. Beregn power(x,n) = xn, hvor x er et reelt tal,

og n er et positivt heltal.

Simpel løsning:

power = x; for (i = 1; i < n; i++) power *= x;

Antal multiplikationer: n-1.

Eksempel:Potensopløftning

Page 14: Rekursion og algoritmedesign

14

double power(double x, int n) { if (n == 1) return x; if (n % 2 == 0) { double p = power(x, n/2); return p*p; } return x * power(x, n-1);}

Løsning ved del-og-hersk:

Hvis n er lige, opløses problemet power(x,n) i to (ens) delproblemer: power(x,n/2) og power(x,n/2), og løsningen bestemmes som power(x,n/2)*power(x,n/2).

Ellers (hvis n er ulige) bestemmes løsningen som x*power(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.

Page 15: Rekursion og algoritmedesign

15

Eksempel:Beregning af den maksimale delsekvenssum

Problem. Givet en sekvens (a1,a2, .., an) af reelle tal. Find en delsekvens (ai,ai+1, .., aj) af konsekutive elementer, sådan at summen af dens elementer er størst mulig.

Enten findes den maksimale delsekvens

(2) helt i den højre del, eller

(3) indeholder midterelementet

Opdel problemet i to omtrent lige store dele:

(1) helt i den venstre del,

Page 16: Rekursion og algoritmedesign

16

De to første værdier bestemmes ved rekursion.

Den sidste værdi bestemmes som summen af • det maksimale suffix for sekvensen til venstre for midterelementet (inklusiv dette), og• det maksimale prefix for sekvensen til højre for midterelementet.

Den maskimale delsekvenssum bestemmes som maksimum af disse 3 værdier.

3 delproblemer

Page 17: Rekursion og algoritmedesign

17

int maxSum(int a[], int left, int right) { if (left == right) return a[left] > 0 ? a[left] : 0; int mid = (left + right)/2; return max3(maxSum(a, left, mid - 1), maxSum(a, mid + 1, right), maxSuffix(a, left, mid) + maxPrefix(a, mid + 1, right));}

Javakode

int maxSuffix(int a[], int left, int right) { int sum = 0, maxSum = 0; for (int i = right; i >= left; i--) if ((sum += a[i]) > maxSum) maxSum = sum; return maxSum;}

maxPrefix implementeres analogt

Page 18: Rekursion og algoritmedesign

18

Kompleksitet

Tiden, det tager at løse et problem med n tal, T(n), hvor n er en potens af 2, opfylder rekursionsligningerne

T(n) = 2*T(n/2) + O(n)T(1) = O(1)

Hvis O(n) og O(1) her erstattes med henholdsvis n og 1, bliver løsningen

T(n) = n log n + n

Der gælder således, at

T(n) = O(n log n).

Page 19: Rekursion og algoritmedesign

19

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.

Page 20: Rekursion og algoritmedesign

20

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

Tænk rekursivt

Page 21: Rekursion og algoritmedesign

21

int faculty(int n) {

if (n == 1)

return 1;

return n * faculty(n-1);

}

• Upræcis definition: n! = n * (n-1) * (n-2) * ... * 2 * 1

Simpelhed og præcision(eksempel: Fakultetsfunktionen)

• Præcis (rekursiv) definition:

1 , hvis n = 1

n * (n-1)! , hvis n > 1 n! ={

Page 22: Rekursion og algoritmedesign

22

Implementering af rekursion

En rekursiv metode kalder en kopi (inkarnation) af sig selv.

Kun én inkarnation er aktiv ad gangen. Resten venter (på, at den kopi, de har kaldt, returnerer).

Rekursion kan implementeres ved hjælp af en stak (idet inkarnationerne returnerer i omvendt rækkefølge i forhold til den rækkefølge, hvori de er kaldt).

Page 23: Rekursion og algoritmedesign

23

int f(int n) {

if (n == 1)

return 1;

return n * f(n-1);

}

Illustration af kaldet f(4)

n = 4f = ?

f(4)

n = 4f = ?

f(4)

f(3)

n = 3f = ?

n = 4f = ?

f(4)

f(3)

n = 3f = ? f(2)

n = 2f = ?

n = 4f = ?

f(4)

f(3)

n = 3f = ? f(2)

n = 2f = 2

n = 4f = ?

f(4)

f(3)

n = 3f = 6

n = 4f = 24

f(4)

f(4)

f(3)

f(2)

f(1)

n = 4f = ?

n = 3f = ?

n = 2f = ?

n = 1f = 1

Page 24: Rekursion og algoritmedesign

24

Brug af stak til håndtering af metodekald

int f(int a1, int a2, int a3) {

int b1, b2, b3;

}

}f' s returværdi

forrige aktiveringspostreturadresse

a1a2a3b1b2b3

Staktop

frit lager

Basis

Aktiveringspost

Page 25: Rekursion og algoritmedesign

25

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.

Tårnene i Hanoi(fra et munkekloster i Tibet)

Problem. Flyt skiverne fra pinden from til pinden to, idet en større skive aldrig må placeres oven på en mindre skive.

from via to

Page 26: Rekursion og algoritmedesign

26

void move(int n, int from, int to, int via) {

if (n == 0)

return;

move(n-1, from, via, to);

System.out.println("Move " + from + " to " + to);

move(n-1, via, to, from);

}

move(2,2,3,1)move(2,1,2,3)

move(1,1,3,2)

move(0,1,2,3) move(0,3,2,1)

move(1,2,1,3)

move(0,2,3,1) move(0,1,2,3)

move(3,1,3,2)

Kaldtræ for move(3,1,3,2)

move(0,1,2,3) move(0,3,2,1)

move(1,1,3,2)

move(0,2,3,1) move(0,3,1,2)

move(1,3,2,1)

Page 27: Rekursion og algoritmedesign

27

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 > 1F(1) = 1

som har løsningen 2n - 1.

Pladsforbruget er det maksimale antal uafsluttede kald af move, dvs. n.

Samlet tidsforbrug for 64 skiver, hvis hver flytning tager 1 sekund:

264 sekunder ≈ 1019 sekunder ≈ 1012 år

Page 28: Rekursion og algoritmedesign

28

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 nærme sig et basistilfælde.

• Tro på det: Antag altid, at et rekursivt kald virker som ønsket.

• Undgå dobbeltarbejde: Sørg for at hvert delproblem kun løses én gang.

Benyt aldrig rekursion som en erstatning for en simpel løkke.

Page 29: Rekursion og algoritmedesign

29

Eksempel(tegnvis udskrivning af et positivt heltal)

void printDecimal(int n) { if (n >= 10) printDecimal(n/10); System.out.print((char) ('0' + n%10));}

Udskriv n = 7913

Page 30: Rekursion og algoritmedesign

30

Rekursion har omkostninger i tid og plads.

Omkostningen i tid skyldes mekanismer for metodekald og parameter-overfø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

Fjernelse af rekursion(et eksempel på algoritmetransformation)

(ved brug af eksplicit stak)

Page 31: Rekursion og algoritmedesign

31

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)

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.

variabel x = a;while (!B(x)) { S2; x = f(x); }S1;

Page 32: Rekursion og algoritmedesign

32

RSA-kryptering(Rivest, Shamir og Adleman, 1978)

Problem: Alice ønsker at sende en besked til Bob, men således at ingen andre kan læse hendes besked.

Løsning: Bob offentliggør to tal, e og N, som alle, der sender beskeder til ham, skal benytte.

Kryptering: Alice sender sin besked M i form af tallet R = Me (mod N).

Dekryptering: Den oprindelige besked gendannes af Bob ved hjælp af transformationen Rd (mod N), hvor d er et tal, som kun Bob kender.

Page 33: Rekursion og algoritmedesign

33

RSA-kryptering

Bestemmelse af e, d og N:

1) Vælg to store primtal p og q (typisk på mere end 100 cifre).

2) Beregn N = p*q.

3) Beregn N’ = (p-1)*(q-1).

4) Vælg e > 1, således at gcd(N’, e) = 1.

5) Vælg d, således at e*d (mod N’) = 1.

[ d.v.s., således at d er multiplikativ invers til e ].

Så vil (Me)d = M (mod N).

Bob bør hemmeligholde p, q, N’ og d.

Page 34: Rekursion og algoritmedesign

34

Et eksempel

(1) p = 47 og q = 79 (to primtal)

(2) N = p*q = 3713

(3) N’ = (p-1)*(q-1) = 3588

(4) e = 37 (gcd(N’, e) = 1)

(5) d = 97 (e*d (mod N’) = 1, idet e*d = 3589)

Page 35: Rekursion og algoritmedesign

35

Eksempel fortsat(e = 37, d = 97, N = 3713)

Meddelelse: ATTACK AT DAWN

Kodning: A = 01, B = 02, C = 03, o.s.v.

A T T A C K A T D A W N 0120200103110001200004012314 (opdel i blokke á 2 tegn)

Kryptering ved den offentlige nøgle 37:

012037 = 1404 200137 = 2392 (mod 3713)

1404239235360001328422802235

Dekryptering ved den hemmelige nøgle 97:

140497 = 0120 239297 = 2001 (mod 3713)

0120200103110001200004012314

Page 36: Rekursion og algoritmedesign

36

Delalgoritmer

(1) Potensopløftning af lange heltal (Me og Rd).

(2) Afgørelse af om et langt heltal er et primtal.

(3) Multiplikation af to lange heltal ((p-1)*(q-1)).

(4) Bestemmelse af største fælles divisor for to lange heltal (gcd(N’, e)).

(5) Bestemmelse af det multiplikative inverse tal til et langt heltal (e*d (mod N’) = 1).

Page 37: Rekursion og algoritmedesign

37

Test af RSA-krypteringimport Supporting.Numerical;

public class RSA { public static void main(String[] args) { long x = 10000, y = 50000, message = 123456789; long p, q, n, nPrime, e, d;

for (p = x; !Numerical.isPrime(p); p++) ; for (q = y + 2; !Numerical.isPrime(q); q++) ; n = p*q; // p == 10007, q == 50021 nPrime = (p - 1)*(q - 1); for (e = nPrime/10; Numerical.gcd(e, nPrime) != 1; e++) ; d = Numerical.inverse(e, nPrime); long code = Numerical.power(message, e, n); long decode = Numerical.power(code, d, n); if (message != decode) System.out.println("OOPS!!!"); else System.out.println("Success"); }}

Page 38: Rekursion og algoritmedesign

38

Sikkerhed ved RSA-kryptering

Hvis d kan bestemmes ud fra kendskab til e og N, brydes brev-hemmeligheden.

Hvis N kan faktoriseres, N = x*y, så kan d bestemmes.

Imidlertid er faktorisering af et tal en meget vanskelig opgave. Med dagens teknologi vil det tage millioner af år for en computer at faktorisere et tal bestående af 200 cifre.

Page 39: Rekursion og algoritmedesign

39

Del-og-hersk (top-til-bund, top-down):

For at løse et stort problem deles problemet op i mindre delproblemer, der løses uafhængigt af hinanden.

Dynamisk programmering

Dynamisk programmering (bund-til-top, bottom-up): For at løse et stort problem løses alle mindre delproblemer, og deres

løsninger gemmes og benyttes til at løse større problemer. Således fortsættes, indtil problemet er løst.

Betegnelsen stammer fra operationsanalysen, hvor “programmering” benyttes om formulering af et problem, således at en bestemt metode kan anvendes.

Page 40: Rekursion og algoritmedesign

40

Dynamisk programmering

Moderne definition:

Bund-til-top implementering af rekursive programmer med overlappende delproblemer.

Top-til-bund implementering er dog også mulig.

Dynamisk programmering er baseret på følgende simple princip: Undgå at gentage en beregning.

Page 41: Rekursion og algoritmedesign

41

F(n) = F(n-1) + F(n-2) for n >= 2,

F(0) = 0,

F(1) = 1

0 0 10 55 20 67651 1 11 89 21 109462 1 12 144 22 177113 2 13 233 23 286574 3 14 377 24 463685 5 15 610 25 750256 8 16 987 26 1213937 13 17 1597 27 1964188 21 18 2584 28 3178119 34 19 4181 29 514229

Simpelt eksempelBeregning af Fibonacci-tal

(Fibonacci, 1202)

Talrækken vokser eksponentielt: F(n)/F(n-1) går imod 1.618... (det gyldne snit = (1+ )/2)5

Page 42: Rekursion og algoritmedesign

42

int F(int n) {

return n <= 1 ? n : F(n-1) + F(n-2);

}

Rekursiv metode til beregning af F(i)

Ineffektiviteten skyldes, at de samme delproblemer løses mange gange.

F.eks. F(9) = F(8) + F(7) = F(7) + F(6) + F(7) = F(6) + F(5) + F(6) + F(6) + F(5)

Simpel, men meget ineffektiv. Antallet af kald, C(n), tilfredsstiller rekursionligningerne

C(n) = C(n-1) + C(n-2) + 1, C(1) = C(0) = 1

som har løsningen C(n) = F(n+2) + F(n-1) - 1.C(n) er altså større end det Fibonacci-tal, der skal beregnes!

Page 43: Rekursion og algoritmedesign

43

Vedligehold en tabel (indiceret ved parameterværdien) indeholdende * 0, hvis den rekursive metode endnu ikke er kaldt med denne

parameterværdi * ellers det resultat, der skal returneres

Første kald af metoden for en given parameterværdi: beregn som før, men gem desuden resultatet.

Efterfølgende kald med samme parameterværdi: returner resultatet fra det første kald.

Undgå genberegninger(benyt “caching”)

int F(int n) { if (Fknown[n] != 0) return Fknown[n]; int r = n <= 1 ? n : F(n-1) + F(n-2); Fknown[n] = r; return r; }

Page 44: Rekursion og algoritmedesign

44

Effektivitet

Køretid: lineær

34

21

13

85

3

2

1

13

8

53

2

1

1

Husk kendte resultater:

1 0

Simpel rekursiv metode: F(9)34

21

13

8

5 3 3

5

23 2 2 1

1 1 1 1 1

2

1 1

1

8

5

3

1 1 2

1 1

12

1 1

3

1

13

8 5

5

32

1 1

1 1 1

3 3

2

121

1 2 11 1

2

1 1

2

1 1

1 0

1 0 1 0 1 0

1 0

1 0

1 0

1 0

1 0 1 0 1 0

1 0

Køretid: eksponentiel

Page 45: Rekursion og algoritmedesign

45

Eksempel: Dynamisk programmering til beregning af Fibonacci-tal:

F[0] = 0; F[1] = 1;for (i = 2; i <= n; i++) F[i] = F[i-1] + F[i-2];

Tidsforbruget er lineært.

Dynamisk programmering (traditionel):

* Tabellæg løsningerne til delproblemerne

* Opbyg tabellen i stigende orden af problemstørrelse

* Benyt tidligere tabelindgange til at beregne senere tabelindgange

Bund-til-top-tilgang

Page 46: Rekursion og algoritmedesign

46

I Finonacci-algoritmen kan tabellen afskaffes:

F = 1; Fprev = 0;

for (i = 2; i <= n; i++)

{ F += Fprev; Fprev = F - Fprev; }

Afskaffelse af tabel

Faktisk kan F(n) beregnes i logaritmisk tid:

F(n) = round

1+ 52

⎛ ⎝ ⎜

⎞ ⎠ ⎟

n

−1− 5

2⎛ ⎝ ⎜

⎞ ⎠ ⎟

n

5

⎜ ⎜ ⎜ ⎜

⎟ ⎟ ⎟ ⎟

Page 47: Rekursion og algoritmedesign

47

Optimal møntveksling

Udbetal et beløb i mønter, således at antallet af mønter er minimalt.

Eksempel: Hvis beløbet skal udbetales i amerikanske cents, kan mønterne 1-, 5-, 10- og 25-cent benyttes. Veksling af 63 cents kan da foretages med 6 mønter, nemlig to 25-cent, en 10-cent og tre 1-cent.

For disse mønter vil en grådig algoritme altid give en optimal løsning. Men hvis der f.eks. indføres en 21-cent-mønt, virker denne metode ikke.

Page 48: Rekursion og algoritmedesign

48

Rekursiv top-til-bund-løsning

int coins[] = {1, 5, 10, 21, 25};

int makeChange(int change) {

if (change == 0) return 0;

int min = Integer.MAX_VALUE;

for (int i = 0; i < coins.length; i++)

if (change >= coins[i])

min = Math.min(min, 1 + makeChange(change - coins[i]));

return min; }

Benyt ikke denne algoritme! Eksponentielt tidsforbrug. Undgå genberegninger.

Beregn for hver mønt det minimale antal mønter, der kan benyttes til at veksle det resterende beløb.Tag minimum.

Page 49: Rekursion og algoritmedesign

49

int makeChange(int change) {

if (change <= 0)

return 0;

if (minKnown[change] > 0)

return minKnown[change];

int min = Integer.MAX_VALUE;

for (int i = 0; i < coins.length; i++)

if (change >= coins[i])

min = Math.min(min,

1 + makeChange(change - coins[i]));

minKnown[change] = min;

return min;

}

Brug af kendte løsninger

Page 50: Rekursion og algoritmedesign

50

int makeChange(int change) {

if (change <= 0)

return 0;

if (minKnown[change] > 0)

return minKnown[change];

int min = Integer.MAX_VALUE, minCoin = 0, m;

for (int i = 0; i < coins.length; i++)

if (change >= coins[i]) {

m = 1 + makeChange(change - coins[i]);

if (m < min)

{ min = m; minCoin = coins[i]; }

}

lastCoin[change] = minCoin;

minKnown[change] = min;

return min;

}

Udskrivning af mønterne i en optimal veksling

for ( ; change > 0; change -= lastCoin[change]) System.out.println(lastCoin[change]);

Page 51: Rekursion og algoritmedesign

51

int makeChange(int change) { minKnown[0] = 0; for (int c = 1; c <= change; c++) { int min = Integer.MAX_VALUE, minCoin = 0, m; for (int i = 0; i < coins.length; i++) if (c >= coins[i]) { m = 1 + minKnown[c - coins[i]]; if (m < min) { min = m; minCoin = coins[i]; } } lastCoin[c] = minCoin; minKnown[c] = min; } return minKnown[change];}

Bund-til-top-løsning(uden rekursion)

Brug fundne løsninger til at bestemme den næste løsning.

Køretiden er proportional med change*coins.length.

Page 52: Rekursion og algoritmedesign

52

Baksporing (engelsk: backtracking)

Benyt rekursion til at prøve alle muligheder.

Page 53: Rekursion og algoritmedesign

53

Søgning i en labyrint

WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW******W W W W W W W W W WWWWWW*W W W W W W W W WWWWWWWWWWW***** WW W WWWW WWW W WWWWWW W W WW*WWWWWWWW W W W W**** W********W WWWWWWW* W W W W**W*W**WWWWW** WW*WWWWWWWWWWWW WW WW*W*W*WWWW*** W WWWW WW********************W*W*W *WWWW W WWWWWWWWWWWWWWWWWWWWW W***W W****W W WW WW W WWWWWWWWWWW*WWWWWWWWW WWWWWWWWWW WWWW WWWWW W W*W***WW WW W W W ***W*****WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW

Løsning

Slut

Start

Start

Slut

ProblemWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW W W W W W W W W W WWWWWW W W W W W W W W WWWWWWWWWWW WW W WWWW WWW W WWWWWW W W WW WWWWWWWW W W W W W W WWWWWWW W W W W W W WWWWW WW WWWWWWWWWWWW WW WW W W WWWW W WWWW WW W W W WWWW W WWWWWWWWWWWWWWWWWWWWW W W W W W WW WW W WWWWWWWWWWW WWWWWWWWW WWWWWWWWWW WWWW WWWWW W W W WW WW W W W WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW

Page 54: Rekursion og algoritmedesign

54

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

Page 55: Rekursion og algoritmedesign

55

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++) System.out.println(solution[x]);}

Page 56: Rekursion og algoritmedesign

56

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: newx = x; newy = y - 1; break;

case 2: newx = x - 1; newy = y; break;

case 3: newx = x; newy = y + 1; break;

case 4: newx = x + 1; newy = y; break;

}

if (newx >= 0 && newx < solution.length &&

newy >= 0 && newy < solution[x].length &&

solution[newx].charAt(newy) == ' ')

visit(newx, newy);

}

if (!solutionFound)

solution[x].setCharAt(y, ' ');

}

Page 57: Rekursion og algoritmedesign

57

Kryds-og-bolle (Engelsk: Tic-tac-toe)

...

...

............

Bolle vinder Uafgjort Kryds vinder

Page 58: Rekursion og algoritmedesign

58

class TicTacToe { public static final int HUMAN = 0; public static final int COMPUTER = 1; public static final int EMPTY = 2;

public static final int HUMAN_WIN = 0; public static final int DRAW = 1; public static final int UNCLEAR = 2; public static final int COMPUTER_WIN = 3;

public TicTacToe() { clearBoard( ); }

public Best chooseMove(int side) { ... } public boolean playMove(int side, int row, int column) { ... } public void clearBoard() { ... } public boolean boardIsFull() { ... } boolean isAWin(int side) { ... }

private int[][] board = new int[3][3]; private void place(int row, int column, int piece) { ... } private boolean squareIsEmpty(int row, int column) { ... } private int positionValue() { ... }}

Page 59: Rekursion og algoritmedesign

59

final class Best { int val; int row; int column;

public Best(int v, int r, int c) { val = v; row = r; column = c; }

public Best(int v) { this(v, 0, 0); }}

Page 60: Rekursion og algoritmedesign

60

Mini-max strategien

1. Hvis stillingen er en slutstilling, så returner dens værdi.

2. Ellers, hvis det er computeren til at trække,så returner den maksimale værdi af alle destillinger, der fremkommer ved at udføre ettræk. Værdierne beregnes rekursivt.

3. Ellers, hvis det er mennesket til at trække,så returner den minimale værdi af alle destillinger, der fremkommer ved at udføre et træk.

Værdierne beregnes rekursivt.

Page 61: Rekursion og algoritmedesign

61

public Best chooseMove(int side) { int bestRow = 0, bestColumn = 0; int value, opp;

if ((value = positionValue()) != UNCLEAR) return new Best(value); if (side == COMPUTER) { opp = HUMAN; value = HUMAN_WIN; } else { opp = COMPUTER; value = COMPUTER_WIN; } for (int row = 0; row < 3; row++) for (int column = 0; column < 3; column++) if (squareIsEmpty(row, column)) { place(row, column, side); Best reply = chooseMove(opp); place(row, column, EMPTY); if (side == COMPUTER && reply.val > value || side == HUMAN && reply.val < value) { value = reply.val; bestRow = row; bestColumn = column; } } return new Best(value, bestRow, bestColumn);}

Page 62: Rekursion og algoritmedesign

62

• Læs kapitel 8 og 9 i lærebogen (side 223-279)

• Løs følgende opgaver

3-1. Opgave 7.15.

3-2. Opgave 7.18.

3-3. Opgave 7.19.

3-4. Opgave 7.20.

3-5. Opgave 7.25.

3-6. Opgave 7.29.

Ugeseddel 325. september - 2. oktober


Recommended