En algoritme for permutasjonsgenerering



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

Backtracking som løsningsmetode

Backtracking som løsningsmetode

INF1010 Rekursjon. Marit Nybakken 1. mars 2004

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

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

INF1010 Sortering. Marit Nybakken 1. mars 2004

Backtracking: Kombinatorikk og permutasjoner

TDT4100 Objektorientert programmering

UNIVERSITETET I OSLO

N-dronningproblemet Obligatorisk oppgave 1 I120, H-2000

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

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

Kombinatorisk søking, rekursjon, avskjæring

Rekursjon som programmeringsteknikk

INF Seminaroppgaver til uke 3

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

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

Algoritmer og Datastrukturer

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

Løsningsforslag EKSAMEN

EKSAMEN. Emne: Algoritmer og datastrukturer

TDT4100 Objektorientert programmering

Quicksort. Fra idé til algoritme.

EKSAMEN med løsningsforslag

Dagens forelesning. Java 13. Rollefordeling (variant 1) Rollefordeling (variant 2) Design av større programmer : fordeling av roller.

UNIVERSITETET I OSLO

INF1000 (Uke 15) Eksamen V 04

INF1000 (Uke 15) Eksamen V 04

UNIVERSITETET I OSLO

Kapittel 9: Sortering og søking Kort versjon

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

Algoritmer og Datastrukturer

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

INF1010 notat: Binærsøking og quicksort

Norges Informasjonsteknologiske Høgskole

Oppsummering. Kort gjennomgang av klasser etc ved å løse halvparten av eksamen Klasser. Datastrukturer. Interface Subklasser Klasseparametre

Argumenter fra kommandolinjen

Algoritmer og Datastrukturer

Løsningsforslag 2017 eksamen

Forklaring til programmet AbstraktKontoTest.java med tilhørende filer Konto.java, KredittKonto.java, SpareKonto.java

2 Om statiske variable/konstanter og statiske metoder.

UNIVERSITETET I OSLO

UNIVERSITETET I OSLO

Feilmeldinger, brukerinput og kontrollflyt

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

Algoritmer og datastrukturer Kapittel 2 - Delkapittel 2.1

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

INF1000 Prøveeksamen Oppgave 7 og 9

Gjøre noe i hele treet = kalle på samme metode i alle objekten. Java datastruktur Klassestruktur

Forelesning inf Java 4

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

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

Løsningsforslag til INF110 h2001

INF1000 Metoder. Marit Nybakken 16. februar 2004

Oppgave 3 a. Antagelser i oppgaveteksten. INF1020 Algoritmer og datastrukturer. Oppgave 3. Eksempelgraf

INF Uke 10. Ukesoppgaver oktober 2012

3 emner i dag! INF1000 Uke 5. Objekter og pekere. null. Litt om objekter, pekere og null Filer og easyio Litt mer om tekster

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

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

Algoritmer og datastrukturer Eksamen

BOKMÅL Side 1 av 7. KONTINUASJONSEKSAMEN I FAG TDT4100 Objektorientert programmering / IT1104 Programmering, videregående kurs

Algoritmer og datastrukturer Kapittel 1 - Delkapittel 1.3

Eks 1: Binærtre Binærtretraversering Eks 2: Binærtre og stakk

Kapittel 8: Sortering og søking INF100

INF1010 våren januar. Objektorientering i Java

Algoritmer og datastrukturer Kapittel 3 - Delkapittel 3.1

UNIVERSITETET I OSLO

Algoritmer og Datastrukturer

Kapittel 8: Sortering og søking

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

OO-eksempel. Modellen ser slik ut: Studenter + antstudenter : int = 0

INF1010 våren Arv og subklasser del 1

Løsningsforslag Test 2

Algoritmer og Datastrukturer

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

UNIVERSITETET I OSLO

IN1010 våren januar. Objektorientering i Java

UNIVERSITETET I OSLO

IN2010: Algoritmer og Datastrukturer Series 2

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

INF Løsning på seminaropppgaver til uke 8

Algoritmer og Datastrukturer

PG4200 Algoritmer og datastrukturer Lab 1. 8.januar I dag skal vi undersøke en rekke velkjente databeholdere i Java:

Algoritmer og datastrukturer Kapittel 9 - Delkapittel 9.2

TDT4102 Prosedyre og Objektorientert programmering Vår 2014

EKSAMEN. Algoritmer og datastrukturer

HØGSKOLEN I SØR-TRØNDELAG

Husk å registrer deg på emnets hjemmeside!

UNIVERSITETET I OSLO

Forelesning inf Java 5

INF1010 Rekursive metoder, binære søketrær. Algoritmer: Mer om rekursive kall mellom objekter Ny datastruktur: binært tre

Forelesning inf Java 5

LO191D/LC191D Videregående programmering

PG 4200 Algoritmer og datastrukturer Innlevering 2

Kapittel 8: Sortering og søking INF100

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

UNIVERSITETET I OSLO

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

Transkript:

Innledning La oss tenke oss at vi har en grunnskole-klasse på 25 elever der enkelte av elever er uvenner med hverandre. Hvis uvenner sitter nær hverandre blir det bråk og slåssing. Er det mulig å plassere elevene slik at uvenner ikke kommer i nærheten av hverandre? En tenkelig løsning vil være å prøve forskjellige måter å plassere de 25 elevene til man finner en plassering som kan godtas. Dessverre kan 25 elever plasseres på 25!=1,55*10 25 måter. Det vil si at en svært rask datamaskin som genererer og tester en milliard permutasjoner per sekund vil bruke ca 500 millioner år på å teste alle permutasjonene. Og hvis det er mye uvennskap i klassen er det ikke en gang sikkert at man da har løst problemet. (Hvilket kanskje ikke er så interessant etter 500 millioner år) Mange problemer vi prøver å løse kan tilnærmes ved en eller annen form for testing av alle mulige permutasjoner, men vi støter dessverre stadig på samme begrensning når antallet komponenter man skal generere permutasjoner av stiger blir problemet fort uhåndterlig. I det følgende skal vi vise forskjellige algoritmer for å generere permutasjoner. Vi vil også diskutere avskjæring, en metode for å redusere tidsforbruket, slik at det blir mulig å håndtere litt større problemer. En algoritme for permutasjonsgenerering For enkelhets skyld vil vi generere permutasjoner av tallene fra 0 til n-1 i en array. Det er enkelt å endre algoritmen slik at den permuterer andre typer objekter. Ideen er at vi bytter om på rekkefølgen av tallene på en systematisk måte slik at alle mulige permutasjoner blir generert. Algoritmen er vist på neste side. Prinsippet er at lagperm(int j) kalles rekursivt. Lagperm lager alle permutasjoner fra og med siffer j, til og med siffer n-1. Dette gjøres ved at siffer j byttes mot siffer j+1 og lagperm(j+1) kalles. Deretter byttes siffer j mot j+2 og lagperm (j+1) kalles. Dette fortsetter til alle sifre til høyre for siffer j har byttet plass med j og alle permutasjoner av disse sifrene er blitt dannet. For å rydde alt tilbake på plass før lagperm(j) returnerer, blir alle siffer til venstre for j flyttet en plass til venstre og siffer j flyttes til slutt. Denne operasjonen kalles rotervenstre(j) La oss si at vi skal generere permutasjoner av fire tall slik at vi starter med disse tallene i arrayen numbers: 0 1 2 3 lagperm(0) kaller først lagperm(1) som kaller lagperm(2) som kaller lagperm(3), som skriver ut 0 1 2 3 og returnerer til lagperm(2). Lagperm(2) kjører så bytt(2,3) og vi får 0 1 3 2. Deretter kjøres lagperm(3) som skriver ut 0 1 3 2 deretter retur til lagperm(2) som kjører rotervenstre(2) og vi er tilbake til 0 1 2 3 Deretter returneres til lagperm(1) som kjører bytt(1,2) og vi har 0 2 1 3. lagperm(1) kaller da lagperm(2), som kaller lagperm(3), som skriver ut 0 2 1 3 og returnerer. lagperm(2) kaller bytt(2,3), og vi har 0 2 3 1, lagperm(3) kalles og skriver ut 0 2 3 1

Deretter retur til lagperm (2) som kjører rotervenstre(2) og vi er tilbake til 0 2 1 3 Retur til lagperm(1) som nå kaller bytt(1,3) og vi får 0 3 1 2. Kall til lagperm(2) som kaller lagperm(3) som skriver ut 0 3 1 2 og retur til lagperm(2) som kjører bytt(2,3) og vi får 0 3 2 1, som skrives ut via lagperm(3) som 0 3 2 1 og ryddes med rotervenstre(2) til 0 3 1 2. Retur til lagperm(1) som kjører rotervenstre(1) og rydder opp til 0 1 2 3, Deretter retur til lagperm(0) som kjører bytt(0,1) og vi har 1023 osv. public class SimplePermGenerator { int n; //antall tall som skal permuteres int[] numbers; //permutasjonene lages her public SimplePermGenerator(int n) { numbers=new int[n]; for (int i=0; i<n;i++){ numbers[i]=i; //lager den første permutasjonen 0, 1, 2,.. void lagperm(int j){ if (j==n-1){ printperm(); else{ //basistilfelle vi er kommet til siste tall //en ferdig permutasjon kan skrives ut. //lag alle permutasjoner av de neste tallene for (int i=j+1;i<n;i++){ bytt(j,i);//bytt tall j med hver av de neste tallene //lag alle perms. av de neste tallene rotervenstre(j);//rydd opp etter alle byttene return; void bytt(int i, int j){ int temp=numbers[i]; numbers[i]=numbers[j]; numbers[j]=temp; return; void rotervenstre(int j){ //flytter tallene til høyre for j ett trinn mot venstre og legger //tall j helt bakerst. int temp=numbers[j]; for (int i=j;i<n-1;i++) numbers[i]=numbers[i+1]; numbers[n-1]=temp; return; void printperm(){ for(int i=0;i<n;i++)system.out.print(numbers[i]+" "); System.out.println(""); public static void main(string[] args){ SimplePermGenerator permgen=new SimplePermGenerator(4); permgen.lagperm(0);

En enklere algoritme Hvis en studerer permutasjonene som ble generert i SimpleGenerator : 0 1 2 3, 0 1 3 2, 0 2 1 3, 0 2 3 1, 0 3 1 2, 0 3 2 1, 1 0 2 3, 1 0 3 2, osv. ser en at en enklere måte å generere dem på kan oppnås ved å betrakte hvert tall som et siffer i et n tallssystem og så bruke en telleteknikk der en går fra 0000 og oppover. Dersom en så dropper alle kombinasjoner der noen sifre er like har man laget permutasjonene. En algoritme som bruker denne teknikken er gjengitt nedenfor: public class SimplerGenerator { int n; int[] numbers; boolean [] brukt; void lagperm(int j){ for (int i=0;i<n;i++){ if (!brukt[i]){ numbers[j]=i; brukt[i]=true; if (j==n-1)//ferdig permutasjon printperm(); else brukt[i]=false; public SimplerGenerator(int n) { numbers=new int[n]; brukt=new boolean[n]; Den boolske arrayen brukt[ ] viser om et gitt tall allerede er i bruk. Hvis så er tilfelle hopper vi over å bruke tallet og prøver neste. Merk at når vi slutter å bruke et gitt tall i setter vi brukt[i] til false. Som man ser slipper vi her hjelperutinene bytt og rotervenstre. Vi har fått en enklere algoritme. Denne algoritmen kan virke mindre effektiv ettersom vi må gjennomgå alle "tall" som kan lages ved hjelp av de aktuelle "sifrene" og ikke bare permutasjoner av sifrene slik som i den forrige algoritmen, men den enkle algoritmen sparer tid på å være enklere slik at man ved lave n ikke vil merke særlig forskjell. Den enklere rutina har også den fordelen at den holder greie på hvilke tall som er brukt. Dette kan i enkelte tilfelle være nyttig når vi skal gjøre avskjæringer..

Avskjæringer For å redusere antall permutasjoner å generere og dermed redusere kjøretida når vi skal løse et problem, kan vi kutte videre beregninger av permutasjoner hvis de tallene vi hittil har generert ikke kan godtas. Ved søk etter konfliktfri plassering av elever, som vi innledet med, kan vi avbryte videre generering av en rekke permutasjoner hvis vi har konflikt mellom elever vi allerede har plassert. Hvis elev 0 og elev 1 er uvenner er det bortkastet arbeid å lage permutasjoner der disse elevene blir plassert ved siden av hverandre, det vil si at vi kan kutte ut (skjære av) alle permutasjoner som begynner med 0 1 og alle permutasjoner som begynner med 1 0. Å kutte videre beregning når vi vet at det ikke er vits i å fortsette kalles avskjæring (pruning på engelsk). Hvis avskjæringen er så sterk at alle permutasjoner som blir ferdig generert er brukbare, sier vi at avskjæringen er fullstendig. Eksempel: Dronningoppgaven: Dronning er en sjakkbrikke som kan bevege seg både vannrett, loddrett og diagonalt. Dronningoppgaven går ut på å plassere n dronninger på et nxn sjakkbrett slik at de ikke kan slå hverandre. Da kan ingen dronninger stå i samme kolonne og ingen dronninger kan stå på samme linje. To dronninger kan heller ikke stå på samme diagonal. Her er en løsning på et 4 x 4 brett. 0 1 2 3 0 D 1 D 2 D 3 D Denne løsningen kan representeres ved permutasjonen: 1 3 0 2 Oppgave: Forklar hvordan representasjonen av dronningposisjonene som permutasjon sikrer at ingen dronninger står på samme linje eller i samme kolonne. En løsning er da gyldig dersom ingen dronning står på samme diagonal. To dronninger i og j er på samme fallende (\) diagonal dersom: numbers[i] i = numbers[j] - j To dronninger i og j er på samme stigende (/) diagonal dersom: numbers[i] + i = numbers[j] + j På neste side er SimplerGenerator utstyrt med en avskjærende testrutine testdiag som sjekker at den dronninga som ble plassert sist ikke er på samme diagonal som noen tidligere plassert dronning. Dersom den er på samme diagonal blir videre beregning på permutasjonen avskåret. Oppgave: Er avskjæringa på neste side fullstendig?

public class Dronning { int n; int[] numbers; boolean [] brukt; public Dronning(int n) { numbers=new int[n]; brukt=new boolean[n]; for (int i=0; i<n;i++){ numbers[i]=i; void lagperm(int j){ for (int i=0;i<n;i++){ if (!brukt[i]){ numbers[j]=i; brukt[i]=true; if ((j==n-1)&&testdiag(j))//ferdig permutasjon printperm(); else if (testdiag(j)) brukt[i]=false; boolean testdiag(int j){ boolean ok=true; for (int i=0;i<j;i++){ ok = ok && ((numbers[i]-i)!=(numbers[j]-j)); ok = ok && ((numbers[i]+i)!=(numbers[j]+j)); return ok; Oppgave: Skriv om den andre permutasjonsgeneratoren slik at du får plassert kall til test for avskjæring. (Tips se liten skrift nedenfor) Du trenger testkall på tre steder

Generalisering av permutasjonsgenerator Som vi ser av det siste eksempelet, kan permutasjonsgeneratoren spesialtilpasses til problemet som skal løses, men for å spare utviklingsarbeid er det bedre å lage en generell permutasjonsgenerator som kan brukes til ulike problemer uten å endres. Vi trenger da et grensesnitt som spesifiserer hvordan permutasjonene kan avskjæres og hvordan de kan overleveres til klassen som skal bruke dem. Forslag til et slikt grensesnitt følger nedenfor: public interface PermInterface { // implementeres av klasser som skal bruke en permutasjonsgenerator. public void useperm(); //melding om at ny permutasjon er klar til bruk public boolean cutoff(int j);//angir om beregning skal avskjæres Da må permutasjonsgeneratoren tilpasses slik at den bruker dette interfacet for å kommunisere med klassen som skal bruke den: public class PermGenerator { int n; //antall tall som skal permuteres int[] numbers; //permutasjonene lages her PermInterface pinterface; public PermGenerator(int n,perminterface p) { pinterface=p; numbers=new int[n]; for (int i=0; i<n;i++){ numbers[i]=i; void lagperm(int j){ if (j==n-1){ if (!pinterface.cutoff(j)) pinterface.useperm(); else{ if (!pinterface.cutoff(j)) for (int i=j+1;i<n;i++){ bytt(j,i); if (!pinterface.cutoff(j)) rotervenstre(j); return; //rotervenstre og bytt er som vist tidligere

Nedefor ser du en klasse som løser dronningoppgaven ved hjelp av den generelle permutasjonsgeneratoren fra forrige side. public class Dronning2 implements PermInterface{ int n; PermGenerator pg; Obs! public boolean cutoff(int j){ boolean cut=false; for (int i=0;i<j;i++){ //skjær av hvis dronningen er på samme diagonal som en annen cut = cut ((pg.numbers[i]-i)==(pg.numbers[j]-j)); cut = cut ((pg.numbers[i]+i)==(pg.numbers[j]+j)); return cut; public void useperm(){ for (int kol=0;kol<n;kol++)system.out.print("+---"); System.out.println("+"); for (int linje=0;linje<n;linje++){ for (int kol=0;kol<n;kol++) if (pg.numbers[kol]==linje) System.out.print(" D "); else System.out.print(" "); System.out.println(" "); for (int kol=0;kol<n;kol++)system.out.print("+---"); System.out.println("+"); System.out.println(""); public Dronning2(int n) { pg=new PermGenerator(n, this); pg.lagperm(0); public static void main(string[] args){ Dronning2 dronningene=new Dronning2(8); Permutasjonsgeneratoren kjøres med lagperm(0) Det er sikrest å instansiere en ny permutasjonsgenerator når du skal starte med ny permutasjonsgenerering, men det er ikke sikkert det er nødvendig Hvis du ikke vil generere alle mulige permuteringer, men greier deg med den første løsningen, må du bygge om permutasjonsgeneratoren slik at den kan stoppes, f.eksempel ved å avskjære ytterligere permutasjonsgenerering når et flagg boolean ferdig er satt. Det kan finnes bedre alternativer enn permutasjonsgenerering for å finne løsninger på et gitt problem. Husk at permutasjonsgenerering kan få svært lang kjøretid når det er mange elementer som skal permuteres. Det er dårlig design at permutasjonsgeneratoren eksponerer numbers-arrayen som public. Oppgave: Forbedre dette!