Exempel: Kappsäcksproblemet Backtracking Dynamisk programmering Föreläsning (Weiss kap..-) Kan man ur en grupp föremål F,,F N med vikterna V,,V N välja ut en delgrupp som väger exakt M kilo? Exempel: föremål vikt Snåla algoritmer Backtracking Exempel: Kappsäcksproblemet, labyrintsökning Dynamisk programmering Man kan packa till alla totalvikter upp till kilo utom till vikterna,, och 0 F F F F F Snåla algoritmer En snål (eng. greedy) algoritm väljer den för tillfället optimala lösningen utan hänsyn till framtida konsekvenser. Exempel: En snål strategi för kappsäcksproblemet vore att prova vikterna i avtagande följd. Metoden löser inte problemet givet uppsättningen på föregående bild för t.ex. totalvikten kg. Varför inte? Problemet kan dock lösas med en snål algoritm om vikterna är tvåpotenser,,, 8,, Rekursiv algoritmidé Kappsäcksproblemet kan lösas rekursivt: Antag att vi skall packa upp till M kilo med föremål ur samlingen F,,F N Fallanalys: (- är basfallen). Om kappsäcken väger M kilo har vi löst problemet.. Om kappsäcken väger mer än M kilo har vi misslyckats.. Om det finns mer plats men inte fler föremål har vi misslyckats. återstår två fall: (rekursionssteget). Vi tar med F N och försöker packa resterande utrymme med F,,F N-. Vi slänger F N och försöker packa resterande utrymme med F,,F N- Förenkla problemet genom att bara betrakta vikterna V,,V N Låt S beteckna packningens vikt Problemet är att avgöra om det finns en delvektor i V,,V N vars summa är M. Generalisering: Definiera predikatet packa(s, M, V,,V N ) som är sant om det finns en delvektor i V,,V N vars summa är M - S och falskt annars Definiera packa(s, M, V,,V N ) rekursivt: Basfallen:. Returnera sant om S = M. Returnera falskt om S > M. Returnera falskt om S < M och N = 0 Rekursionssteg: S < M och N > 0. Lös problemet för utrymmet M - S och vikterna V,,V N Rekursionsantagande: Antag att packa(x, M, V,,V N- ) löser problemet för ett godtyckligt X S samt vikterna V,,V N-
Med stöd av rekursionsantagandet kan vi anta att: packa(s + V N, M, V,,V N- ) löser problemet för fallet då V N tas med och att packa(s, M, V,,V N- ) löser problemet för fallet då V N slängs Alltså: returnera SANT för S och V,,V N om packa(s + V N, M, V,,V N- ) eller packa(s, M, V,,V N- ) returnerar SANT. Implementering i Java boolean packa( int S, int M, int[] V, int N ) if ( S == M ) if ( S > M N == 0 ) return false; return packa( S + V[ N - ], M, V, N - ) packa( S, M, V, N - ); 8 Vilka vikter löste problemet? En vikt får bara skrivas ut när vi vet att den bidrar till lösningen. boolean packa( int S, int M, int[] V, int N ) if ( S == M ) if ( S > M N == 0 ) return false; if( packa( S + V[ N - ], M, V, N - ) ) System.out.print( V[ N - ] ); return packa( S, M, V, N - ); Backtracking Att lösa ett problem genom att rekursivt prova alla möjligheter. I varje rekursionssteg väljs den bästa dellösningen. Exempel Labyrint Tic-Tac-Toe se Weiss. 9 0 Exempel: Labyrintsökning Givet: En rektangulär labyrint L med ickecykliska gångar 8 9 8 9 0 Tre olika sökproblem Resultat. Finns det en utväg från (x,y)? Ja/Nej. Skriv ut en utväg från (x,y) en väg. Skriv ut den kortaste utvägen från (x,y) optimal väg En position (cell) i labyrinten är antingen golv eller vägg (svart) Gångarna har bredden Förflyttning sker stegvis en cell i taget vågrätt eller lodrätt
Genom att ta ett steg kommer vi in i en dellabyrint. (Man backar bara till en cell för att prova en annan riktning.) Ett steg uppåt från (,) tar oss till dellabyrinten (L ) 8 9 0 Divide & Conquer Problemet att hitta ut från (,) i L kan delas upp rekursivt i delproblemen att hitta ut via (,) i L eller via (,) i L, eftersom vi kan nå L och L i ett steg De tre problemen. Det finns en väg ur L om det finns en väg ur L, eller en väg ur L. Skriv ut en väg via L, eller en via L Ett steg nedåt från (,) tar oss till dellabyrinten (L ) 8 9 0 8 9. Skriv ut den kortaste av vägarna via L resp. L Basfall: a) Återvändsgränd b) Ute Rekursionsantagande: Sökalgoritmen kan avgöra problemet för dellabyrinterna Pseudoalgoritm boolean finnsutväg( pos, labyrint ) om pos är utanför labyrinten returnera SANT annars för varje riktning r p' = steg( pos, r ) om ärtillåtetsteg från pos till p' och finnsutväg( p', labyrint ) returnera SANT returnera FALSKT Klassen Labyrint public class labyrint public Labyrint( String filnamn ); public void hittaut (Position startpos )... private boolean ärutanför( Position p )... private Position steg( Position p, Riktning r )... private boolean ärtillåtetsteg( Position gpos,position pos, Riktning rikt )... private boolean sök ( Position gpos, Position pos )... private boolean ärgolv( Position p )... private char[][] labyrinten; private int bredd, höjd; Datatyper för positioner och riktningar public class Position public Position( int rad, int kolumn ) this.rad = rad; this.kolumn = kolumn; public boolean equals( Position rhs ) return ( rad == rhs.rad && kolumn == rhs.kolumn ); public int rad, kolumn; ; public enum Riktning VÄNSTER, UPP, HÖGER, NER Tre användbara operationer boolean ärutanför( Position pos ); // avgör om pos är utanför labyrinten eller ej Position steg( Position pos, Riktning riktning ); // returnerar positionen man kommer till // genom ett steg från pos i angiven riktning bool tillåtetsteg( Position gpos, Position pos, Riktning riktning ); // avgör om ett steg från pos i angiven riktning // leder utanför labyrinten, eller till en annan // golvposition än gpos 8
Ett steg i rätt riktning? Metoden ärutanför gpos vi gick härifrån... Nix backa! rikt sök boolean ärutanför( Position p ) return p.rad < 0 p.rad >= höjd p.kolumn < 0 p.kolumn >= bredd; pos...och hit steg(pos,rikt) kan vi gå dit? 9 0 Metoden steg Metoden ärtillåtetsteg Position steg( Position p, Riktning r ) switch ( r ) case VÄNSTER: return new Position( p.rad, p.kolumn - ); case HÖGER: return new Position( p.rad, p.kolumn + ); case NER: return new Position( p.rad +, p.kolumn ); case UPP: return new Position( p.rad -, p.kolumn ); boolean ärtillåtetsteg( Position gpos, Position pos, Riktning rikt ) Position nypos = steg( pos, rikt ); return ärutanför( nypos ) ( labyrinten[nypos.rad][nypos.kolumn] == golv &&! nypos.equals( gpos ) ); Metoden hittaut Metoden Sök void hittaut( Position startpos ) if ( sök( startpos, startpos ) ) System.out.print( "det finns en utväg ); boolean sök( Position gpos, Position pos ) for ( Riktning riktning : Riktning.values() ) if ( ärtillåtetsteg( gpos, pos, riktning ) && sök( pos, steg( pos, riktning ) ) ) return false; // inlåst!?
Hur skriver man ut vägen? Metoden hittautväg När kan man tidigast skriva ut information om vägen? Hur räknar man ut den kortaste vägen? void hittautväg( Position startpos ) if (! sökutväg( startpos, startpos ) ) Metoden sökutväg boolean sökutväg( Position gpos, Position pos ) for ( Riktning riktning : Riktning.values() ) if ( ärtillåtetsteg( gpos, pos, riktning ) && sökutväg( pos, steg( pos, riktning ) ) ) System.out.print( riktning ); return false; // inlåst!? Hur skriver man ut vägen rättvänd? Lägg riktningarna på en stack istället för att skriva ut dem under sökningen Skriv ut innehållet i stacken när sökningen är klar 8 Metoden hittarättutväg void hittarättutväg( Position startpos ) Stack<Riktning> vägen; if ( sökrättväg( startpos, startpos, vägen ) ) while (! vägen.isempty() ) skrivriktning( vägen.peek() ); vägen.pop(); Metoden sökrättväg boolean sökrättväg( Position gpos, Position pos, Stack<Riktning> vägen ) for ( Riktning riktning : Riktning.values() ) if ( ärtillåtetsteg( gpos, pos, rikt ) && sökrättväg( pos, steg( pos, rikt ), vägen ) ) vägen.push( rikt ); return false; 9 0
Hur finner man den kortaste vägen? Inför en speciell datatyp för vägar Operationer: returnera vägens längd: längd() lägg till en riktning: add skriv ut vägen: print() Klassen Väg class Väg public Väg() längden = Integer.MAX_VALUE; vägen = new LinkedList(); public int längd() return längden; public Väg add( Riktning r )... public void print()... private LinkedList<Riktning> vägen; private int längden; ; // Antag oändligt lång // Ger en ny väg inkl. r // FIFO-kö Metoden hittakortastevägen Metoden sökkortastevägen void hittakortastevägen( Position startpos ) Väg kortastevägen = sökkortastevägen( startpos, startpos, new Väg() ); if ( kortastevägen.längd() < Integer.MAX_VALUE ) kortastevägen.print(); Väg sökkortastevägen( Position gpos, Position pos, Väg vägen ) return vägen; Väg kortast, aktuell; for ( Riktning riktning : Riktning.values() ) if ( ärtillåtetsteg( gpos, pos, rikt ) ) aktuell = sökkortastevägen( pos, steg( pos, rikt ), vägen.add( rikt ) ); if ( aktuell.längd() < kortast.längd() ) kortast = aktuell; return kortast; Dynamisk programmering Problemet En rekursiv D&C-algoritm som löser många överlappande delproblem blir orimligt ineffektiv Dynamisk programmering Lösningarna på delproblemen sätts successivt in i en tabell. Inget delproblem löses flera gånger. Metoden kräver extra minne men kan i gengäld bli avsevärt effektivare än en naiv D&C-algoritm. Exempel: se Weiss. Ex. fibonaccital med dynamisk programmering fibtable 0 Hämta värdet från tabellen om n 8 Tekniken kallas memoization Komplettera tabellen om n >
public class MemoizingFib private long[] fibtable; int highfibindex; Implementering i Java Komplexitet trade off: tid - minne public MemoizingFib(int size) fibtable = new long[size]; fibtable[0] = fibtable[] = ; highfibindex = ; T(N) = tidsåtgång M(N) = minnesbehov för beräkning av det N:te fibonaccitalet public long fib(int n) if ( n >= fibtable.length ) // allocate a bigger table and copy elements if ( n > highfibindex ) fibtable[n] = fib(n-) + fib(n-); highfibindex = n; public static void main(string[] args) return fibtable[n]; MemoizingFib mf = new MemoizingFib(00); for ( int n = 0; n < 00; n++ ) System.out.println(mf.fib(n)); WC AC Iterativ fib Naiv rekursiv fib Memoiserande fib T(N) = O(N) M(N) = O() T(N) = O( N ) M(N) = O(N) T(N) = O(N) M(N) = O(N) T(N) = O() M(N) = O(N) 8