2D1354 HT06 Övning 4 Inpassning av DNA-sekvenser 1. Inom molekylärbiologin arbetar man sedan ett antal år på att kartlägga och analysera DNA från människan och en del djur. DNA-molekylen består, något förenklat, av en lång följd av nukleotider, där det finns fyra val för varje nukleotid: Adenin, Cytosin, Guanin och Tymin: ACGATCGATCGTACGTAGCTTGCTGTCTAGCTTAGC... Vid laboratorieexperiment bryts DNA-molekylen ner i kortare delar, som är lättare att analysera än hela molekylen som består av miljontals nukleotider. Ett vanligt problem är att avgöra hur pass nära besläktade två DNAsekvenser är; i den här uppgiften ska vi studera inpassningsproblemet, som går ut på att bestämma ett mått på släktskapen. Väsentligen vill man placera sekvenserna ovanpå varandra så att så många nukleotider som möjligt stämmer överens. Under evolutionens gång har vissa mutationer skett. Ett A kan ha blivit ett C etc. Dessutom händer det att nukleotider eller nukleotidsekvenser faller bort ur DNA-kedjan. Detta brukar man man låta svara mot att blanka positioner (betecknas -) kan skjutas in i sekvenserna. Alla dessa händelser är dock relativt ovanliga. För att hitta den korrekta inpassningen straffar man därför dessa händelser. Indata: Två nukleotidsekvenser a = a1 a2 . . . am och b = b1 b2 . . . bn . Utdata: Två sekvenser c och d av samma längd k där varje element i sekvensen är A, C, G, T eller -. c ska kunna fås ur a genom insättning av noll eller flera -. Samma relation gäller mellan d och b. Paret (c, d) ska maximera släktskapsmåttet k s(ci , di ) S(c, d) = i=1 där straffmatrisen A C G A 0 -2 -4 C -2 0 -4 G -4 -4 0 T -4 -4 -2 - -3 -3 -3 s har utseendet T -4 -3 -4 -3 -2 -3 0 -3 -3 -8 Exempel: Den bästa inpassningen av sekvenserna ACGTACGATCGATACG GACGACGCTCATACGT är -ACGTACGATCGATACGGACG-ACGCTC-ATACGT 1 med släktskapsmåttet −14. Uppgiften är att konstruera och analysera en effektiv algoritm som löser inpassningsproblemet med hjälp av dynamisk programmering. Talföljder 2. Givet är två följder av positiva heltal a1 , a2 , . . . , an och b1 , b2 , . . . , bn där alla tal är mindre än n2 samt ett positivt heltal B där B ≤ n3 . Problemet n är att avgöra om det finns någon talföljd c1 , c2 , . . . , cn så att i=1 ci = B och ci = ai eller ci = bi för 1 ≤ i ≤ n. Beskriv och analysera en algoritm som löser detta problem med hjälp av dynamisk programmering. Tala också om hur man ska utvidga algoritmen så den också talar om hur lösningen ser ut, alltså om ci = ai eller ci = bi för 1 ≤ i ≤ n. TSP 3. Handelsresandeproblemet (TSP) lyder som följer: Givet n städer där avståndet mellan stad i och stad j är givet som di,j . Problemet är att hitta en tur som börjar i stad 1, passerar varje annan stad exakt en gång och återvänder till stad 1, så att den totala längden av turen är minimal. Den triviala algoritmen för detta problem är att pröva alla (n − 1)! möjliga turer och räkna ut längden av varje. Uppgiften är att konstruera en algoritm som löser problemet i tid O(n2 2n ). Tips: Lös följande delproblem: Givet en delmängd S av städer och en speciell slutstad i, hitta den kortaste stigen som börjar i stad 1, passerar alla städer i S och slutar i stad i. Analysator för kontextfri grammatik 4. En kontextfri grammatik brukar användas för att beskriva syntax för bland annat programspråk. En kontextfri grammatik i Chomskynormalform beskrivs av • en mängd slutsymboler T (som brukar skrivas med små bokstäver), • en mängd ickeslutsymboler N (som brukar skrivas med stora bokstäver), • startsymbolen S (en av ickeslutsymbolerna i mängden N ), • en mängd omskrivningsregler som antingen är på formen A → BC eller A → a, där A, B, C ∈ N och a ∈ T . Om A ∈ N så definieras L(A) genom L(A) = {bc : b ∈ L(B) och c ∈ L(C) där A → BC} ∪ {a : A → a}. Språket som genereras av grammatiken definieras nu som L(S), vilket alltså är alla strängar av slutsymboler som kan bildas med omskrivningskedjor som börjar med startsymbolen S. Exempel: Betrakta grammatiken med T = {a, b}, N = {S, A, B, R}, startsymbolen S och reglerna S → AR, S → AB, A → a, B → b, R → SB. 2 Vi kan se att strängen aabb tillhör språket som genereras av grammatiken med hjälp av följande kedja av omskrivningar: S → AR → aR → aSB → aSb → aABb → aaBb → aabb. I själva verket kan man visa att det språk som genereras av grammatiken är precis alla strängar som består av k stycken a följt av k stycken b där k är ett positivt heltal. Din uppgift är att konstruera och analysera en effektiv algoritm som avgör ifall en sträng tillhör det språk som genereras av en grammatik. Indata är alltså en kontextfri grammatik på Chomskynormalform samt en sträng av slutsymboler. Utdata är sant eller falskt beroende på om strängen kunde genereras av grammatiken eller inte. Ange tidskomplexiteten för din algoritm uttryckt i antalet regler m i grammatiken och längden n av strängen. Not: vissa typer av kontextfria grammatiker kan analyseras i linjär tid i n med speciella algoritmer. Dessa algoritmer och språk som kan beskrivas av sådana grammatiker är mycket viktiga och behandlas i kursen Artificiella språk och syntaxanalys som går i period 3. Lösningar 1. En lämplig optimalvärdesfunktion är följande: Låt M (i, j) vara det maximala släktskapsmåttet vid inpassning av sekvenserna a1 . . . ai och b1 . . . bj . Den bästa inpassningen av hela sekvenserna a och b svarar då mot M (m, n). Med definitionen av M (i, j) gäller följande rekursionsekvation om i ≥ 1 och j ≥ 1: M (i − 1, j − 1) + s(ai , bj ) M (i, j) = max M (i − 1, j) + s(ai , −) M (i, j − 1) + s(−, bj ) Några basfall dyker också upp: M (0, 0) = 0 M (i, 0) = −3i M (0, j) = −3j Baserat på rekursionsekvationen ovan kan hela tabellen med M (i, j)-värden beräknas på O(mn) tid eftersom varje värde bara beror på tre andra värden. En optimal inpassning (det kan finnas flera) kan hittas på följande sätt: Starta på värdet M (m, n) och avgör ur värdena M (m−1, n−1), M (m, n− 1) och M (m−1, n) vilka de sista symbolerna i c och d är. Samma förfarande kan tillämpas tills hela sekvenserna bestämts. Det är alltså enklast att bestämma sekvenserna bakifrån. 3 Tidskomplexiteten hos den bifogade algoritmen är O(mn) eftersom ett konstant arbete utförs i den innersta slingan. (Efterarbetet, d.v.s. bestämningen av sekvenserna från tabellen, tar bara tid O(m + n).) SequenceAlignment(a, b) (1) M (0, 0) ← 0 (2) for i = 0 to m (3) M (i, 0) ← is(−, −) (4) for j = 0 to n (5) M (0, j) ← js(−, −) (6) for i = 1 to m (7) for j = 1 to n (8) M (i, j) ← max M (i − 1, j − 1) + s(ai ,bj ), M (i − 1, j) + s(ai , −), M (i, j − 1) + s(−, bj ) (9) i←m (10) j←n (11) crev ← {} (12) drev ← {} (13) while i = 0 or j = 0 (14) if i = 0 and M (i, j) = M (i − 1, j) + s(ai , −) (15) crev ← crev + ai (16) drev ← drev + − (17) i←i−1 (18) else if j = 0 and M (i, j) = M (i, j − 1) + s(−, bj ) (19) crev ← crev + − (20) drev ← drev + bj (21) j ←j−1 (22) else (23) crev ← crev + ai (24) drev ← drev + bj (25) i←i−1 (26) j ←j−1 (27) c ← reverse(crev ) (28) d ← reverse(drev ) 2. Vi skapar en boolesk n × B-matris M som från början är fylld med nollor. En etta i M [k, s] ska betyda att det finns något val av c1 , . . . , ck så att k i=1 ci = s. Rekursionsekvationen för M [k, s] blir: 1 om k = 1 och (s = a1 eller s = b1 ), 1 om k > 1 och (M [k − 1, s − ak ] = 1 eller M [k − 1, s − bk ] = 1), M [k, s] = 0 annars. Beräkningen börjar med att M [1, a1 ] och M [1, b1 ] sätts till 1. Därefter sätter man ettor i M [2, a1 +a2 ], M [2, a1 +b2 ], M [2, b1 +a2 ] och M [2, b1 +b2 ] i den andra raden av matrisen, sedan sätter man ettor i den tredje raden och så vidare. Om det hamnar en etta i M [n, B] så är svaret på problemet ja. Algoritmen kan se ut så här i C: 4 int ExistsC(int n, int a[], int b[], { char M[n + 1][B + 1]; /* Dynamiska int i, j; for (i = 1; i <= n; i++) for (j = 1; j <= B; j++) M[i][j] M[1][a[1]] = M[1][b[1]] = 1; for (i = 2; i <= n; i++) for (j = 1; j <= B; j++) { if (j - a[i] > 0 && M[i - 1][j if (j - b[i] > 0 && M[i - 1][j } return M[n][B]; } int B) matriser är ett tillägg i gcc */ = 0; - a[i]]) M[i][j] = 1; - b[i]]) M[i][j] = 1; Satserna inuti den dubbla forslingan utförs (n − 1)B gånger och i dom satserna utförs högst 4 jämförelser och 2 tilldelningar, dvs ett konstant antal. Hela algoritmen går alltså i O(n2 ) + O((n − 1)B) = O(n2 + nB) ⊆ O(n4 ). Genom att gå bakifrån (från den n-te raden och uppåt) och undersöka i vilka positioner det finns ettor kan man lista ut en lösning. Ettan i M [n, B] måste ha satts dit på grund av att det finns en etta i antingen M [n − 1, B − an ] eller M [n − 1, B − bn ]. Kom ihåg vilken av dessa det är. Om det står en etta i båda positionerna är det bara att välja en av dom (eftersom bara en enda lösning efterfrågas). Fortsätt på samma sätt från den valda positionen ända tills rad 1 har nåtts. Implementationen i C ser ut på följande sätt. void WriteC(int n, int a[], int b[], int B) { int c[n + 1], pos; /* samma satser som i proceduren ExistsC ovan ska in här*/ pos = B; for (i = n; i > 1; i++) { if (pos - a[i] > 0 && M[i - 1][pos - a[i]]) c[i] = a[i]; else c[i] = b[i]; pos -= c[i]; } c[1] = pos; printf("Lösningen är %d", c[1]); for (i = 2; i <= n; i++) printf(" + %d", c[i]); } 3. As often, the solution is rather easy to explain in words, the pseudo-code becomes somewhat less transparent and the actual implementation is quite unreadable. The idea behind this solution is best explained by an example. Suppose {2,3,4} we have a graph with vertices V = {1, 2, 3, 4, 5}. Let l5 be the length of the shortest path starting in 1, visiting 2, 3, 4, with 5 as endpoint, {2,3,5} and let l4 be the shortest path starting in 1, visiting 2, 3, 5, with {2,4,5} {3,4,5} and l2 are the shortest paths endpoint in 4. In the same way, l3 5 {2,3,4} {2,3,5} with endpoints in 3 and 2 respectively. Now, if we know l5 , l4 , {2,4,5} {3,4,5} and l2 we can easily solve the TSP-problem; the length of the l3 shortest cycle on G is simply {2,3,4} min(l5 {2,3,5} + d5,1 , l4 {2,4,5} + d3,1 , l2 {2,3,4} {2,3,5} + d4,1 , l3 {3,4,5} + d2,1 ). {2,4,5} (1) {3,4,5} , l4 , l3 and l2 , What remains is to find the values of l5 {2,3,4} as the minimum but this can be solved in the same way: we find l5 {2,3} {2,4} {3,4} {2,3} + d4,5 , l3 + d3,5 and l2 + d2,5 , where l4 is the shortest of l4 {2,4} path starting in 1, visiting 2 and 3, with 4 as endpoint. Likewise for l3 {3,4} and l2 . Hence, in order to find the shortest cycle on G we need to find the shortest path on every subset of V , each with a fixed endpoint. Let lvS be the shortest path from 1 to v passing through all the vertices in the subset S of V . Then the shortest tour has length lv{2,...,n}−{v} + dv,1 min v∈{2,...,n} where lf∅ = d1,f for all f ∈ {2, . . . , n} and lfS = min lvS−{v} + dv,f v∈S for all S = ∅. In the algorithm below the actual path is stored in pathSv . Since the shortest path from the vertex 1 to a vertex v is just d1,v , we initialize the variables by for v ← 2 to n do lv∅ ← d1,v path∅v ← 1, v Now that we have taken care of all paths of length 1, we begin the main algorithm by investigating the paths of length 2. Remember that we assign an index to each subset. The paths of length 1 in the initialization above corresponds to the n − 1 first subsets, hence we should start the main algorithm with index n. for length ← 2 to n − 1 do for all subsets S ⊆ {2, 3, . . . , n} such that |S| = length do for all f ∈ S do /∗ Compute shortest path for each endpoint f in S ∗/ T ← S − {f } min ← ∞ for all v ∈ T do /∗ Which endpoint v = f gives shortest path? ∗/ T −{v} + dv,f tmp ← lv if tmp < min then min ← tmp 6 endpoint ← v lfT ← min T −{endpoint} , f) pathTf ← append(pathendpoint Finally, using (1), we turn the shortest paths on G into the shortest cycle that visits all vertices. S ← {2, 3, . . . , n} min ← ∞ for all v ∈ S do S−{v} + dv,1 tmp ← lv if tmp < min then min ← tmp endpoint ← v S−{endpoint} , 1) pathS1 ← append(pathendpoint S return (path1 , min) There are 2n subsets of V and for each subset there can be at most n endpoints. Hence, we have to compute the shortest path for at most n2n instances. This requires at most n comparisons so that the total amount of work is bounded by O(n2 2n ). This is also reflected in the pseudo-code where we perform two for-loops of at most n steps for each subset of V which gives a running time of O(n2 2n ). This is the best known time complexity for the general Travelling salesperson problem. However, if the cities are given as points in R2 , we have what is known as the Euclidean TSP. This problem can be solved faster (the best known algorithm for this special case is by Viggo Kann and runs √ in time 2O( n log n) ). 4. Vi använder dynamisk programmering ungefär som i problemet där man letar efter optimal matriskedjemultiplikationsordning. Här ska vi istället bestämma i vilken ordning och på vilken delsträng reglerna ska tillämpas. Indata är en uppsättning regler R och en vektor w[1..n] som alltså indexeras från 1 till n. Låt oss bygga upp en matris M[1..n,1..n] där elementet M[i,j] anger dom ickeslutsymboler från vilka man med hjälp av kedjor av omskrivningar kan härleda delsträngen w[i..j]. Rekursiv definition av M[i,j]: {X : (X → w[i]) ∈ R} om i = j M[i, j] = {X : (X → AB) ∈ R ∧ ∃k : A ∈ M[i, k − 1] ∧ B ∈ M[k, j]} om i < j Eftersom varje position i matrisen är en mängd av ickeslutsymboler så måste vi välja en lämplig datastruktur också för detta. Låt oss representera en mängd av ickeslutsymboler som en bitvektor som indexeras med ickeslutsymboler. 1 betyder att symbolen är med i mängden och 0 att den inte är med i mängden. Exempel: Om M[i,j][B]= 1 så är ickeslutsymbolen B med i mängden M[i,j], vilket betyder att det finns en kedja av omskrivningsregler från B till delsträngen w[i..j]. 7 Algoritm som beräknar matrisen M[i,j] och returnerar sant ifall strängen tillhör språket som genereras av grammatiken: for i←1 to n do M[i,i]←0; /* alla bitar nollställs */ för varje regel X →w[i] do M[i,i][X]←1; for len←2 to n do for i←1 to n-len+1 do j←i+len-1; M[i,j]←0; for k←i+1 to j do för varje regel X → AB do if M[i,k-1][A]=1 and M[k,j][B]=1 then M[i,j][X]←1; return M[1,n][S]= 1; Tid: O(n3 m). Minne: O(n2 m) (eftersom m är en övre gräns för antalet ickeslutsymboler). 8