Rekursiv programmering BTeksempel Datastruktur I klassen Persontre (rotperson==<hele treet>) Rekursjon Noen oppgaver/problemer er rekursive «av natur» Eksempel på en rekursiv definisjon Fakultetsfunksjonen Metoder som kaller seg selv Vertikal utskrift Hele programmet Oppsummering Finne maksimal verdi i en array Fibonaccitallene 4. rekursjonsregel Traversering Oppsummering - programskisse for traversering av en graf (dybde først) Rekursjonsreglene Rekursive metoder Datastruktur rotperson kristhk andrepar snorrewb kimst mariubac behrozm wenjinl bohalvor Et objekt av klassen Persontre Datastruktur 1 abstract class BTnode { 2 BTnode venstre, høyre ; 3... 4 } 5 class Person extends BTnode { 6 String navn; 7... 8 } BTnode venstre BTnode høyre class Person extends BTnode String navn behrozm "behrozm" I klassen Persontre (rotperson==<hele treet>) 1 public void settinn ( Person inn ) { 2 if ( rotperson == null ) rotperson = inn ; 3 else settinnrek ( inn, rotperson ) ; 4 System. out. print ( inn.hentnavn ( ) ) ; 5 System. out. print ( " " ) ; 6 } 7 8 private void settinnrek ( Person inn, Person tre ) { 9 int smnlgn = tre. sammenlign( inn ) ; 10 if ( smnlgn < 0 ) { 11 if ( tre.høyre == null ) tre.høyre = inn ; 12 else settinnrek ( inn, (Person ) tre.høyre ) ; 13 } 14 else if ( smnlgn > 0 ) { 15 if ( tre. venstre == null ) tre. venstre = inn ; 16 else settinnrek ( inn, (Person ) tre. venstre ) ; 17 } 18 }
Noen oppgaver/problemer er rekursive «av natur» rekursjon subst. -en. Hvis du har forstått hva rekursjon er, slutt å lese. Hvis ikke, se rekursjon. I Bokmålsordboka er rekursjon definert som periodisk gjentakelse. Oppgaver som er rekursive av natur kan ofte deles opp i flere deloppgaver der en eller flere av deloppgavene er svært lik den opprinnelige oppgaven. Å lage en definisjon eller et program av en slik oppgave er enkelt. Eksempel på en rekursiv definisjon 2 «grener»: 1. personen har bodd minst 20 år i Bergen og vi stanser undersøkelsen (basistilfelle) 2. Personen har ikke bodd minst 20 år i Bergen og vi må fortsette undersøkelsen på moren (rekursjon) Ikke rekursiv definisjon: En bergenser er en person som har bodd minst 20 år i Bergen, eller som har en mor som er bergenser, eller som har en mormor som er bergenser, eller som har en mormormor som er bergenser, eller som har en mormormormor som er bergenser, eller... Definisjon av en bregne En bregne er en stilk med bregner på. Eksempel på en rekursiv definisjon Definisjon av bergenser Oppgave: Finn ut om en gitt person er bergenser. En bergenser er en person som har bodd minst 20 år i Bergen, eller som har en mor som er bergenser. Oppgaven, undersøkelsen stopper enten fordi vi i støter på en person som har bodd mer enn 20 år i Bergen, eller fordi vi ikke finner (har en peker) til moren til den vi nå undersøker Hvisn=7, skal vi finne produktet av regnestykket 1 2 3 4 5 6 7= Vi gir metoden navnetf, den er av type int og har en parameternav type int: 1 2 int f ( int n) {... } f(1)=1 f(2)=1 2=2 f(3)=1 2 3=6 f(4)=1 2 3 4=24
f(5)=f(4) 5=24 5=120 f(6)=f(5) 6=120 6=720 f(n)=1 2 3 4... (n 1) n f(n)=f(n 1) n Den siste formelen er en rekursiv definisjon av metodenf. Det er denne vi benytter når vi skal programere rekursivt: 1 int f ( int n) { 2 int svar ; 3 i f (n == 1) svar = 1; 4 else svar = f (n 1) * n; 5 return svar ; 6 } prompt> java fakultet 7 f(7) = 5040 For bedre å se hva som skjer, legger vi inn testutskrift når metoden starter og før den avslutter. 1 int f ( int n) { 2 int svar ; 3 4 System. out. println ( " testutskrift : f ( "+n+" ) er kalt " ) ; 5 6 i f (n == 1) svar = 1; 7 else svar = f (n 1) * n; 8 9 System. out. println ( " testutskrift : f ( " + n + 10 " ) returnerer verdien " + svar ) ; 11 12 return svar ; 13 } 1 class fakultet { 2 3 public static void main( String [ ] args ) { 4 fakultet dd = new fakultet ( ) ; 5 int argument = Integer. parseint ( args [ 0 ] ) ; 6 i f ( argument < 1) argument = 1; 7 System. out. println ( " f ( " + argument + " ) = " 8 + dd. f (argument ) ) ; 9 } 10 11 int f ( int n) { 12 int svar ; 13 i f (n == 1) svar = 1; 14 else svar = f (n 1) * n; 15 return svar ; 16 } 17 } prompt> java fakultet 4 testutskrift: f(4) er kalt testutskrift: f(3) er kalt testutskrift: f(2) er kalt testutskrift: f(1) er kalt testutskrift: f(1) returnerer verdien 1 testutskrift: f(2) returnerer verdien 2 testutskrift: f(3) returnerer verdien 6 testutskrift: f(4) returnerer verdien 24 f(4) = 24 Dette går bra, fordi nårf(k 1) kalles, forsvinner ikke (instansen av metoden) kalletf(k). Sistnevnte overlater kontrollen tilf(k 1), «går i dvale» og «våkner opp igjen» nårf(k 1) avsluttes og returnerer med en verdi.
n = 5; main ( args[] = "5") svar = n * f(4); dd.f(5); n = 4; n = 1; svar = n * f(3); svar = 1; return 1; n = 3; n = 2; svar = n * f(2); svar = n * f(1); Test av programmet medn=5 gir følgende «resultat»: prompt> java fakultet 5 tstutskr: f(5) er kalt og går i dvale... tstutskr: f(4) er kalt og går i dvale... tstutskr: f(3) er kalt og går i dvale... tstutskr: f(2) er kalt og går i dvale... tstutskr: f(1) er kalt og returnerer verdien 1 tstutskr: f(2) våkner opp og returnerer verdien 2 tstutskr: f(3) våkner opp og returnerer verdien 6 tstutskr: f(4) våkner opp og returnerer verdien 24 tstutskr: f(5) våkner opp og returnerer verdien 120 f(5) = 120 Her kunne vi si enda mer i detf(k) våkner opp, f.eks. hvilken verdi som kom ut av det rekursive kalletf(k 1). Vi legger inn testutskrifter før det rekursive kallet: 1 int f ( int n) { 2 int svar ; 3 4 System. out. print ( " tstutskr : f ( "+ n +" ) er kalt og " ) ; 5 6 i f (n == 1) svar = 1; 7 else { 8 System. out. println ( " går i dvale... " ) ; 9 10 svar = f (n 1) * n; 11 12 System. out. print ( " tstutskr : f ( "+ n +" ) våkner opp og " ) ; 13 } 14 15 System. out. println ( " returnerer verdien " + svar ) ; 16 return svar ; 17 } Vertikal utskrift Et program som skriver ut en tekststreng «vertikalt», dvs. ett tegn per linje (println). 1 void skriv ( String s) { 2 while ( s. length ( ) > 0) { 3 System. out. println ( s. substring (0,1)); 4 // Husk å kaste utskrevet tegn 5 s = s. substring (1,s. length ( ) ) ; 6 } 7 } Delopppgavene: skriv ut tekststrengens første tegn skriv ut resten av teksstrengen
Vertikal utskrift 1 void skriv ( String s) { 2 if ( s. length ( ) == 1) System. out. println ( s ) ; 3 else { 4 // Mer enn e t t tegn, skriver ut første 5 System. out. println ( s. substring (0,1)); 6 // Kapper av utskrevet tegn 7 s = s. substring (1,s. length ( ) ) ; 8 // Da gjenstår bare å skrive ut resten. 9 // Vi sender med samme parameter, 10 // siden s nå er er e t t tegn kortere. 11 skriv ( s ) ; 12 } 13 } Hele programmet 1 class vertprint { 2 void skriv ( String s) { 3 if ( s. length ( ) == 1) System. out. println ( s ) ; 4 else { 5 System. out. println ( s. substring (0,1)); 6 // Kapper av utskrevet tegn 7 s = s. substring (1,s. length ( ) ) ; 8 skriv ( s ) ; 9 } 10 } 11 12 public static void main( String [ ] args ) { 13 vertprint vp = new vertprint ( ) ; 14 String argument = args [ 0]; 15 if (argument. length ( ) > 0) 16 vp. skriv (argument ) ; 17 else return ; 18 } 19 } Vertikal utskrift En test av programmet kan se slik ut: prompt> java vertprint rekursjon r e k u r s j o n prompt> Oppsummering Hva har de to metodene felles? en if-test deler metoden i to utfall: et basistilfelle med enkel løsning n=1 s.length()==1 et mer komplisert tilfelle hvor parameteren er «en nærmere» basistilfellet kaller påf(n 1) kaller på skriv(s) (s ett tegn kortere) vi antar at metoden virker som den skal når vi bruker (kaller) den.
Finne maksimal verdi i en array Nå prøver vi bevisst å tenke rekursivt før vi starter å lage skisse av programmet. Programskisse: Anta vi har arrayen int A [] og at i er høyeste indeks i A. Startkallet for arrayen A med lengde n blir finnmax(n-1) 1 int finnmax ( int i ) { 2 if ( i =0) 3 // basistilfelle 4 <returner A[ i ]> 5 else 6 <returner største av A[ i ] 7 og finnmax( i 1)> 8 } Fibonaccitallene Fibonaccitallene er definert ved: F0= 1 Fn= F1= 1 Fn=Fn 1+Fn 2 nårn>1. (1) Vi regner ut noen verdier avf: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 1 1 2 3 5 8 13 21 Finne maksimal verdi i en array Når vi programmerer ut skissen <største av A[i] og finnmax(i-1)> må vi passe på så vi ikke får mer enn ett rekursivt kall: 1... 2 if (A[ i ] > finnmax( i 1)){ 3 return A[ i ] 4 } 5 else { 6 return finnmax( i 1) 7 } 8.... Fibonaccitallene Dette kan implementeres rekursivt slik: 1 2 3 int fib ( int n) { 4 if (n <= 1) { 5 return 1; 6 } else { 7 return fib (n 1) + fib (n 2); 8 } 9 } (Hva skjer hvis vi kaller på fib(-1)?)
4. rekursjonsregel Ikke løs samme instans av et problem i separate rekursive kall F6 F5 F4 F4 F3 F3 F2 F3 F2 F2 F1 F2 F1 F1 F0 F2 F1 F1 F0 F1 F0 F1 F0 F1 F0 Ideen er å lage en metode fargelegg som tar et felt som parameter og som fargelegger feltet og kaller seg selv med hvert nabofelt som parameter (for-løkke) Siden alle felter er 4-nabo med minst to andre felt, skulle vi på denne måten komme til alle felt. Basistilfellet i denne algoritmen er når parameteren er et felt som ikke har umalte naboer. Da blir det heller ingen rekursive kall, men feltet blir fargelagt. La oss for eksemplets skyld ta et kvadratisk rom, hvor feltene også er kvadratiske. Dette kan f.eks. være et rom med kvadratiske fliser. Her et eksempel med 9 9 felt: Dette problemet er rekursivt, fordi det å fargelegge hele rommet kan deles opp i deloppgaver å fargelegge feltene. Det rekursive kallet er nærmere et basistilfelle fordi vi fargelegger eget felt før rekursive kall: antall felt totalt er endelig det gjenstår ett felt mindre å fargelegge enn i den metodeinstansen vi nå er 1. Fargelegg felt 2. Se på alle nabofelter: Hvis feltet ikke er fargelagt, fargelegg det.
Programskisse, der vi tenker oss en datastruktur hvor feltene er objekter av klassen Felt (datastrukturen kan f.eks. være en en array A[][]. 1 void fargelegg ( Felt f ) { 2 f.mal( sinober ) ; // fargelegging 3 nf = f.nestenabo ( ) ; 4 while ( ( nf!= null ) && (! nf.malt ( ) ) { 5 fargelegg ( nf ) ; 6 } 7 } Vi har antatt at Felt har en metode mal(string farge) som fargelegger et Felt-objekt (merker det malt) og en boolsk metode malt() som returnerer true hvis feltet er fargelagt. Oppsummering - programskisse for traversering av en graf (dybde først) behandle dette objektet (f.eks. endre en lokal verdi) For nabo i naboliste: Hvis naboobjekt er ubehandlet og lovlig: Traverser med utgangspunkt i naboobjekt. Algoritmen for å fargelegge ruter i et rektangel: 1 2 void fargelegg ( Felt f ) { 3 f.mal( sinober ) ; // fargelegging 4 nf = f.nestenabo ( ) ; 5 while ( ( nf!= null ) && (! nf.malt ( ) ) { 6 fargelegg ( nf ) ; 7 } kan benyttes generelt på objekter i en graf såfremt: man fra et objekt kan nå alle gjennom nabopekere nestenabo() gir alle naboer til et objekt Rekursive metoder En rekursiv metode er en metode som kaller seg selv. Huskeregler: Det må alltid finnes et basistilfelle som kan løses uten rekursjon. De rekursive kallene må gå i retning av et basistilfelle. Designregel: Anta at de rekursive kallene fungerer. Ikke løs samme instans av et problem i separate rekursive kall