Rekursjon. Hanois tårn. Milepeler for å løse problemet

Rekursjon. Hanois tårn. Milepeler
for å løse problemet
Hanois tårn. Milepeler for å løse problemet
ñ
Forstå spillet
ñ
Bestemme/skjønne hvordan spillet løses
ñ
Lage en plan for hva programmet skal gjøre (med ord)
ñ
Lage en programskisse (tegning og kvasikode)
ñ
Dele opp problemet i deler som kan gjøres for seg
ñ
Kompilere og test første del
ñ
Utvide programmet stegvis og teste for hvert steg
21. mai 2015
2
Forstå spillet/problemet
Illustrasjonen er hentet fra www.wikipedia.org
ñ Bare en plate kan flyttes om gangen
ñ Platen som flyttes må flyttes til en av de andre pinnene
ñ En plate kan ikke legges over en mindre plate
21. mai 2015
3
Om tårnet i Hanoi eller Hanois tårn
ñ
Legenden
ñ
ñ
ñ
ñ
Hindutempel med
3 påler, 64 gullplater med hull i midten
Når alle 64 plater er flyttet til en annen påle vil
templet og verden gå under
Minst 18.446.744.073.709.551.615 enkeltflyttinger
ñ
Edouard Lucas 1883
ñ
Spillet
ñ
Det finnes flere algoritmer
21. mai 2015
4
Lage en plan for hva programmet skal
gjøre (med ord)
Løsningen av spillet er en liste av trekk eller enkeltflytninger. Et trekk
er entydig bestemt hvis det angis fra hvilken pinne det flyttes til
hvilken pinne, siden det bare er lov å flytte platen som ligger øverst.
Hvis de tre pinnene heter 1 (fra), 2 (via), og 3 (til). Og spillet går ut på
å flytte to plater fra pinne 1 til pinne 3, er løsningen denne lista med
3 enkeltflytninger:
Flytt en plate fra 1 til 2
Flytt en plate fra 1 til 3
Flytt en plate fra 2 til 3
Lag først en metode som kan flytte en plate. Lag så en som kan flytte
to plater vha den første. Så en som kan flytte tre plater.
21. mai 2015
5
Metoden som kan flytte tre plater lages slik:
Vha metoden som kan flytte 2 plater flytter vi disse til pinne 2 (via).
Så flytter vi den største plata (nr 3) fra pinne 1 (fra) til pinne 3 (til). Til
sist flytter vi de 2 platene fra pinne 2 (via) til pinne 3 (til) vha
metoden som kan flytte 2.
Så lager vi en ny metode som kan flytte 4 plater. Denne bruker
metoden vi nettopp laget som kan flytte 3 plater. Slik fortsetter vi, til
vi har laget metoden som flytter så mange plater som vi ønsker. I
følge legenden 64.
Først ser vi på en alternativ løsning fra nettet:
21. mai 2015
6
Ikke-rekursiv algoritme som løser Hanois
tårn med n plater
Definerer en positiv retning:
P 1 ⇒ P 2 ⇒ P 3 ⇒ P 1 hvis antall plater er et ulikt tall
P 1 ⇒ P 3 ⇒ P 2 ⇒ P 1 hvis antall plater er et partall
Algoritme:
Gå i løkke:
1. Flytt den minste platen en pinne i postiv retning
2. Gjør det eneste andre lovlige trekket (enkeltflytt)
som ikke involverer den minste platen.
Avslutt løkka hvis 2 ikke var mulig,
hvis 2 kunne gjøres, fortsett
slutt
21. mai 2015
7
I kvasikode:
while ( !ferdig ) {
1. Flytt den minste platen en pinne i postiv retning
2. Hvis mulig, gjør det lovlige trekket mellom de to
andre pinnene (ikke pinnen med den minste platen)
Hvis trekk 2 ikke var mulig { ferdig = true }
}
Det er lett å lage en datamodell og et javaprogram som utfører
algoritmen ved å angi hvilke enkeltflytt som skal gjøres.
Det er derimot vanskelig å skjønne at dette programmet løser Hanois
tårn med n plater. (Beviset for det går langt utover det vi skal lære i
INF1010. Interesserte kan finne bevis på nettet, f.eks.
http://www.cs.yorku.ca/ andy/courses/3101/lecture-notes/LN0.html)
21. mai 2015
8
Top-down programmeringsutvikling
Først skriver vi et program som løser det legendariske
problemet med 64 plater. 64 er mer konkret enn n og
lettere å forholde seg til. Vi vet allerede nå at løsningen på
oppgaven er en sekvens (liste) av (lovlige) flytt.
Lage en metode som kan flytte 64 plater ved hjelp av to
andre metoder:
1. en metode som kan flytte en plate
2. en metode som kan flytte 63 plater
21. mai 2015
9
public s t a t i c void flytt64 ( int fra , int t i l , int via ) {
flytt63 ( fra , via , t i l ) ;
f l y t t 1 ( fra , t i l , via ) ;
flytt63 ( via , t i l , fra ) ;
}
public s t a t i c void f l y t t 1 ( int fra , int t i l , int via ) {
System . out . println ( ‘ ‘ F l y t t e r en plate fra : ‘ ‘
+ fra + ‘ ‘ t i l : ‘ ‘ + t i l ) ;
}
public s t a t i c void flytt63 ( int fra , int t i l , int via ) { . . . }
21. mai 2015
10
Hvis flytt63 gjør det den skal vil dette virke. Vi må da
programmere flytt63, og gjør det over samme lest:
public s t a t i c void flytt63 ( int fra , int t i l , int via ) {
flytt62 ( fra , via , t i l ) ;
f l y t t 1 ( fra , t i l , via ) ;
flytt62 ( via , t i l , fra ) ;
}
Vi skjønner mønsteret (vi trenger flytt62, flytt61, ...., flytt2
også) og hvis vi behersker en teksteditor godt, bruker vi
ikke lang tid på å lage programmet:
21. mai 2015
11
public s t a t i c void flytt64 ( int fra , int t i l , int via ) {
flytt63 ( fra , via , t i l ) ;
f l y t t 1 ( fra , t i l , via ) ;
flytt63 ( via , t i l , fra ) ;
}
public s t a t i c void flytt63 ( int fra , int t i l , int via ) {
flytt62 ( fra , via , t i l ) ;
f l y t t 1 ( fra , t i l , via ) ;
flytt62 ( via , t i l , fra ) ;
}
...
21. mai 2015
12
// 60 metoder u t e l a t t ( flytt62 . . . tilogmed . . . f l y t t 3 ) )
public s t a t i c
f l y t t 1 ( fra ,
f l y t t 1 ( fra ,
f l y t t 1 ( via ,
}
void
via ,
til ,
til ,
f l y t t 2 ( int fra , int t i l , int via ) {
til );
via ) ;
fra ) ;
public s t a t i c void f l y t t 1 ( int fra , int t i l , int via ) {
System . out . println ( ‘ ‘ F l y t t e r en plate fra : ‘ ‘
+ fra + ‘ ‘ t i l : ‘ ‘ + t i l ) ;
}
21. mai 2015
13
Denne måten å utvikle programmet på (vi bruker metoder vi ikke har
programmert ennå) kalles top-down, siden vi begynner i den
komplekse enden (løser problemet for 64 plater og antar vi har
metoder som løser de enklere oppgavene før vi har laget dem).
ñ
lag først en metode som kan flytte 64 plater (vha
flytt63)
ñ
så en som kan flytte 63 ved hjelp av den som kan
flytte 62
ñ
så en som kan flytte 62 ....
ñ
...
ñ
så en som kan flytte 2 ved hjelp av en som kan flytte 1
ñ
tilslutt den som flytter 1 plate
21. mai 2015
14
bottom-up programutvikling
Vi kan også tenke omvendt. Den utviklingsmetoden kalles
bottom-up:
ñ
lag først en metode som kan flytte 1 plate
ñ
ved hjelp av den lager vi metoden som kan flytte 2
ñ
så en som kan flytte 3 ved hjelp av metoden som
flytter 2
ñ
...
ñ
så en som kan flytte 63 ved hjelp av den som kan
flytte 62
ñ
tilslutt en som kan flytte 64
Programmet blir det samme uansett utviklingsmetode.
21. mai 2015
15
Rekursiv metode som kan flytte n plater
Mens vi har laget 64 nesten like metoder, har vi fått ideen om å la
antall plater som skal flyttes være en parameter til metoden, la oss
kalle antallet n, og de tre pinnene henholdsvis frapinnen, tilpinnen
og viapinnen:
1. flytt n − 1 plater fra frapinnen til viapinnen
2. flytt 1 plate fra frapinnen til tilpinnen
3. flytt n − 1 plater fra viapinnen til tilpinnen
Hvis algoritmen klarer å flytte n − 1 plater riktig, skulle dette fungere
for n plater (vi har programmert slik). Vi sørger så for at metoden
virker for n = 1 (basistilfellet) og metoden vil være korrekt (ved
induksjon).
21. mai 2015
16
Vi skriver algoritmen i Java
public s t a t i c void f l y t t ( int ant , int pinne1 , int pinne3 , int pin
i f ( ant == 0) {
} else {
f l y t t ( ant − 1 , pinne1 , pinne2 , pinne3 ) ;
f l y t t 1 ( pinne1 , pinne3 ) ;
f l y t t ( ant − 1 , pinne2 , pinne3 , pinne1 ) ;
}
}
public s t a t i c void f l y t t 1 ( int pinne1 , int pinne2 ) {
a n t a l l F l y t t ++;
System . out . println ( " F l y t t e r en plate fra pinne "
+ fra + " t i l pinne " + pinne2 ) ;
}
21. mai 2015
17
Utvide programmet stegvis og teste for
hvert steg
Hva er basistilfellet og er de rekursive kallene nærmere dette?
Hvordan representerer vi pinner og plater i programmet
(datamodellen)?
Vi har her utviklet et program som lager en liste over enkeltflytninger
som må gjøres for å løse oppgaven. En naturlig utvidelse (er vi nå
bottom-up eller top-down?) er at vi ønsker at vi istedet skal se
trekkene bli utført grafisk på skjermen. Til det trenger vi et utsyn
som kan tegne opp de tre pålene eller pinnene, med platene på.
Da kan det kanskje være hensiktsmessig å ha en modell av
situasjonen i spillet. Ved å kalle på en passende metode i utsynet fikk
vi så tegnet situasjonen på skjermen.
21. mai 2015
18
I stedet for å skrive ut enkeltflytningene, kan man lage kommandoer
(metodekall) til modellen som foretar den tilsvarende endringen.
I dette eksemplet er datamodellen så enkel, at det er lett å klare seg
uten en egen modellklasse og la utsynet lese datastrukturen direkte.
En annen naturlig utvidelse er å la brukeren få velge antall plater i
spillet. Vi kan også tenke oss muligheten av at brukeren får spille
spillet ved å angi hvilken plate som skal flyttes.
Hvis andre har laget en spesifikasjon (eller prekode) er det på dette
stadiet (når vi vet vi har løst selve kjernen av problemet) naturlig å se
på denne igjen og starte med å innarbeide den til et ferdig program.
21. mai 2015
19