Kapittel 12: Rekursjon



Like dokumenter
Kapittel 12: Rekursjon

Rekursiv programmering

Kapittel 9: Sortering og søking Kort versjon

Rekursjon som programmeringsteknikk

Kapittel 7: Mer om arv

LITT OM OPPLEGGET. INF1000 EKSTRATILBUD Stoff fra uke September 2012 Siri Moe Jensen EKSEMPLER

INF1000 Metoder. Marit Nybakken 16. februar 2004

INF1010 notat: Binærsøking og quicksort

Rekursjon. Binærsøk. Hanois tårn.

Kapittel 8: Programutvikling

En implementasjon av binærtre. Dagens tema. Klassestruktur hovedstruktur abstract class BTnode {}

Algoritmer - definisjon

Algoritmer og datastrukturer Kapittel 9 - Delkapittel 9.2

Gjennomgang prøveeksamen oppgave 1, 2, 4, 5, 7

Kapittel 15: Grafiske brukergrensesnitt: Enkel GUI. Del I

Utførelse av programmer, metoder og synlighet av variabler i JSP

UNIVERSITETET I OSLO

Introduksjon til objektorientert. programmering. Hva skjedde ~1967? Lokale (og globale) helter. Grunnkurs i objektorientert.

"behrozm" Oppsummering - programskisse for traversering av en graf (dybde først) Forelesning i INF februar 2009

Tre måter å lese fra terminal. Java 4. Eksempel. Formatert utskrift til skjerm

Sortering med tråder - Quicksort

INF1010. Rekursjon En rekursiv definisjon av rekursjon, slik det kunne stå i en ordbok: Introduksjon til Rekursiv programmering

EKSAMEN. Dato: 18. mai 2017 Eksamenstid: 09:00 13:00

Løse reelle problemer

Kapittel 1: Datamaskiner og programmeringsspråk

UNIVERSITETET I OSLO

Algoritmer og datastrukturer Kapittel 1 - Delkapittel 1.3

Algoritmer og datastrukturer Kapittel 9 - Delkapittel 9.1

NITH PG4200 Algoritmer og datastrukturer Løsningsforslag Eksamen 4.juni 2013

Rekursiv programmering

UNIVERSITETET I OSLO

Algoritmer og datastrukturer Kapittel 1 - Delkapittel 1.8

EKSAMEN. Dato: 9. mai 2016 Eksamenstid: 09:00 13:00

Dagens tema: 12 gode råd for en kompilatorskriver. Sjekking av navn. Lagring av navn. Hvordan finne et navn?

Innlesning fra tastatur med easyio. INF1000 høst Vi må først skrive i toppen av programmet: import easyio.*;

Sorteringsproblemet. Gitt en array A med n elementer som kan sammenlignes med hverandre:

Programmering Høst 2017

KAPITTEL 3 Litt logikk og noen andre småting

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

TOD063 Datastrukturer og algoritmer

i=0 Repetisjon: arrayer Forelesning inf Java 4 Repetisjon: nesting av løkker Repetisjon: nesting av løkker 0*0 0*2 0*3 0*1 0*4

Forelesning inf Java 4

Logaritmiske sorteringsalgoritmer

UNIVERSITETET I OSLO

INF1000 EKSTRATILBUD. Stoff fra uke 1-5 (6) 3. oktober 2012 Siri Moe Jensen

Antall sider (inkl. forsiden): 7. Alle trykte og håndskrevne

Forkurs INF1010. Dag 1. Andreas Færøvig Olsen Tuva Kristine Thoresen

Quicksort. Fra idé til algoritme.

Dagens temaer. Sortering: 4 metoder Søking: binærsøk Rekursjon: Hanois tårn

Algoritmer og Datastrukturer

INF1010 Sortering. Marit Nybakken 1. mars 2004

Algoritmeanalyse. (og litt om datastrukturer)

INF Notater. Veronika Heimsbakk 10. juni 2012

Java-kurs. Andreas Knudsen Nils Grimsmo 9th October 2003

Løsningsforslag EKSAMEN

UNIVERSITETET I OSLO

Algoritmer og datastrukturer Kapittel 11 - Delkapittel 11.2

BOKMÅL Side 1 av 5. KONTERINGSEKSAMEN I FAG TDT4102 Prosedyre og objektorientert programmering. Onsdag 6. august 2008 Kl

Leksjon 3. Kontrollstrukturer

Hvorfor sortering og søking? Søking og sortering. Binære søketrær. Ordnet innsetting forbereder for mer effektiv søking og sortering INF1010 INF1010

Hva er verdien til variabelen j etter at følgende kode er utført? int i, j; i = 5; j = 10; while ( i < j ) { i = i + 2; j = j - 1; }

TDT4102 Prosedyreog objektorientert programmering Vår 2016

Rekursjon. (Big Java kapittel 13) Fra Urban dictionary: recursion see recursion. IN1010 uke 8 våren Dag Langmyhr

Fra Python til Java. En introduksjon til programmeringsspråkenes verden. Dag Langmyhr

INF2220: Forelesning 7. Kombinatorisk søking

public static <returtype> navn_til_prosedyre(<parameter liste>) { // implementasjon av prosedyren

Oppgavesettet består av 7 sider, inkludert denne forsiden. Kontroll& at oppgaven er komplett før du begynner å besvare spørsmålene.

Rekursjon. (Big Java kapittel 13) Fra Urban dictionary: recursion see recursion. IN1010 uke 8 våren Dag Langmyhr

Oblig 4 (av 4) INF1000, høsten 2012 Værdata, leveres innen 9. nov. kl

Klasser, objekter, pekere og UML. INF gruppe 13

public static <returtype> navn_til_prosedyre(<parameter liste>) { // implementasjon av prosedyren

Kompilering Statiske Syntaksanalyse Feilsjekking Eksempel Oppsummering

<?php. count tar en array som argument, og returnerer et tall som uttrykker antallet innførsler i arrayen.

EKSAMEN. Algoritmer og datastrukturer. Eksamensoppgaven: Oppgavesettet består av 11 sider inklusiv vedlegg og denne forsiden.

Konstruktører. Bruk av konstruktører når vi opererer med "enkle" klasser er ganske ukomplisert. Når vi skriver. skjer følgende:

Oppgave 1 a. INF1020 Algoritmer og datastrukturer. Oppgave 1 b

UNIVERSITETET I OSLO

Kapittel 13: Grafiske brukergrensesnitt INF 100. Java som første programmeringsspråk

TDT4100 Objektorientert programmering

EKSAMEN I FAG TDT4100 Objekt-orientert programmering. Fredag 3. juni 2005 KL

IN1010. Fra Python til Java. En introduksjon til programmeringsspråkenes verden Dag Langmyhr

TOD063 Datastrukturer og algoritmer

UNIVERSITETET I OSLO

Repetisjon: Statiske språk uten rekursive metoder (C1 og C2) Dagens tema Kjøresystemer (Ghezzi&Jazayeri 2.6, 2.7)

2 Om statiske variable/konstanter og statiske metoder.

Dagens tema Kjøresystemer (Ghezzi&Jazayeri 2.6, 2.7)

Transkript:

Kapittel 12: Rekursjon Redigert av: Khalid Azim Mughal (khalid@ii.uib.no) Kilde: Java som første programmeringsspråk (3. utgave) Khalid Azim Mughal, Torill Hamre, Rolf W. Rasmussen Cappelen Akademisk Forlag, 2006. ISBN: 82-02-24554-0 http://www.ii.uib.no/~khalid/jfps3u/ (NB! Boken dekker opptil Java 6, men notatene er oppdatert til Java 7.) 12/7/2012 12: Rekursjon 12-1/39 Emneoversikt Rekursive definisjoner Rekursjon som problemløsningsteknikk Utforming av rekursjon Nøsting av metodekall Rekursive algoritmer Metodeaktiviseringer Rekursjonsdybde Evig rekursjon (rekursjonsbrønn) Forskjellige typer rekursjon: Direkte rekursjon Indirekte rekursjon Enkel rekursjon Hale-rekursjon Iterasjon kontra rekursjon Eksempel: Fakultet-funksjon Binært søk Tårn i Hanoi Quicksort Fibonacci JFPS3U 12: Rekursjon 12-2/39

Rekursjon To iterate is human; to recurse, divine. L. Peter Deutsch, Robert Heller Rekursjon oppstår når en operasjon benytter seg selv som en del av sin egen utførelse. Kan brukes på problemer som kan deles opp i mindre utgaver av seg selv. Problem: Finn ut hva fotokromi betyr, ved hjelp av ordbok: fotokromi: metode til fremstilling av fargetrykk i litografi. Delproblem: Finn ut hva litografi betyr, ved hjelp av ordbok: litografi: grafisk trykkemetode hvorved en tegning eller skrift blir overført på en spesiell slags stein for derpå å trykkes på papir. Del-delproblem: Finn ut hva grafisk betyr, ved hjelp av ordbok... Hver deloperasjon må fullføres før operasjonen over kan fullføres. For at rekursiv operasjon skal kunne fullføres, må noen deloperasjoner kunne fullføres uten behov for flere deloperasjoner. JFPS3U 12: Rekursjon 12-3/39 Rekursive definisjoner Rekursiv definisjon: en definisjon som er definert ved hjelp av seg selv. Sett i ordliste: Rekursjon: se rekursjon. Eks: Definisjon på en setning i Java er en rekursiv definisjon: en for-løkke kan ha en for-løkke i løkke-kroppen, og denne indre løkken kan ha en for-løkke i sin løkkekropp, osv. for (...;...;...) { for (...;...;...) { for (...;...;...) {... Rekursjon er et velkjent begrep fra matematikken. Faktorial-funksjon kan uttrykkes ved hjelp av en rekursiv definisjon: 0! = 1 n! = n * (n-1)! 3! = 3 * 2! = 3 * 2 * 1! = 3 * 2 * 1 * 0! = 3 * 2 * 1 * 1 = 6 JFPS3U 12: Rekursjon 12-4/39

Rekursjon som problemløsningsteknikk Modellerer problemer som kan deles i mindre, enklere underoppgaver som ligner på hverandre. hver underoppgave kan løses ved å anvende samme teknikk. Hele oppgaven blir løst ved å kombinere løsninger til underoppgavene. en form for splitt og hersk. I Java modellerer vi dette med metoder som kaller seg selv. Rekursiv metode: En metode som er definert med kall til seg selv i metodekroppen. Krever et basistilfelle for å stoppe rekursjon. JFPS3U 12: Rekursjon 12-5/39 Utforming av rekursjon Logikken i metoden må føre til at et basistilfelle blir utført: sekvens av mindre og mindre oppgaver konvergerer til et basistilfelle. Dette oppnås vanligvis ved hjelp av en valgsetning, der en betingelse stopper rekursjon. Denne betingelsen er vanligvis avhengig av en parameter til metoden. Et eller flere tilfeller der returverdien beregnes uten bruk av rekursive kall. Disse tilfellene kalles for basistilfeller. Et eller flere tilfeller der returverdien beregnes ved rekursive kall. Rekursive kall løser enda mindre versjoner av samme oppgaven. JFPS3U 12: Rekursjon 12-6/39

Fakultet funksjon Problem: Gitt n parkeringsplasser og n biler, finn antall kombinasjoner (permutasjoner) for å parkere bilene. Iterativ definisjon: n! = n * (n-1) *... * 2 * 1 Rekursiv definisjon: n! = n * (n-1)! 0! = 1 Rekursive metodekall definerer funksjonen fakultet på samme måte som den matematiske definisjonen. fakultet(n) = n! Basistilfelle: For n = 0: fakultet(n) gir 1 Rekursivt kall: For n > 0: fakultet(n) gir (n * fakultet(n-1)) JFPS3U 12: Rekursjon 12-7/39 Eksempel: Iterativ utregning av fakultet // IterativFakultet.java -- Iterativ utregning av fakultet public class IterativFakultet { public static void main(string[] args) { System.out.println("5! = " + fakultet(5)); static int fakultet(int n) { int verdi = 1; for (int i=2; i<=n; i++) verdi *= i; return verdi; Utskrift ved kjøring: 5! = 120 JFPS3U 12: Rekursjon 12-8/39

Eksempel: Rekursiv utregning av fakultet // RekursivFakultet.java -- Rekursiv utregning av fakultet public class RekursivFakultet { public static void main(string[] args) { System.out.print("Skriv heltall for utregning av fakultet: "); Scanner tastatur = new Scanner(System.in); int n = tastatur.nextint(); System.out.println(n + "! = " + fakultet(n)); static int fakultet(int n) { if (n == 0) return 1; // 0! = 1 (basistilfelle) return n * fakultet(n-1); Kjøring av programmet: Skriv heltall for utregning av fakultet: 12 12! = 479001600 // n! = n * (n-1)! 4! = 4 * 3! 3! = 3 * 2! 2! = 2 * 1! 1! = 1 2! = 2 * 1 = 2 3! = 3 * 2 = 6 4! = 4 * 6 = 24 fakultet(4) 24 fakultet(3) fakultet(2) fakultet(1) 1 2 6 JFPS3U 12: Rekursjon 12-9/39 Nøsting av metodekall Rekursive metoder: Objekter kan sende (forskjellige) meldinger til seg selv, men må nå sende samme melding til seg selv. Metoden må nå også håndtere mindre versjoner av den opprinnelige oppgaven. I en rekursiv metode nøstes metodekallene til basistilfellet nås. Når basistilfellet returnerer, så begynner alle metodekallene å utnøstes. Hver nøstede utføring av metoden tar vare på sine egne lokale variabler. Disse variablene blir husket når programflyten returnerer fra et rekursivt metodekall. Tilordninger som gjøres på lokale variabler i nøstede metodekall, vil ikke direkte påvirke lokale variabler i ytre metodekall. JFPS3U 12: Rekursjon 12-10/39

Rekursive algoritmer Rekursiv algoritme: Har to primære arbeidsmåter: generelle tilfeller og basistilfeller Implementeres som en rekursiv metode. Oppgavens omfang: angis av parameterene til den rekursive metoden. avtar med hvert rekursive kall. Generelle tilfeller: Gjør oppgaver mindre ved å dele den opp. Benytter samme algoritme for å løse de mindre oppgavene. Løsningen blir funnet ved hjelp av delløsningene. Basistilfeller: Gir direkte løsninger for trivielle oppgaver. Ingen nye rekursive kall. Nødvendig for at de rekursive kallene skal slutte. Oppgavene konvergerer mot basistilfellet. JFPS3U 12: Rekursjon 12-11/39 Metodeaktivisering og rekursjonsdybde Konsekvenser av rekursjon: Mange aktiviseringer av en metode kan forekomme samtidig. Hver aktivisering har egne instanser av lokale variabler (inkl. parametere). Rekursjonsdybde: antall nøstede metodekall som gjøres før basistilfellet nås. Rekursjonsbrønn: rekursjon som aldri stopper. Skjer hvis en rekursiv metode ikke konvergerer mot et basistilfelle. Er en logisk programmeringsfeil. Programmer med slike brønner vil som oftest bryte sammen under kjøring på grunn av den uendelige nøstingen. JFPS3U 12: Rekursjon 12-12/39

Huskeregeler for bruk av rekursjon Kontroller at rekursjonsbrønn ikke forekommer. For hvert rekursive kall må metoden komme et skritt nærmere basistilfellet. Bunnen av rekursjonskallene er nådd når argumentene beskriver et basistilfelle. Et rekursivt kall må aldri resultere i at metoden blir kalt på nytt med samme argumenter. Kontroller at hvert basistilfelle utfører riktig handling. Kontroller at for hvert tilfelle som bruker rekursjon, dersom alle rekursive kall utfører sine handlinger riktig, så utføres hele tilfellet riktig. JFPS3U 12: Rekursjon 12-13/39 Forskjellige typer rekursjon Direkte rekursjon: Definisjon av en metode foo() inneholder (et eller flere) kall til metode foo(). Eksempel: alle eksempler gitt i notatet. Indirekte rekursjon: To eller flere metoder samarbeider rekursivt istedenfor bare én. Metoder kan være implementert i samme eller i forskjellige objekter. F.eks. definisjon av en metode foo() inneholder (et eller flere) kall til metode bar() (i samme eller forskjellige objekter) som i sin tur inneholder kall til metode foo(). Enkel rekursjon (simple recursion): Kall til en metode foo() fører til ett rekursivt kall av metode foo(), mao. ingen oppspalting av rekursjon hver underoppgave kaller kun én annen underoppgave. Eksempel på enkel rekursjon: Faktorial-funksjon, binært søking. Eksempel på oppspalting av rekursjon: Tårn i Hanoi, fibonacci tall. Halerekursjon (tail recursion): Definisjon av en metode er slik at alle rekursive kall forekommer som den siste setningen som utføres før retur fra metoden. Eksempel på halerekursjon: binært søking. JFPS3U 12: Rekursjon 12-14/39

Sammenligning av rekursjon og iterasjon Rekursjon er vanligvis mer kostbar pga. flere metodekall. Rekursjon er mer konsis og elegant for oppgaver som naturlig egner seg til denne teknikken. Enkel rekursjon og iterasjon er beregningsmessig ekvivalente (jfr. Faktorial-eksempel). Basert på en kontrollstruktur: Bruker repetisjon: Stoppkriteriet: Konvergens mot terminering: Iterasjon bruker en løkke-struktur (for, while, do-while) eksplisitt bruk av en løkkestruktur for repetisjon stopp når løkkebetingelsen blir usann kan føre til evig løkke teller-basert repetisjon konvergerer mot sluttverdi som terminerer løkken Rekursjon bruker en valg-struktur (if, ifelse, switch) gjentatte metodekall for å oppnå repetisjon stopp når et basistilfelle blir utført kan føre til rekursjonsbrønn stadig enklere versjoner av underoppgaver konvergerer mot et basistilfelle som terminerer rekursjon JFPS3U 12: Rekursjon 12-15/39 Binært søk Finner elementer i sorterte tabeller. Deler søkerommet i to for hvert skritt. Eliminerer halve søkerommet for hvert skritt. Kan løses både iterativt og rekursivt: Rekursiv binærsøk algoritme: Operasjon binærsøk(målelement, søkerom): Hvis søkerommet er tomt: Returner målet-finnes-ikke. Finn midterste-element i søkerommet. Hvis midterste-element == målelement: Returner posisjonen-til-målelementet. Hvis målelement < midterste-element: Returner binærsøk(målelement, søkerom-før-midterste-element). Ellers: Returner binærsøk(målelement, søkerom-etter-midterste-element). JFPS3U 12: Rekursjon 12-16/39

// FinnRekursivt.java -- Rekursivt binærsøk etter primtall public class FinnRekursivt { // Konstant som signaliserer at et tall ikke finnes i tabellen. static final int FINNES_IKKE = -1; public static void main(string[] args) { // Tabell med primtallene mellom 1 og 100 i sortert rekkefølge: int[] primtall = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97; System.out.print("Skriv heltall mellom 1 og 100: "); Scanner tastatur = new Scanner(System.in); int tall = tastatur.nextint(); if ((tall<1) (tall>100)) { System.out.println("Tallet var ikke mellom 1 og 100"); return; int primtallindex = finn(primtall, tall); if (primtallindex == FINNES_IKKE) { System.out.println(tall + " er ikke et primtall"); else { System.out.println(tall + " er primtall nummer " + (primtallindex + 1)); JFPS3U 12: Rekursjon 12-17/39 static int finn(int[] sorterttabell, int nøkkel) { return binærsøk(sorterttabell, nøkkel, 0, sorterttabell.length - 1); static int binærsøk(int[] sorterttabell, int mål, int nedre, int øvre) { if (nedre > øvre) { return FINNES_IKKE; // basistilfelle, tom søkerom. int midten = (nedre + øvre)/2; int midterstetall = sorterttabell[midten]; if (mål == midterstetall) { return midten; // Enda et basistilfelle. Målet ble funnet. // Generelt tilfelle: søk i enten nedre eller øvre tabellsegment. if (mål < midterstetall) { return binærsøk(sorterttabell, mål, nedre, midten-1); else { return binærsøk(sorterttabell, mål, midten+1, øvre); JFPS3U 12: Rekursjon 12-18/39

Java implementasjon av binærsøkalgoritme Finner heltall i en sortert tabell. Tabellen inneholder alle primtallene fra 1 til 100. For angitt heltall, slår opp i tabellen og rapporterer: om tallet er et primtall, med andre ord, finnes i tabellen. hvor langt ut i primtallrekken tallet er. Kjøring av programmet: Skriv heltall mellom 1 og 100: 73 73 er primtall nummer 21 Parameterene spesifiserer nedre og øvre grensene til søkerommet. Søkerommet halveres for hvert rekursivt kall. Binærsøkalgoritmen bruker halerekursjon. JFPS3U 12: Rekursjon 12-19/39 Søking etter tallet 73 ved kjøring: Søke eksempel 1 binsøk(sorterttabell, 73, 0, 24) nedre=0 midten=12 øvre=24 2 2 3 5... 37 41 43... 83 89 97 73 > 41 binsøk(sorterttabell, 73, 13, 24) nedre=13 midten=18 øvre=24 3 43 47 53 59 61 67 71 73 79 83 89 97 73 > 67 binsøk(sorterttabell, 73, 19, 24) midten=21 øvre=24 4 binsøk(sorterttabell, 73, 19, 20) nedre=19 midten=19 71 73 79 83 89 97 73 < 79 5 binsøk(sorterttabell, 73, 20, 20) nedre=19 71 73 73 > 71 midten=20 øvre=20 nedre=20 73 øvre=20 73 == 73 JFPS3U 12: Rekursjon 12-20/39

Tårn i Hanoi Spill oppfunnet av den franske matematiker Edouard Lucas i 1883. Tre pinner Ringer av forskjellige størrelser Pinne 1 Pinne 2 Pinne 3 Tårn av ringer Mål: flytt tårn med n ringer av forskjellige størrelser fra pinne 1 til pinne 3. Regel 1: Ringer kan ikke plasseres oppå mindre ringer. Regel 2: Kun én ring kan flyttes om gangen. JFPS3U 12: Rekursjon 12-21/39 Tårn i Hanoi (en ring) Spillet kan spilles med et hvilket som helst antall ringer. Hanoi-løsning for én ring: Løsning er triviell. (tenk basistilfelle) Ring kan flyttes direkte til målpinnen. JFPS3U 12: Rekursjon 12-22/39

Tårn i Hanoi (to ringer) Hanoi-løsning for to ringer: Nederste ring må havne nederst på målpinnen. (regel 1) Ringen som ligger oppå må flyttes vekk først. Flytt den lille ringen til en midlertidig pinne. Flytt den store ringen til målpinnen. Flytt den lille ringen fra den midlertidige pinnen oppå den store ringen på målpinnen. JFPS3U 12: Rekursjon 12-23/39 Tårn i Hanoi: rekursiv algoritme Løsninger følger et visst mønster. Blir tydelig når antall ringer øker. Kan beskrives som en rekursiv algoritme. Flytte et tårn fra en pinne til en annen: Flytt vekk alle ringene bortsett fra den nederste ringen til en temporær pinne. (et mindre Hanoi-spill) Flytt den nederste ringen til målpinnen. Flytt ringene fra den temporære pinnen til Løsning for to pinner målpinnen (et mindre Hanoi-spill) Hanoi-løsning for tre ringer: JFPS3U 12: Rekursjon 12-24/39

public class Hanoi { public static void main(string[] args) { int antallringer = 3; System.out.println("Flytter " + antallringer + " ringer:"); /* Flytt alle ringene fra pinne 1 til pinne 3 ved å bruke 2 som midlertidig lagringsplass. */ flyttringer(antallringer, 1, 3, 2); static void flyttringer(int antall, int fra, int til, int tmp) { if (antall < 1) return; // Basistilfelle: Ingen ringer å flytte. Flytter 3 ringer: 1 ---> 3 1 ---> 2 3 ---> 2 1 ---> 3 2 ---> 1 2 ---> 3 1 ---> 3 /* Først flyttes de antall-1 nederste ringene over til "tmp"-pinnen. Bruk "til"-pinnen som midlertidig lagringsplass: */ flyttringer(antall-1, fra, tmp, til); // Ringene over er fjernet, og den nederste ringen kan flyttes: System.out.println(fra + " ---> " + til); /* De antall-1 ringene på "tmp"-pinnen kan nå flyttes til "til"-pinnen. Bruk "fra"-pinnen som midlertidig lagringsplass: */ flyttringer(antall-1, tmp, til, fra); JFPS3U 12: Rekursjon 12-25/39 To rekursive kall per rekursjonsnivå. Metoden flyttringer() har følgende lokale variabler: antall: antall ringer i tårnet som skal flyttes i denne deloppgaven fra: tallet som identifiserer pinnen tårnet skal flyttes vekk fra til: tallet som identifiserer pinnen tårnet skal flyttes til tmp: tallet som identifiserer pinnen som kan brukes til midlertidig lagring av ringer Hver utføring av metoden har sin egen utgave av disse variablene. Tas vare på under rekursjonen. Når programflyten vender tilbake fra et rekursivt metodekall, vil variablene fremdeles være slik de var før det rekursive metodekallet. Under metodekall blir lokale variabler lagret i en implisitt stabel. (programstabel, eng. program stack) Hver metodeutføring har sin egen stabelramme på programstabelen: Inneholder de lokale variablene. Opprettes på programstabelen når en metode blir kalt. Fjernes når metoden returnerer. JFPS3U 12: Rekursjon 12-26/39

Programstabel som vokser og krymper flyttringer(3, 1, 3, 2) antall=3 fra=1 antall=2 til=3 fra=1 antall=1 tmp=2 til=2 tmp=3 fra=1 til=3 tmp=2 antall=0 1 ---> 3 antall=0 1 ---> 2 antall=1 fra=3 antall=0 til=2 3 ---> 2 tmp=1 antall=0 1 ---> 3 antall=2 fra=2 antall=1 til=3 tmp=1 fra=2 til=1 tmp=3 antall=0 2 ---> 1 antall=0 2 ---> 3 antall=1 fra=1 antall=0 til=3 1 ---> 3 tmp=2 antall=0 1 2 3 Antall rekursive kall: 2 n - 1, der n er antall ringer. JFPS3U 12: Rekursjon 12-27/39 Rekursiv sorteringsalgoritme: Quicksort En effektiv rekursiv sorteringsalgoritme for tabeller. Denne splitt-og-hersk algoritmen deler sorteringsoppgaven i to og bruker et rekursivt kall for å løse hver del av oppgaven. Mesteparten av arbeidet i Quicksort-algoritmen består i å dele opp oppgaven. En operasjon arbeider på et tildelt tabellsegment som skal sorteres. Tabellsegmentet deles opp og tilpasses slik at oppgaven kan løses ved å sortere hver av delene separat. Et segment deles slik at det største elementet i den første delen er mindre enn alle elementene i den andre delen. JFPS3U 12: Rekursjon 12-28/39

Algoritme: Quicksort Operasjon quicksort(segment): Hvis segment er tomt: Returner. skilleelement = første element i segment Utfør partisjonering av segment i to deler: del1 og del2 quicksort(del1) quicksort(del2) Operasjon partisjonering(segment): skilleelement = et valgt element i segmentet Bytt rundt på elementene i segmentet slik at: Alle elementene mindre enn skilleelement kommer før skilleelement Alle elementene større enn skilleelement kommer etter skilleelement JFPS3U 12: Rekursjon 12-29/39 Quicksort static <T extends Comparable<T>> void quicksort(t[] elementer, int første, int siste, int nivå) { // Basistilfelle: 0 eller ett element, ingen sortering nødvendig. if (første >= siste) { System.out.println(blanker(nivå) + nivå + "> basistilfelle"); return; // Generelt tilfelle: Partisjoner segmentet. skrivsegment(blanker(nivå) + nivå + "> segment før deling: ", elementer, første, siste); int skilleposisjon = partisjoner(elementer, første, siste, nivå); System.out.println(blanker(nivå) + nivå + "> skilleelementet: " + elementer[skilleposisjon]); skrivsegment(blanker(nivå) + nivå + "> segment etter deling: ", elementer, første, siste); // Sorter elementene som var mindre eller lik skilleelementet: skrivsegment(blanker(nivå) + nivå + "> sorter: ", elementer, første, skilleposisjon-1); quicksort(elementer, første, skilleposisjon-1, nivå+1); skrivsegment(blanker(nivå) + nivå + "> sortert: ", elementer, første, skilleposisjon-1); JFPS3U 12: Rekursjon 12-30/39

// Sorter elementene som var større enn skilleelementet: skrivsegment(blanker(nivå) + nivå + "> sorter: ", elementer, skilleposisjon+1, siste); quicksort(elementer, skilleposisjon+1, siste, nivå+1); skrivsegment(blanker(nivå) + nivå + "> sortert: ", elementer, skilleposisjon+1, siste); JFPS3U 12: Rekursjon 12-31/39 Partisjonering skilleelement <= skilleelement > skilleelement skilleelement syk > hei syk lot dal nes buk lam lam > hei lam lot dal nes buk syk buk < hei buk lot dal nes lam syk lot > hei buk lot dal nes lam syk nes > hei buk nes dal lot lam syk dal < hei buk dal nes lot lam syk hei buk dal nes lot lam syk dal buk hei nes lot lam syk JFPS3U 12: Rekursjon 12-32/39

static <T extends Comparable<T>> int partisjoner(t[] elementer, int første, int siste, int nivå) { /* Generelt tilfelle: Del opp elementene i dem som er mindre eller lik skilleelementet, og dem som er større enn skilleelementet. */ T skilleelement = elementer[første]; int sistelite = første; int sisteudelte = siste; // Utfør så lenge det er flere elementer som ikke har blitt delt. while (sistelite < sisteudelte) { // skrivsegment(blanker(nivå) + nivå + "> deling: ", // elementer, første, siste); int førsteudelte = sistelite + 1; T testelement = elementer[førsteudelte]; boolean større = testelement.compareto(skilleelement) > 0; if (større) { // Bytt om for å plassere elementet etter de udelte. elementer[førsteudelte] = elementer[sisteudelte]; elementer[sisteudelte] = testelement; --sisteudelte; else { // Elementet er på riktig side av de udelte allerede. ++sistelite; JFPS3U 12: Rekursjon 12-33/39 /* Ingen udelte igjen. Plassér skilleelementet mellom de store og små elementene ved å bytte plass med det siste av de små. */ int skilleposisjon = sistelite; elementer[første] = elementer[skilleposisjon]; elementer[skilleposisjon] = skilleelement; // skrivsegment(blanker(nivå) + nivå + "> deling: ", // elementer, første, siste); return skilleposisjon; JFPS3U 12: Rekursjon 12-34/39

Kjøring av programmet: Tabell usortert: hei syk lot dal nes buk lam 1> segment før deling: hei syk lot dal nes buk lam 1> skilleelementet: hei 1> segment etter deling: dal buk hei nes lot lam syk 1> sorter: dal buk 2> segment før deling: dal buk 2> skilleelementet: dal 2> segment etter deling: buk dal 2> sorter: buk 3> basistilfelle 2> sortert: buk 2> sorter: 3> basistilfelle 2> sortert: 1> sortert: buk dal 1> sorter: nes lot lam syk 2> segment før deling: nes lot lam syk 2> skilleelementet: nes 2> segment etter deling: lam lot nes syk 2> sorter: lam lot 3> segment før deling: lam lot 3> skilleelementet: lam 3> segment etter deling: lam lot 3> sorter: 4> basistilfelle 3> sortert: 3> sorter: lot 4> basistilfelle 3> sortert: lot 2> sortert: lam lot 2> sorter: syk 3> basistilfelle 2> sortert: syk 1> sortert: lam lot nes syk Tabell sortert: buk dal hei lam lot nes syk JFPS3U 12: Rekursjon 12-35/39 import junit.framework.testcase; Enhetstest for Quicksort public class TestSort extends TestCase { String[] array; public TestSort(String testmethodname) { super(testmethodname); public void setup() { array = new String[] { "This", "is", "not", "so", "difficult" ; public void testorder() { // array[0] <= array[] <=... <= array[n-1] Quicksort.sorter(array); for (int i = 1; i < array.length; i++) { int status = array[i-1].compareto(array[i]); asserttrue("not sorted. Check index: " + i, status <= 0); JFPS3U 12: Rekursjon 12-36/39

Eksponentiell økning i antall metodekall Dersom oppgaven deles opp i to eller flere rekursive metodekall, er ikke rekursjonsdybden lik antall ganger metoden kalles. I Quicksort-algoritmen, dobles det totale antall metodekall for hvert rekursjonsnivå. Rekursjonsdybde 1 2 3 4 5 6 7 8 9 Resultat av mer enn ett rekursivt kall per rekursjonsnivå. Gjør at rekursive algoritmer ikke alltid er beste løsning. JFPS3U 12: Rekursjon 12-37/39 Fibonacci-serien Tallrekke 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 510, 787... Matematisk definisjon: f 1 = 1, f 2 = 1 f n = f n-1 + f n-2 Java implementasjon: static int fibonacci(int n) { if (n < 3) return 1; return fibonacci(n-2) + fibonacci(n-1); Utregning av f n vil kreve utregning av f n-1 og f n-2. begge disse krever utregning av f n-3 utregningen vil skje separat for både f n-1 og f n-2 Mengden med dobbeltarbeid øker eksponentielt, med n. Fibonaccitall f 3 3 f 4 5 f 5 9 f 6 15 Antall metodekall JFPS3U 12: Rekursjon 12-38/39

Iterativ utregning er bedre. static int fibonacci(int n) { int nestsiste = 1; int siste = 1; for (int i=3; i<=n; i++) { int neste = siste + nestsiste; // Forskyv variablene: nestsiste = siste; siste = neste; return siste; Regner ut f n ved å regne ut verdiene fra f 1 til f n-1 i sekvens. JFPS3U 12: Rekursjon 12-39/39