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