Föreläsning 1 Algoritmiska paradigm TDDC70/91: DALG Utskriftsversion av föreläsning i Datastrukturer och algoritmer 15 oktober 013 Tommy Färnqvist, IDA, Linköpings universitet 1.1 Innehåll Innehåll 1 Dekomposition 1 Totalsökning 3 3 Dynamisk programmering 4 4 Giriga algoritmer 7 1. 1 Dekomposition Divide and conquer, söndra och härska Ett allmänt paradigm för algoritmdesign: Dela upp indata S i två eller fler disjunkta delmängder S 1,S,... Lös delproblemen rekursivt Kombinera lösningarna till delproblemen till en lösning till S Basfallen är delproblem av konstant storlek Analysen kan genomföras m.h.a. rekurrensrelationer 1.3 Exempel: Matrismultiplikation ( ( C11 C C = AB 1 A11 A = 1 C 1 C A 1 A ( B11 B 1 B 1 B Antag att vi har n n-matriser och att n = k för något k function MUL(A, B, n if n = 1 then return A B C 11 =ADD(MUL(A 11,B 11, n,mul(a 1,B 1, n, n C 1 =ADD(MUL(A 11,B 1, n,mul(a 1,B, n, n C 1 =ADD(MUL(A 1,B 11, n,mul(a,b 1, n, n C =ADD(MUL(A 1,B 1, n,mul(a,b, n, n return C Analys T (1 = 1 T (n = 8T ( n + 4 ( n } T (n O(n 3 1.4 1
Exempel: Matrismultiplikation (Strassen ( ( ( c11 c 1 a11 a = 1 b11 b 1 c 1 c a 1 a b 1 b Kan beräknas genom m 1 = (a 1 a (b 1 + b m = (a 1 + a (b 11 + b m 3 = (a 11 a 1 (b 11 + b 1 m 4 = (a 11 + a 1 b m 5 = a 11 (b 1 b m 6 = a (b 1 b 11 m 7 = (a 1 + a b 11 c 11 = m 1 + m m 4 + m 6 c 1 = m 4 + m 5 c 1 = m 6 + m 7 c = m m 3 + m 5 m 7 Analys Sammanlagt 7 multiplikationer och 18 additioner/subtraktioner Multiplikation av två n n-matriser tar tid T (1 = 1 T (n = 7T ( n + 18 ( n } T (n n.81 1.5 Strassen i praktiken Använd Strassens matrismultiplikationsalgoritm för stora matriser (n > b. Använd vanlig matrismultiplikation för mindre matriser (n b Hur ska brytpunkten väljas? n = 51 1.6 Exempel: Multiplikation av binära tal x = x n 1 x n...x n x n }{{} 1...x 1 x 0 = a n + b }{{} a b y = y n 1 y n...y n y n }{{} 1...y 1 y 0 = c n + d }{{} c d x y = a c n + (a d + b c n + b d Antag att n = k function MULT(x, y, k if k = 1 then return x y [a,b] x [c,d] y p 1 MULT(a,c,k 1 p MULT(b,d,k 1 p 3 MULT(a,d,k 1 p 4 MULT(b,c,k 1 return p 1 n + (p 3 + p 4 n + p Analys T (1 = Θ(1 och ( n T (n = 4T + Θ(n T (n O(n log 4 1.7
Exempel: Smartare multiplikation (Karatsuba A a c B b d C (a + b (c + d D A n + (C A B n + B D = a c n + (a d + b c n + b d = x y function SMARTMULT(x, y, k if k 4 then return x y [a,b] x [c,d] y A SMARTMULT(a,c,k 1 B SMARTMULT(b,d,k 1 C SMARTMULT(a + b,c + d,k 1 return A n + (C A B n + B Analys T (4 = Θ(1 T (n = 3 T ( n + Θ(n } T (n O(n log 3 O(n 1,58 1.8 Totalsökning Exhaustive search, totalsökning Gå igenom alla tänkbara lösningar och kolla om det är den sökta lösningen.detta görs med fördel rekursivt. Ofta är antalet tänkbara lösningar exponentiellt många och då går det bara att använda för små n. Totalsökning är en metod man tar till i sista hand. Det svåraste med totalsökning är normalt att se till att man går igenom varje tänkbar lösning en (och helst inte mer än en gång. Att sedan kolla om det är den sökta (eller optimala lösningen brukar vara lätt. Ibland kan man redan innan en tänkbar lösning är färdigkonstruerad se att den inte är en möjlig lösning. Då kan man strunta i att gå vidare med den och istället gå tillbaka och konstruera nästa möjliga lösning. Detta kallas backtracking. 1.9 TSP handelsresandeproblemet Hitta kortaste turen som passerar alla städer en gång. Olika varianter av problemet: Generell TSPprobleminstans: graf med kantvikter Euklidisk TSP i dimension dprobleminstans: städerna givna som koordinater i R d 1.10 Exempel: Lösning av generell TSP med totalsökning function TSP(n,d[1...n,1...n] minlen for i 1 to n do visited[i] false for i 1 to n do perm[1] i; visited[i] true CHECKPERM(, 0 visited[i] false return minlen procedure CHECKPERM(k, length if k > n then totallength length + d[perm[n], perm[1]] 3
if totallength < minlength then minlength totallength for i 1 to n do if visited[i] then perm[k] i; visited[i] true CHECKPERM(k + 1,length + d[perm[k 1],i] visited[i] false 1.11 3 Dynamisk programmering Beräkning av Fibonaccital Fibonaccitalen har en rekursiv definition:f 0 = 0F 1 = 1F i = F i 1 + F i för i > 1 Som rekursiv algoritm (första försöket: function BINARYFIB(k if k < then return k return BINARYFIB(k 1+BINARYFIB(k Analys av Fibonacci med binär rekursion Låt n k beteckna antalet rekursiva anrop gjorda av BinaryFib(k n 0 = n 1 = 1 n = n 1 + n 0 + 1 = 1 + 1 + 1 = 3 n 3 = n + n 1 + 1 = 3 + 1 + 1 = 5 n 4 = n 3 + n + 1 = 5 + 3 + 1 = 9 n 5 = n 4 + n 3 + 1 = 9 + 5 + 1 = 15 n 6 = n 5 + n 4 + 1 = 15 + 9 + 1 = 5 n 7 = n 6 + n 5 + 1 = 5 + 15 + 1 = 41 Värdet åtminstone fördubblas för vartannat värde på n k. D.v.s n k > k/. Antalet anropet växer exponentiellt! En bättre algoritm för Fibonaccital Använd linjär rekursion istället: function LINEARFIB(k if k = 1 then return (1,0 (i, j LINEARFIB(k 1 return (i + j,i Går i tid O(k Dynamisk programmering Tillämpbart på problem som vid första påseende verkar kräva mycket tid (möjligtvis exponentiellt förutsatt att vi har: Enkla delproblem: delproblemen kan definieras i termer av några få variabler Optimalitet hos delproblemen: det globalt optimala värdet kan definieras i termer av optimala delproblem Överlapp hos delproblemen: delproblemen är inte oberoende, istället överlappar de (och borde därför konstrueras botten-upp Dynamisk programmering Bestäm strukturen hos optimal lösning Ställ upp rekursion för optimalvärdet Beräkna delproblemens optimalvärden från små till stora Konstruera optimallösningen (ev. med extra information som lagras i steg 3 1.1 1.13 1.14 1.15 1.16 4
Exempel: Maximal triangelstig Problem: hitta stigen från toppen till botten som maximerar summan av de ingående talen. 7 3 8 8 0 1 7 4 4 4 5 6 5 n = antalet rader finns n 1 stigar att pröva a i j = element nr j på rad i 1.17 Dynamisk programmeringslösning Optimala lösningens strukturstig uppbyggd av delstigar RekursionLåt V [i, j] = värdet på bästa stigen från a i j ner till rad n { ai V [i, j] = j om i=n (sista raden a i j + max(v [i + 1, j],v [i + 1, j + 1] annars Beräkning for j = 1 to n do V [n, j] a n j for i = n 1 downto 1 do for j = 1 to i do V [i, j] a i j + max(v [i + 1, j],v [i + 1, j + 1] return V [1,1] Tidskomplexitet: O(n 1.18 Dkvenser En dkvens av en sträng x 0 x 1 x...x n 1 är en sträng på form x i1 x 1...x ik, där i j < i j+1 Inte samma sak som delsträng! Exempel: ABCDEFGHIJK Delsträng: CDEF Dkvens: ACEGIJK Dkvens: DFGHK Inte dkvens: DAGH 1.19 Längsta gemensamma dkvensproblemet (LCS Givet två strängar X och Y är längsta gemensamma dkvensproblemet (LCS att hitta den längsta dkvensen gemensam för X och Y Har tillämpningar vid testning av överensstämm mellan DNA-strängar (alfabetet är {A,C,G,T} Exempel ABCDEFG och XZACKDFWGH har ACDFG som en längsta gemensam dkvens Ett dåligt sätt att närma sig LCS-problemet En brute-forcelösning Analys Enumerera alla dkvenser av X Testa vilka som också är dkvenser av Y Välj den längsta Om X har längd n innehåller den n dkvenser Det här en algoritm som kräver exponentiell tid! 1.0 1.1 5
Dynamisk programmering och LCS-problemet Låt L[i, j] vara längden av den längsta gemensamma dkvensen av X[0...i] och Y [0... j] Tillåt 1 som index, så att L[ 1,k] = 0 och L[k, 1] = 0 för att indikera att null-delen av X eller Y inte alls matchar den andra strängen Då kan vi definiera L[i, j] i det allmänna fallet som följer: Om X[i] = Y [ j] är L[i, j] = L[i 1, j 1] + 1 (vi kan lägga till den här träffen Om X[i] Y [ j] är L[i, j] = max{l[i 1, j],l[i, j 1]} (vi har ingen träff här Fall 1: Fall : 1. En algoritm för LCS function LCS(X,Y, X = n, Y = m for i = 1 to n-1 do L[i, 1] = 0 for j = 0 to m-1 do L[ 1, j] = 0 for i = 0 to n 1 do for j = 0 to m 1 do if X[i] = Y [ j] then L[i, j] = L[i 1, j 1] + 1 L[i, j] = max{l[i 1, j],l[i, j 1]} return array L 1.3 Visualisering av LCS-algoritmen 6
m n 1.4 Analys av LCS-algoritmen Vi har två nästlade loopar Den yttre itererar n ggr Den inre itererar m ggr I varje iteration av innerloopen utförs en konstant mängd arbete Alltså är den totala exekveringstiden O(nm Svaret finns i L[n,m] (och dkvensen kan återskapas utifrån tabellen L 1.5 4 Giriga algoritmer Giriga algoritmer Algoritmer som löser en bit av problemet i taget. I varje steg görs det som ger bäst utdelning/kostar minst. Den giriga metoden är ett allmänt paradigm för algoritmdesign som bygger på följande: konfigurationer: olika val, samlingar eller värden att hitta målfunktion: konfigurationer tilldelas en poäng som vi vill maximera eller minimera Det fungerar bäst applicerat på problem med greedy-choice-egenskapen: en globalt optimal lösning kan alltid hittas genom en serie lokala förbättringar utgående från en begynn-konfiguration För många problem ger giriga algoritmer inte optimala lösningar utan kanske hyfsade approximativa lösningar. 1.6 Textkomprimering Givet en sträng X, koda X som en kortare sträng Y Sparar minne/bandbredd Ett bra sätt att göra det på: Huffmankodning Beräkna frekvensen f (c för varje tecken c Använd korta koder för tecken med hög frekvens Inget kodord är prefixet av ett annat kodord Använd ett optimalt kodningsträd för att bestämma kodorden 1.7 7
Exempel på kodningsträd En kod avbildar varje tecken i ett alfabet på ett binärt kodord En prefixkod är en binär kod sådan att inget kodord är ett prefix av ett annat kodord En kodningsträd representerar en prefixkod Varje extern nod lagrar ett tecken Kodordet för ett tecken ges av stigen från roten till den externa noden som lagrar tecknet (0 för ett vänsterbarn och 1 för ett högerbarn 00 010 011 10 11 a b c d e a d e b c 1.8 Optimering av kodningsträd Givet en textsträng X vill vi hitta en prefixkod för tecknen i X som ger en liten kodning av X Vanliga tecken borde få korta kodord Ovanliga tecken borde få långa kodord Exempel: X = abrakadabra T 1 kodar X med 9 bitar T kodar X med 4 bitar a b k d r 5 1 1 T 1 T k d b a b r a r k d 1.9 Huffmans algoritm Givet en sträng X konstruerar Huffmans algoritm en prefixkod som minimerar storleken på kodningen av X Algoritmen går i tid O(n + d logd, där n är storleken på X och d är antalet distinkta tecken i X En heapbaserad priokö används som extra datastruktur function HUFFMANENCODING(X, X = n C DISTINCTCHARACTERS(X COMPUTEFREQUENCIES(C, X Q ny tom heap for all c C do T nytt ennodsträd som lagrar c Q.INSERT(GETFREQUENCY(C, 1 while Q.SIZE( > 1 do f 1 Q.MIN( T 1 Q.REMOVEMIN( f Q.MIN( T Q.REMOVEMIN( T JOIN(T 1,T Q.INSERT( f 1 + f,t return Q.REMOVEMIN( 1.30 8
Ett till exempel 1.31 9