INF1010 Rekursjon. Marit Nybakken 1. mars 2004

Like dokumenter
INF1010. Sekvensgenerering Alle mulige sekvenser av lengde tre av tallene 0, 1 og 2: Sekvensgenerering. Generalisering. n n n! INF1010 INF1010 INF1010

INF2220: Forelesning 7. Kombinatorisk søking

En algoritme for permutasjonsgenerering

INF1000. Marit Nybakken 10. februar 2004

INF1010 Sortering. Marit Nybakken 1. mars 2004

Eksempel 1 Eksempel 2 Dramatisering. INF1000 uke 3. Sundvollen 7. september 2015 Dag Langmyhr. INF1000 Sundvollen

IN2010: Forelesning 11. Kombinatorisk søking Beregnbarhet og kompleksitet

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

UNIVERSITETET I OSLO

Løsningsforslag ukeoppg. 6: 28. sep - 4. okt (INF Høst 2011)

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

Backtracking som løsningsmetode

Norsk informatikkolympiade runde

INF1010 Hashing. Marit Nybakken 8. mars 2004

Velkommen til. INF våren 2016

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

UNIVERSITETET I OSLO

Forelesning inf Java 4

INF1000 (Uke 15) Eksamen V 04

INF1000 (Uke 15) Eksamen V 04

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

INF1010 Tråder J. Marit Nybakken Motivasjon. Å lage en tråd

UNIVERSITETET I OSLO

Backtracking som løsningsmetode

INF 1000 høsten 2011 Uke september

Om å lete gjennom «alle muligheter» Eller: Kombinatorisk søking, rekursjon og avskjæring 1 Innledning Problemer, instanser og løsningsalgoritmer

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

INF1000 undervisningen INF 1000 høsten 2011 Uke september

UNIVERSITETET I OSLO

UNIVERSITETET I OSLO

UNIVERSITETET I OSLO

INF1000 Metoder. Marit Nybakken 16. februar 2004

Norsk informatikkolympiade runde

UNIVERSITETET I OSLO

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

Løse reelle problemer

UNIVERSITETET I OSLO

Løsnings forslag i java In115, Våren 1996

Lenkelister. Lister og køer. Kopi av utvalgte sider fra forelesningen.

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

INF1010 notat: Binærsøking og quicksort

INF1000 oppgaver til uke 38 (17 sep 23 sep)

UNIVERSITETET I OSLO

Oversikt. INF1000 Uke 1 time 2. Repetisjon - Introduksjon. Repetisjon - Program

Norsk informatikkolympiade runde

2 Om statiske variable/konstanter og statiske metoder.

Løsnings forslag i java In115, Våren 1998

INF Uke 10. Ukesoppgaver oktober 2012

alternativer til sortering og søking binære trær søketrær Ikke-rekursiv algoritme som løser Hanois tårn med n plater

INF 1000 Prøveeksamen. 23. november Ole Christian og Arne. Oppgave 1 (10 poeng) Er disse programsetningene lovlige i Java? Oppgave 2 (10 poeng)

EKSAMENSFORSIDE Skriftlig eksamen med tilsyn

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; }

Kort om meg. INF1000 Uke 2. Oversikt. Repetisjon - Introduksjon

UNIVERSITETET I OSLO

Forelesning inf Java 5

Kombinatorisk søking, rekursjon, avskjæring

Forelesning inf Java 5

UNIVERSITETET I OSLO

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

Oppgave 1. INF1000 Uke 13. Oppgave 2. Oppgave 3. Er dette lovlige deklarasjoner (når de foretas inni en metode)? JA NEI

UNIVERSITETET I OSLO

PG 4200 Algoritmer og datastrukturer Innlevering 1. Frist: 2.februar kl 21.00

OBJEKTER SOM EN PROGRAMMERINGS-TEKNIKK

Oppgave 1. Oppgave 2. Oppgave 3. Prøveeksamen i INF1000. Ole Christian og Arne. 23. november 2004

INF1000 Behandling av tekster

Prøveeksamen i INF1000. Ole Christian og Arne. 23. november 2004

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

INF1000: Forelesning 6. Klasser og objekter del 1

Backtracking: Kombinatorikk og permutasjoner

Jentetreff INF1000 Debugging i Java

Ukeoppgaver INF1000: 12. feb 16. feb

E K S A M E N. Algoritmiske metoder I. EKSAMENSDATO: 11. desember HINDA / 00HINDB / 00HINEA ( 2DA / 2DB / 2EA ) TID:

Uke 8 Eksamenseksempler + Ilan Villanger om studiestrategier. 11. okt Siri Moe Jensen Inst. for informatikk, UiO

EKSAMEN med løsningsforslag

UNIVERSITETET I OSLO

Programmering Høst 2017

Norsk informatikkolympiade runde

UNIVERSITETET I OSLO

Norsk informatikkolympiade runde

Endret litt som ukeoppgave i INF1010 våren 2004

Løsnings forslag i java In115, Våren 1999

Beregning av med svært mange desimaler

INF Løsning på seminaropppgaver til uke 8

UNIVERSITETET I OSLO

UNIVERSITETET I OSLO

INF1000: noen avsluttende ord

I et Java-program må programmøren lage og starte hver tråd som programmet bruker. Er dette korrekt? Velg ett alternativ

Lese fra fil. INF1000 : Forelesning 5. Eksempel. De vanligste lesemetodene. Metoder:

Algoritmer og Datastrukturer IAI 21899

INF2220: Time 8 og 9 - Kompleksitet, beregnbarhet og kombinatorisk søk

INF1000 : Forelesning 4

UNIVERSITETET I OSLO

i=0 i=1 Repetisjon: nesting av løkker INF1000 : Forelesning 4 Repetisjon: nesting av løkker Repetisjon: nesting av løkker j=0 j=1 j=2 j=3 j=4

Oppgave 1 - Kortsvarsoppgave. INF1000 eksamen V05. Oppgave 1 (c) Oppgave 1 (b) Svar: a = 9, b=10

Gjennomgang av eksamen H99

EKSAMEN 6108/6108N PROGRAMMERING I JAVA Alt trykt og skriftlig materiale.

Norsk informatikkolympiade runde. Sponset av. Uke 46, 2013

Norsk informatikkolympiade runde. Sponset av. Uke 46, 2017

Binære søketrær. Et notat for INF1010 Stein Michael Storleer 16. mai 2013

Obligatorisk oppgave 1 INF1020 h2005

Transkript:

INF1010 Rekursjon Marit Nybakken marnybak@ifi.uio.no 1. mars 2004 Å lære rekursjon gjøres ved å prøve å gjøre en masse oppgaver selv til man får en aha-opplevelse. Marit prøver likevel å forklare littegranne. Nå har vi kommet til den delen av programmering der vi må lage programmer som svarer på de kjipe spørsmålene. Kan vi plassere åtte dronninger på et sjakkbrett uten at de kan slå hverandre? Kan vi plassere 20 kranglete elever på C313 på en sånn måte at ingen kan slå til hverandre? Kan du lage et program som kan slå Arne Maus i bondesjakk? Eller Rubiks kube? Det finnes som regel ingen smart formel for å løse sånne problemer som vi bare kan putte inn i programmet. Hvordan skal vi gå i gang med å programmere noe vi ikke forstår hvordan henger sammen? Svaret er: prøv alle mulige kombinasjoner (kalles også brute force, uttømmende søk ). Vi er langt smartere enn maskinen, men skulle vi prøvd alle mulige kombinasjoner for hånd, ville vi kanskje brukt måneder på det. Datamaskinen er ikke smart, men den er skikkelig flink til å gjøre millionvis av kalkulasjoner på kort tid. 1

Figur 1: Sammen gjør vi en innsats for fedrelandet Generere sekvenser - alle mulige kombinasjoner Ok, så da må vi finne ut hvordan vi kan få maskinen til å prøve ut alle kombinasjoner. Her er det altså at rekursjon kommer inn. Tenk deg at du skulle måle lengden av alle veiene i Norge. Heldigvis har du vært med i en stygg kjemisk ulykke og er i stand til å klone deg selv. Du starter i Sinsenkrysset. For hver arm ut kloner du deg selv og sender avgårde klonen din med et målebånd. Så setter du deg ned og venter. Klonene dine er genetiske kopier og også i stand til å klone seg selv. Så hver gang de kommer til et nytt veikryss, kloner de seg selv på nytt, sender ut en klone per vei og setter seg til å vente på resultatet. Så er det bare å vente. Før eller siden kommer det en klone til en blindvei i Honningsvåg. Når han er ferdig med å måle veistubben sin, går han tilbake og forteller klonen som klonet ham hva lengden var. Så skyter han seg selv, det er ikke bruk for ham lenger. Når alle kloner av denne klonen er ferdige med veiene sine og har fortalt sjefen hvor lange de var, summerer han dette på et ark og legger til lengden 2

av sin egen vei. Så går han tilbake og forteller klonen som klonet ham igjen om sine resultater, før han skyter seg selv. Denne samler så igjen sammen alle resulatene og forteller det til sin klone igjen. Før eller siden kommer vi tilbake til Oslo og Sinsen igjen. Du samler inn resultatene fra alle klonene fra hver arm, summerer disse og skyter dem alle. Så står du igjen med lengden av alle veiene i Oslo og millioner av døde kloner strødd utover hele Norge. double målveiene(vei vei) { double minveilengde = <resultat av målingen>; // strekke målebåndet langs min vei // Hvis ingen veier går ut, går denne løkka 0 ganger og ingen // nye kloner lages for(<alle veier som går ut fra min vei>) { // Send ut en klone minveilengde += målveiene(vei.veiutframinvei(i)); 10 if(vei == <sinsenkrysset>) { // Tilbake i mål! System.out.println("Summen av alle veier i Norge er : " + minveilengde); return minveilengde; Nåja. Poenget er at for hver gang vi kommer til et veiskille så genererer vi en klone, dvs vi kaller rekursivt metoden vi er i på nytt. Tenk for eksempel på teddybjørnene fra en uke tilbake, hver gang vi sto igjen med et bestemt antall bjørner, kalte vi bjørnemetoden igjen for hver mulighet vi hadde til å gi ut bjørner, vi sendte en klone for hver vei ut. Å generere alle kombinasjoner av tall kan også sees på som å prøve alle veier. Forskjellen her er at det alltid går et bestemt antall veier ut fra hvert kryss, dette tilsvarer antall sifre vi kan bruke. Dybden på veitreet er også konstant, den tilsvarer hvor mange sifre vi skal ha i sekvensen av tall. Kombinasjonen 00000000 kan f.eks. være at vi alltid velger veien som gikk til venstre, mens 00000001 er at vi tok veien nest til venstre ved siste skille. Følgende kodebit lager alle mulige kombinasjoner av et bestemt antall sifre. 3

Vi kan bruke sifrene mellom 0 og 9, så det blir ti muligheter ved hvert veivalg, ti grener ut: class Sekvens{ // Her skal sifrene lagres int[ ] sifrenevåre; int antallsifre; Sekvens(int antallsifre) { sifrenevåre = new int[antallsifre]; this.antallsifre = antallsifre; 10 public void puttallemuligetallpåfølgendeplassering(int plassering) { // Gå gjennom alle mulige siffer som vi kan putte på denne plasseringen i arrayen for (int siffer = 0; siffer < 10 ; siffer++) { // Putt sifferet inn i arrayen sifrenevåre[plassering] = siffer; // Hvis dette den siste plassen i arrayen så skal vi ikke 20 // kalle videre, men vise frem resultatet if (plassering == antallsifre 1) { // Fin ny kombinasjon som må skrives ut på skjermen for (int j = 0; j < antallsifre; j++) { System.out.print(sifreneVåre[j]); System.out.println(); else { // Få nestemann i rekka til å lage alle 30 // kombinasjoner på neste plass puttallemuligetallpåfølgendeplassering(plassering+1); public static void main(string [ ] args) { int hvormangetall = Integer.parseInt(args[0]); new Sekvens(hvorMangeTall).puttAlleMuligeTallPåFølgendePlassering(0); 40 Når vi skriver slike metoder, så kan det (for mange) hjelpe å tenke at vi nå står midt i genereringen, halve jobben er gjort, og de tidligere metodekallene 4

fungerte som de skulle (ja, det er sært, men dette funker faktisk). Vi tenker oss at sekvensen 3 1 2 2 er generert og lagt i arrayen sifrenevåre og metoden er kalt med plassering = 4. Vår jobb her er nå å putte alle mulige sifre på plass nummer 4 og for hver gang kalle videre. Javel, da legger vi inn en for-løkke som spoler gjennom alle sifrene, putter sifferet vi har kommet til på riktig plass og kaller videre med plassering+1 som argument. Det første som da skjer er at vi setter sifrenevåre[4] = 0 Deretter kaller vi videre på puttallemuligetallpåfølgendeplassering(5) og får generert alle mulige rekkefølger der de første fem sifrene er 3 1 2 2 0. Så settes sifrenevåre[4] = 1 og kallet videre genererer alle mulige rekkefølger der de første fem sifrene er 3 1 2 2 1. Stoppkriterium Når vi skriver metoden må vi også tenke oss at vi er på slutten av genereringen. Hva skal til for at vi klarer å stoppe og ikke raser videre inn i evigheten? Det er dette som kalles stoppkriteriet og det er det som skal hindre at vi får en av de skumle rekursjonsbrønnene der metoden går og går og aldri kommer til døra. Vi legger derfor inn en test på om vi er på den siste plassen 5

if(plassering == antallsifre - 1) og kaller videre kun hvis vi ikke er det. Ellers skriver vi ut resultatet. Generere permutasjoner - ingen får være like Temmelig ofte så er det sånn at hvert siffer bare får lov til å komme en gang i rekkefølgen. Det kan vi få til i det forrige eksempelet ved å legge til en boolsk array som sier hvilke av sifrene som til enhver tid er brukt og hvilke som ikke er det. Så hvis en av de forrige metodekallene brukte sifferet 4, så er brukt[4] = true; class Sekvens{ // Her skal sifrene lagres int[ ] sifrenevåre; // Husker på hvilke av sifrene som er brukt til nå boolean [ ] erdettetalletbruktmontro; int antallsifre; Sekvens(int antallsifre) { sifrenevåre = new int[antallsifre]; erdettetalletbruktmontro = new boolean[10]; this.antallsifre = antallsifre; 10 public void puttallemuligetallpåfølgendeplassering(int plassering) { // Gå gjennom alle mulige siffer som vi kan putte på denne plasseringen i arrayen for (int siffer = 0; siffer < 10 ; siffer++) { 20 // Hvis ingen av metodekallene før oss har brukt dette sifferet if(!erdettetalletbruktmontro[siffer]) { // Da erre vårt! // Putt sifferet inn i arrayen sifrenevåre[plassering] = siffer; // Si fra til fremtidige metodekall at de ikke får lov til å bruke sifferet 30 erdettetalletbruktmontro[siffer] = true; 6

// Hvis dette den siste plassen i arrayen så skal vi ikke // kalle videre, men vise frem resultatet if (plassering == antallsifre 1) { // Fin ny kombinasjon som må skrives ut på skjermen for (int j = 0; j < antallsifre; j++) { System.out.print(sifreneVåre[j]); 40 System.out.println(); else { // Få nestemann i rekka til å lage alle // kombinasjoner på neste plass puttallemuligetallpåfølgendeplassering(plassering+1); // Nå kommer det som er litt snodig. Vi må sette tallet til ikkebrukt // før neste runde i løkka. Hvis vi brukte 3 nå og neste runde bruker 4, // så er jo ikke 3 opptatt for fremtidige metodekall lenger. 50 erdettetalletbruktmontro[siffer] = false; public static void main(string [ ] args) { int hvormangetall = Integer.parseInt(args[0]); new Sekvens(hvorMangeTall).puttAlleMuligeTallPåFølgendePlassering(0); 60 Hver av rekkefølgene vi nå genererer kalles en permutasjon. En permutasjon er hvis vi har en haug med elementer (tall, elever, kyr) og så stiller dem opp i en eller annen rekkefølge. Man får ikke lov til å gjenta noen av dem altså, bare blande dem rundt. Dette konseptet må vi f.eks. bruke når vi spiller bondesjakk med Arne. Når det er vår tur, sjekker vi hvilke av plassene på brettet som allerede er brukt. Så går vi gjennom alle mulige steder å sette neste brikke på ut i fra dette. Man må selvfølgelig ha noe mikkmakk som regner på hva som er en lur plassering og hva som ikke er det, sånn at Arne ikke vinner. Dette går gjerne ut på å spille spillet ferdig ut i fra den plasseringen vi har valgt, med alle mulige plasseringer Arne så kan gjøre, og se hvordan det vil gå. 7

Lett og vrien? Det prates veldig mye om at vi har en lett del og en vrien del når vi driver og prøver å løse denne typen problemer. Den lette delen skal liksom være det å generere alle kombinasjoner. Dette gjøres ved hjelp av rekursjon, som vi ikke synes er enkelt akkurat nå. Den vriene delen er det å gjøre avskjæringer og å finne ut hvilke av kombinasjonene som er løsninger og hvilke som ikke er det. Et eksempel på slik avskjæring er brukt-arrayen, som i og for seg ikke var så veldig vrient. Derfor kan dere ignorere lett og vrien -pratet hvis dere synes det gir like lite mening som det jeg gjør. Hvor mange ganger kaller vi? Dette er ikke akkurat pensum, men kan kanskje gjøre det lettere å få litt oversikt? Vi kan skille mellom lineær, binær og N iær rekursjon. Lineær rekursjon er der hvert metodekall bare kaller seg selv en gang. void ølpåhylla(int antallflasker) { if(antallflasker > 1) { System.out.println(antallFlasker + " bottles of beer on the wall"); System.out.println(antallFlasker + " bottles of beer"); System.out.println("You take one down and pass it around"); 8

System.out.println((antallFlasker 1)+ " bottles of beer on the wall"); ølpåhylla(antallflasker 1); 10 Denne kunne vært erstattet med en løkke. Det gjelder mange av dem, f.eks. metoden for å beregne fakultetet av et tall. Binær rekursjon er der hvert metodekall kaller videre to ganger. Dette kan blant annet brukes til søking, og, som vi skal se, til ulike typer sortering. Binærsøk fra notatet er et godt eksempel. N iær rekursjon (jada, det er ikke noe ord) er der hvert metodekall kaller videre mange ganger. Metoden inneholder typisk en løkke og kaller rekursivt videre for hver runde i løkka. Her kommer selvfølgelig generering av permutasjoner inn. Dette er tøysekode som prøver å illustrere hvordan vi kunne gått frem for å søke etter en fil på disken. Fil finnefilen(string filnavn, Katalog katalog) { // Gå gjennom alle filer i katalogen for(<alle filer i katalog>) { if(<fil nr i matcher filnavn>) { // Funnet! return <fil nr i>; 10 // Gå gjennom alle underkataloger for(<alle kataloger i katalogen>) { // Sjekk om noen av disse inneholder filen Fil f = finnefilen(filnavn, <katalog nr i>); if(f!= null) { // funnet i underkatalog, returner videre return f; 20 // Metoden vil ikke kalle videre hvis det ikke er 9

// noen underkataloger, og dermed går den ikke for evig // og alltid :) Avskjæring igjen Hvis dere prøver å kjøre Sekvens-programmene og ber om å få 8 sifre generert, så ser dere at det genereres utrolig mange kombinasjoner. Det tar griselang tid å bli ferdig faktisk. Og i mange programmer, f.eks. dronningoppgaven, så er det bare noen ytterst få av disse kombinasjonene som faktisk er riktige. Hvis en av sekvensene setter den andre dronningen rett til høyre for den første slik at de slår hverandre, og man fortsetter å generere helt til man har kommet til slutten fordi man tenker at, psjt, dette kan jeg teste å til slutt, så vil man på et 8x8-brett prøve ut 8ˆ6, nesten 300000 totalt unyttige kombinasjoner på den måten. Bedre blir det ikke hvis brettet er på 14x14, det gir nesten 69 milliarder unyttige forsøk. Sliten datamaskin da. Vi ser derfor at vi har mye å spare på å gjøre litt avskjæring, ikke kalle videre når det vi driver med ikke fører noen vei. Avskjæringen består som oftest av en if-test som sier hvorvidt vi skal kalle videre eller ikke. Til slutt... Det finnes utrolig mye stoff om rekursjon på nettet. Det er ikke bare dere som har slitt med det. Hvis dere finner rekursjonskompendiet og foilene utilstrekkelige, så er det bare å gå og søke litt. 10

Figur 2: Forsetter vi her, gjør vi utrolig mange unyttige beregninger 11