Subsequence - 演算法筆記
文章推薦指數: 80 %
二維陣列length[i][j] ,代表「 s1 前i 個元素」和「 s2 前j 個元素」的LCS 長度。
... 中央橫排,找到最大值位置,分成左上區與右下區,遞迴縮小問題。
LongestCommonSubsequence
LongestCommonSubsequence(LCS)
「最長共同子序列」。
出現於每一個序列、而且是最長的子序列。
可能有許多個。
s1:2579312
s2:35328
LCS(s1,s2)=532
s1:abcdbceea
s2:cabdefga
s3:dcea
LCS(s1,s2,s3)={cea,dea}
求出兩個序列的LCS,P問題。
接下來將介紹各種演算法。
求出一群序列的LCS,NP-hard問題,沒有快速的演算法。
簡單的方式是窮舉法:窮舉s1的所有子序列,檢查s2...sN是否都有該子序列。
時間複雜度O(s1!⋅(s2+...+sN))。
LongestCommonSubsequence:DynamicProgramming
演算法(Needleman–WunschAlgorithm)
削減尾端元素,遞迴縮小問題。
s1和s2的最後一個元素,叫做e1和e2,拆解出來。
s1=sub1+e1
s2=sub2+e2
四種情況:
一、LCS包含e1不含e2:LCS(s1,s2)=LCS(s1,sub2)
二、LCS不含e1包含e2:LCS(s1,s2)=LCS(sub1,s2)
(e1毫無用處,想找LCS只需要找sub1。
)
三、LCS不含e1e2:LCS(s1,s2)=LCS(sub1,sub2)
四、LCS包含e1e2:LCS(s1,s2)=LCS(sub1,sub2)+e1
(LCS的最後一個元素一定是e1,也等於e2。
)
遞迴公式:
LCS(s1,s2)=
{max(LCS(sub1,s2),LCS(s1,sub2),LCS(sub1,sub2))
{whene1≠e2
{LCS(sub1,sub2)+e1whene1=e2
簡化:
LCS(s1,s2)=
{max(LCS(sub1,s2),LCS(s1,sub2))whene1≠e2
{LCS(sub1,sub2)+e1whene1=e2
初始值:
LCS(s1,s2)=Øwhens1=Øors2=Ø
時間複雜度O(NM),空間複雜度O(NM)。
N和M是序列長度。
©2010tkcn.Allrightsreserved.
計算LongestCommonSubsequence的長度
二維陣列length[i][j],代表「s1前i個元素」和「s2前j個元素」的LCS長度。
有個節省記憶體空間的方法。
填表格,只需要上格、左格、左上格。
計算順序,設定為從左往右、再從上往下,如此只需要一條陣列(上排)、以及一個數值(左上格)。
長序列為直向,短序列為橫向,陣列長度最短。
空間複雜度降為O(min(N,M))。
constintN1=7,N2=5;
//為了實作方便,從陣列的第1格開始存入序列。
ints1[N1+1]={0,2,5,7,9,3,1,2};
ints2[N2+1]={0,3,5,3,2,8};
intlength[N1+1][N2+1]; //DP表格
intLCS()
{
//初始值:當s1或s2是空集合,則LCS是空集合。
for(inti=0;i<=N1;i++)length[i][0]=0;
for(intj=0;j<=N2;j++)length[0][j]=0;
//填表格:依照遞迴公式
for(inti=1;i<=N1;i++)
for(intj=1;j<=N2;j++)
if(s1[i]==s2[j])
length[i][j]=length[i-1][j-1]+1;
else
length[i][j]=max(length[i-1][j],
length[i][j-1]);
returnlength[N1][N2];
}
找出一個LongestCommonSubsequence
使用一個二維陣列,記錄每一格的結果是從哪一格而來。
往回追溯,每當發現某一格length[i][j]是由length[i-1][j-1]+1而來,就印出s1[i](也是s2[j])。
有個節省記憶體空間的方法。
只計算LCS長度。
填表格,自頂往中央,自底往中央,相遇於中央,將答案相加。
中央橫排,找到最大值位置,分成左上區與右下區,遞迴縮小問題。
遞迴下去的區域,只佔半個表格。
整個遞迴過程,1+(1/2)+(1/4)+(1/8)+...≤2,只佔兩個表格。
時間複雜度升為兩倍。
空間複雜度降為O(N+M)。
constintN1=7,N2=5;
ints1[N1+1]={0,2,5,7,9,3,1,2};
ints2[N2+1]={0,3,5,3,2,8};
intlength[N1+1][N2+1]; //DP表格
intprev[N1+1][N2+1]; //記錄每一格的的結果是從哪一格而來
intlcs[min(N1,N2)];
voidLCS()
{
for(inti=0;i<=N1;i++)length[i][0]=0;
for(intj=0;j<=N2;j++)length[0][j]=0;
for(inti=1;i<=N1;i++)
for(intj=1;j<=N2;j++)
if(s1[i]==s2[j])
{
length[i][j]=length[i-1][j-1]+1;
prev[i][j]=0; //左上格
}
else
{
if(length[i-1][j]
往回追溯的時候,將這些情形通通追溯一遍,求出所有LCS。
LCS數量最多O(2ᴺ)個。
UVa11153110066101921040510723
演算法(Hirschberg'sAlgorithm)
表格分割成左上區、右下區,遞迴縮小問題。
前面已介紹。
時間複雜度O(NM)。
空間複雜度O(N+M)。
演算法(Four-RussiansSpeedup)
表格切割成小方塊,小方塊直接查表。
小方塊邊長logN/4。
時間複雜度O(N²/logN)。
不無小補。
LongestCommonSubsequence:Hunt–SzymanskiAlgorithm
LCS問題化作二維LIS問題
兩序列找出相同元素,表示成位置數對。
兩序列的LCS=位置數對的2DLIS。
(1)LCSproblem:
index:0123456789
--------------------------
s1:abacd
s2:dbaabca
(2)matchedpositions:
dbaabca
+---------------aaabb
a|***(0,2)(0,3)(0,6)(1,1)(1,4)
b|**
a|***aaacd
c|*(2,2)(2,3)(2,6)(3,5)(4,0)
d|*
(3-1){LCS==2DLIS}example:
dbaabca
+---------------
a|x**LCS:aba
b|*x2DLIS:(0,2)(1,4)(2,6)
a|**x
c|*數對呈嚴格遞增,在表格中則是往右下走。
d|*
(3-2){LCS==2DLIS}anotherexample:
dbaabca
+---------------
a|***LCS:bac
b|x*2DLIS:(1,1)(2,2)(3,5)
a|x**
c|x數對呈嚴格遞增,在表格中則是往右下走。
d|*
二維LIS問題化作一維LIS問題
排序所有數對:第一維由小到大排序;當第一維平手,則第二維由大到小排序。
排序之後,原本數對們的2DLIS=第二維的1DLIS。
(1)sortallpairs:
aaabbaaacd
(0,6)(0,3)(0,2)(1,4)(1,1)(2,6)(2,3)(2,2)(3,5)(4,0)
increasingin1stcomponents.
thendecreasingin2ndcomponents.
(2)2ndcomponents:
6324163250
(3){2DLIS==1DLIS}example:
6324163250
***
LCS:aba
2DLIS:(0,2)(1,4)(2,6)
1DLIS:246
(4)allinone:
abacds1
->{a(6,3,2)b(4,1)a(6,3,2)c(5)d(0)}sortedmatchedpositions
->{6324163250}2ndcomponent
->246LIS
演算法
LCS問題,化作2DLIS問題,再化作1DLIS問題,最後套用Robinson–Schensted–KnuthAlgorithm。
排序所有數對,使用CountingSort。
掃描一遍s2,把每個字元的位置紀錄下來。
較短的序列當作s1,時間複雜度是O(Klog(min(N,M))+R)。
K是數對數目,N和M是序列長度,R是數字範圍。
K至多是NM,最差情況下比先前的演算法還慢,平均情況下比先前的演算法快上許多。
R源自CountingSort。
計算LongestCommonSubsequence的長度
intLCS(vector&s1,vector&s2)
{
// if(s1.size()==0||s2.size()==0)return0;
/*CountingSort*/
vectorp[128]; //假設字元範圍為0~127
for(inti=0;i
A和B的LCS,就是A的LIS。
LCS轉LIS:將序列A和B當中的相同字母配對通通找出來,表示成位置數對,以特殊規則排序,然後找到LIS,就是A和B的LCS。
LongestCommonIncreasingSubsequence
LongestCommonIncreasingSubsequence(LCIS)
嚴格遞增的LCS。
LCS的DP演算法稍作修改,隨時找到最有潛力的LCIS。
時間複雜度仍是O(NM)。
421423142142314214231
+---------------+---------------+---------------
1|**1|x*1|x*
2|**2|*x2|*x
4|**4|**4|**
3|*3|x3|*
4|**4|**4|**
allmatchedpairsLCISbest-candidateLCIS
whenvisitpair(3,3)
constintN1=5,N2=7;
ints1[N1]={1,2,4,3,4};
ints2[N2]={4,2,1,4,2,3,1};
intlength[N1]; //精簡成一維陣列
intprev[N1][N2];
intlcis[min(N1,N2)];
voidLCIS()
{
memset(prev,-1,sizeof(prev)); //初始化為-1
for(inti=0;il)
l=length[j],p=j;
}
}
//計算LCIS長度、尾端位置
intl=0,p=-1;
for(inti=0;il)
l=length[i],p=i;
//找出一個LCIS
for(inti=N1-1,j=p,k=l;k>0;--i)
if(prev[i][j]!=-1)
{
lcis[--k]=s2[j];
j=prev[i][j];
}
cout<
延伸文章資訊
- 1【java】使用遞迴求和二維陣列中的整數? - 程式人生
【java】使用遞迴求和二維陣列中的整數? ... 2, 3 }, { 3, 2, 1 }, { 1, 2, 3 } }; int sum = rec(tabell, 2, ... 首先建立一個...
- 2請問大神要怎麼把二維陣列丟入副程式裡執行,以這裡為例。
想把迭代轉遞迴但二維陣列傳入副程式格式不知道怎麼用卡住了。這篇只是在試用法。 hsuan1985419 (發問者) 2 年前. #include <stdio.h> #include <stdl...
- 3Dynamic Programming - 演算法筆記
【註: recursion 和recurrence ,中文都翻譯為「遞迴」,然而兩者意義大不相同, ... 空間複雜度分析:總共XY 個問題,所以需要O(XY) 空間,簡單來說就是二維陣列啦!
- 4遞迴函式
遞迴深度淺一些的問題拆解方法,例如在計算陣列data[] 裡n. 個元素data[0]~data[n-1] 的總和時, ... 右圖二維陣列代表一個迷宮路徑, 空格代表可以走的通道, X 代表不.
- 5Subsequence - 演算法筆記
二維陣列length[i][j] ,代表「 s1 前i 個元素」和「 s2 前j 個元素」的LCS 長度。 ... 中央橫排,找到最大值位置,分成左上區與右下區,遞迴縮小問題。