Föreläsning 9 Innehåll Mer om rekursion söndra-och-härska-algoritmer dynamisk programmering backtracking Orientering om versionshantering med git Söndra och härska Divide and conquer teknik för att konstruera rekursiva algoritmer. Avser rekursiva algoritmer som gör minst två rekursiva anrop i varje upplaga. Problemet delas upp i två eller flera mindre som löses rekursivt (söndra). Lösningarna kombineras till en lösning till det ursprungliga problemet (härska). 2 87 34 12 56 10 2 34 87 10 12 56 2 10 12 34 56 87 Datavetenskap (LTH) Föreläsning 9 HT 2017 1 / 24 Söndra och härska Datavetenskap (LTH) Föreläsning 9 HT 2017 2 / 24 Fibonaccitalen Sortering av element i en vektor: Dela vektorn i två lika stora halvor Sortera (rekursivt) första halvan a[0... n/2] Sortera (rekursivt) andra halvan a[n/2 + 1... n 1] Slå samman de båda sorterade delvektorerna så att hela vektorn a[0... n 1] blir sorterad Det är i härska-steget som själva algoritmkonstruktionen ligger. Söndra-stegen är bara rekursiva anrop. Sorteringsmetoden, Mergesort, blir mycket snabb när steg 3 utförs på ett effektivt sätt. Vi återkommer till denna metod senare i kursen. Leonardo Pisano Fibonacci Born: 1170 in (probably) Pisa (now in Italy) Died: 1250 in (possibly) Pisa (now in Italy) A certain man put a pair of rabbits in a place surrounded on all sides by a wall. How many pairs of rabbits can be produced from that pair in a year if it is supposed that every month each pair begets a new pair which from the second month on becomes productive? Datavetenskap (LTH) Föreläsning 9 HT 2017 3 / 24 Datavetenskap (LTH) Föreläsning 9 HT 2017 4 / 24
Fibonaccitalen söndra och härska Definition Fn = Fn 1 + Fn 2 för n 2 F0 = 0 F1 = 1 return n; return fib(n - 1) + fib(n - 2); Diskutera Fibonaccitalen return n; return fib(n - 1) + fib(n - 2); Är detta en bra metod? Hur många anrop av metoden fib krävs för att beräkna andra fibonaccitalet? Tredje? Fjärde? Datavetenskap (LTH) Föreläsning 9 HT 2017 5 / 24 Fibonaccitalen effektivitet Datavetenskap (LTH) Föreläsning 9 HT 2017 6 / 24 Dynamisk programmering Samma Fibonaccital kommer att beräknas många gånger. Ineffektivt! Det blir exponentiell tidskomplexitet, dvs O(2 n ) för att beräkna fib(n). Studera t ex ett anrop fib(4): fib(3) fib(4) fib(2) Dynamisk programmering innebär att man i en tabell håller reda på vilka instanser av problemet man redan löst. När man behöver lösningen till en viss instans kontrollerar man först i tabellen om den redan beräknats. I så fall hämtas lösningen där, d.v.s. man gör inget rekursivt anrop. Om lösningen inte finns i tabellen gör man ett rekursivt anrop och sätter sedan in den beräknade lösningen i tabellen. fib(2) fib(0) 0 1 1 2-1 -1-1 -1-1 fib(0) 0 1 2 3 4 5 6 7... Datavetenskap (LTH) Föreläsning 9 HT 2017 7 / 24 Datavetenskap (LTH) Föreläsning 9 HT 2017 8 / 24
Dynamisk programmering - Fibonaccitalen long[] table = new long[n+1]; // skapa en tabell for (int i = 0; i <= n; i++) { table[i] = -1; // negativt värde <=> ej beräknat return recfib(table, n); private static long recfib(long[] table, int n) { if (table[n] < 0 ) { table[n] = n; table[n] = recfib(table, n-1) + recfib(table, n-2); return table[n]; Dynamisk programmering - Fibonaccitalen Dynamisk programmering för Fibonaccitalen har linjär tidskomplexitet. Varje tal beräknas en gång. fib(4) fib(3) fib(2) fib(0) Datavetenskap (LTH) Föreläsning 9 HT 2017 9 / 24 Fibonaccitalen iterativ lösning Effektiviteten vid beräkning av Fibonaccitalen med hjälp av dynamisk programmering är av samma storleksordning som iden iterativa algoritmen, d.v.s. linjär: return n; int nbr1 = 0; int nbr2 = 1; int res = 0; for (int i = 2; i <= n; i++) { res = nbr1 + nbr2; nbr1 = nbr2; nbr2 = res; return res; Datavetenskap (LTH) Föreläsning 9 HT 2017 11 / 24 Datavetenskap (LTH) Föreläsning 9 HT 2017 10 / 24 Fibonaccitalen alternativ, rekursiv lösning Notera: F (n) alltid bygger på seriens två föregående tal. Om vi returnerar ett par av heltal (vektor) behöver varje värde bara beräknas en gång: private static long[] recfib(int n) { // returnera [F(n-1), F(n)] long[] result = { 0, n ; return result; long[] prev = recfib(n - 1); long[] result = { prev[1], prev[0] + prev[1] ; return result; return recfib(n)[1]; Ingen generell ersättning för dynamisk programmering, men praktiskt just här, då det bara är den föregående dellösningen som behövs. Datavetenskap (LTH) Föreläsning 9 HT 2017 12 / 24
Backtracking Åtta damer på ett schackbräde Allmän strategi för att lösa vissa typer av problem, där vi kan bryta ner problemet i ett antal steg. Exempel: Idé: Hitta den kortaste vägen genom en labyrint Placera ut åtta damer på ett schackbräde Lös ett Sudoku-pussel Pröva en hypotes (ta ett steg i labyrinten, placera ut en dam, sätt ut en Sudoku-siffra,...) Ofta praktiskt att notera detta i något slags datastruktur Undersök rekursivt lösningarna som återstår Basfall: lyckad eller misslyckad lösning I schack hotar en dam en pjäs på samma rad, kolonn, eller diagonal. Kan man placera ut åtta sådana damer på ett schackbräde (8 8), utan att någon av dem hotar någon annan? Datavetenskap (LTH) Föreläsning 9 HT 2017 14 / 24 Datavetenskap (LTH) Föreläsning 9 HT 2017 13 / 24 Åttadamersproblemet: en lösning (av flera) Åttadamersproblemet: mitt i en lösning, fem damer återstår Pröva att placera ut en dam på rad 3, på var och en av de åtta positionerna, i tur och ordning. För varje position vi prövar: Kolla om det gick att göra utan att den nya damen hotar någon befintlig dam. Om det gick bra, gör ett nytt rekursivt anrop för att placera ut de återstående fyra damerna. Räkna antalet lösningar. Hur många lösningar finns det? Låt oss använda backtracking. Datavetenskap (LTH) Föreläsning 9 HT 2017 16 / 24 Datavetenskap (LTH) Föreläsning 9 HT 2017 15 / 24
Rekursionsträd för fyra damer Pseudokod för att lösa åttadamersproblemet Pseudokod för att skriva ut alla lösningar och räkna dem: int solve(board, row) if (row == 8) print board return 1 solutions = 0 for column = 0 to 7 if (tryaddqueen(board, row, column)) solutions += solve(board, row+1) removequeen(board, row, column) return solutions Anropas med solve(board, 0). Koden kan generaliseras till att hantera n damer på ett n n-bräde. Datavetenskap (LTH) Föreläsning 9 HT 2017 17 / 24 Pseudokod för backtracking Pseudokod för allmän backtracking som kollar om det finns minst en lösning och skriver ut den: boolean solve(v) if v is a solution print v return true for each promising choice c make choice c if (solve(v with c)) return true unmake choice c return false Datavetenskap (LTH) Föreläsning 9 HT 2017 19 / 24 Datavetenskap (LTH) Föreläsning 9 HT 2017 18 / 24 När ska rekursion användas? Vissa av de exempel vi sett kan lika enkelt lösas icke-rekursivt. Ex: beräkning av n! Ibland leder rekursion till väldigt ineffektiva algoritmer. Ex: fibonaccitalen (om man inte tänker sig för) Divide-and-conquer möjliggör effektiva algoritmer Effektivt för mergesort (kommer senare)......men ineffektivt för fibonaccitalen Rekursion bör användas då Det är svårt att uttrycka lösningen icke-rekursivt. Ex: algoritmer som manipulerar datastrukturer som träd och grafer. Det finns en icke-rekursiv lösning, men den rekursiva är effektivare. Ex: sortering Det finns icke-rekursiv lösning och en lika effektiv rekursiv lösning som är enklare (att förstå, implementera... ). Ex: binärsökning, behandla elementen i en lista i omvänd ordning Datavetenskap (LTH) Föreläsning 9 HT 2017 20 / 24
git git: ett verktyg, byggt för ett sätt att arbeta git: A completely ignorant, childish person with no manners. (www.urbandictionary.com) Ett versionshanteringssystem: Koden i en gemensam databas (gitlab.com) Varje användare har en egen lokal kopia Integrerat i Eclipse Vi kan skicka lokala förändringar till databasen (commit+push) Vi kan hämta förändringar från databasen till den lokala kopian (pull) Många fler finesser... Datavetenskap (LTH) Föreläsning 9 HT 2017 22 / 24 Datavetenskap (LTH) Föreläsning 9 HT 2017 21 / 24 Detaljerade instruktioner för git Exempel på vad du ska kunna Detaljerade instruktioner för hur du använder git, Eclipse och gitlab ihop finner du i filmform på kurssidan: cs.lth.se/edaa30 (Föreläsning 9) Instruktionerna utgår från att du och dina projektkollegor redan skapat varsitt konto på gitlab.com. Förklara begreppet söndra-och-härska i samband med rekursion. Resonera om tidskomplexitet för rekursiva algoritmer. Använda dynamisk programmering för att eliminera onödiga rekursiva anrop. Använda backtracking för att lösa vissa problem (typiskt av kombinatorisk karaktär). Vid behov, om du vill: använda git med Eclipse för att samarbeta i programmeringsprojekt. Datavetenskap (LTH) Föreläsning 9 HT 2017 23 / 24 Datavetenskap (LTH) Föreläsning 9 HT 2017 24 / 24