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
© Copyright 2024