Click here to load reader

Rekursion og algoritmedesign

  • View
    25

  • Download
    1

Embed Size (px)

DESCRIPTION

Rekursion og algoritmedesign. Plan. • Rekursion Matematisk induktionRekursion Simple eksempler Kryptering. • Designmetoder Del-og-hersk Dynamisk programmering Baksporing. Tre centrale begreber. (1) Syntaks Grammatik (udseende) - PowerPoint PPT Presentation

Text of Rekursion og algoritmedesign

IntroduktionMetoder 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.
*
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.
*
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
*
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.
*
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),
*
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();
return binarySearch(a, key, mid + 1, high);
if (a[mid].compares(key) > 0)
return binarySearch(a, key, low, mid - 1);
return mid;
private int binarySearch(Comparable[] a, Comparable key,
int low, int high) throws ItemNotFound {
...
}
throws ItemNotFound {
}
*
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.
*
Problem. Beregn power(x,n) = xn, hvor x er et reelt tal,
og n er et positivt heltal.
Simpel løsning:
power = x;
power *= x;
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.
double power(double x, int n) {
if (n == 1)
Beregning af den maksimale delsekvenssum
Enten findes den maksimale delsekvens
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.
(2) helt i den højre del, eller
(3) indeholder midterelementet
(1) helt i den venstre del,
*
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
if (left == right)
int mid = (left + right)/2;
maxSum(a, mid + 1, right),
}
int sum = 0, maxSum = 0;
for (int i = right; i >= left; i--)
if ((sum += a[i]) > maxSum)
maxSum = sum;
return maxSum;
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).
*
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
*
elegante løsninger på problemer, der ellers er svære at løse
algoritmer, der er simple at analysere
*
int faculty(int n) {
if (n == 1)
n! ={
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).
*
int b1, b2, b3;
(fra et munkekloster i Tibet)
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.
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
if (n == 0)
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.
Samlet tidsforbrug for 64 skiver, hvis hver flytning tager 1 sekund:
264 sekunder ≈ 1019 sekunder ≈ 1012 år
*
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.
*
Udskriv n = 7913
void printDecimal(int n) {
if (n >= 10)
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
(ved brug af eksplicit stak)
*
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
(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).
*
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).
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.
*
(2) N = p*q = 3713
(3) N’ = (p-1)*(q-1) = 3588
(4) e = 37 (gcd(N’, e) = 1)
*
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
0120200103110001200004012314
(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)).
*
long x = 10000, y = 50000, message = 123456789;
long p, q, n, nPrime, e, d;
for (p = x; !Numerical.isPrime(p); p++)
;
;
nPrime = (p - 1)*(q - 1);
;
if (message != decode)
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.
*
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.
*
Top-til-bund implementering er dog også mulig.
*
F(0) = 0,
F(1) = 1
Simpelt eksempel
5
*
}
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.
C(n) = C(n-1) + C(n-2) + 1,
*
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”)
Fknown[n] = r;
* Benyt tidligere tabelindgange til at beregne senere tabelindgange
Bund-til-top-tilgang
F[0] = 0; F[1] = 1;
for (i = 2; i <= n; i++)
F[i] = F[i-1] + F[i-2];
Tidsforbruget er lineært.
F = 1; Fprev = 0;
{ F += Fprev; Fprev = F - Fprev; }
Afskaffelse af tabel
*
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.
*
int makeChange(int change) {
int min = Integer.MAX_VALUE;
if (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.
if (change >= coins[i])
for (int i = 0; i < coins.length; i++)
if (change >= coins[i]) {
if (m < min)
}
for ( ; change > 0; change -= lastCoin[change])
System.out.println(lastCoin[change]);
Køretiden er proportional med change*coins.length.
int makeChange(int change) {
minKnown[0] = 0;
int min = Integer.MAX_VALUE, minCoin = 0, m;
for (int i = 0; i < coins.length; i++)
if (c >= coins[i]) {
if (m < min)
}
*
******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 W W W ***W*****
WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW
Løsning
Slut
Start
Start
Slut
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 W W W W
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 W W W W ",
"WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"};
static int startX = 1, startY = 0, endX = 11, endY = 40;
static boolean solutionFound = false;
for (int x = 0; x < problem.length; x++)
solution[x] = new StringBuffer(problem[x]);
visit(startX, startY);
if (solutionFound)
System.out.println(solution[x]);
solution[x].setCharAt(y,'*');
for (int d = 1; d <= 4 && !solutionFound; d++) {
int newx, newy;
}
newy >= 0 && newy < solution[x].length &&
solution[newx].charAt(newy) == ' ')
public TicTacToe() { clearBoard( ); }
public void clearBoard() { ... }
public boolean boardIsFull() { ... }
boolean isAWin(int side) { ... }
private void place(int row, int column, int piece) { ... }
private boolean squareIsEmpty(int row, int column) { ... }
private int positionValue() { ... }
{ val = v; row = r; column = c; }
public Best(int v)
{ this(v, 0, 0); }
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 de
stillinger, der fremkommer ved at udføre et
træk. Værdierne beregnes rekursivt.
så returner den minimale værdi af alle de
*
int value, opp;
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);
side == HUMAN && reply.val < value) {
}
*
• 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.