最大子序列、最長公共子串、最長公共子序列
最大子序列
最大子序列是要找出由陣列成的一維陣列中和最大的連續子序列。比如{5,-3,4,2}的最大子序列就是 {5,-3,4,2},它的和是8,達到最大;而 {5,-6,4,2}的最大子序列是{4,2},它的和是6。你已經看出來了,找最大子序列的方法很簡單,只要前i項的和還沒有小於0那麼子序列就一直向後擴充套件,否則丟棄之前的子序列開始新的子序列,同時我們要記下各個子序列的和,最後找到和最大的子序列。
程式碼如下:
最長公共子串(LCS)找兩個字串的最長公共子串,這個子串要求在原字串中是連續的。其實這又是一個序貫決策問題,可以用動態規劃來求解。我們採用一個二維矩陣來記錄中間的結果。這個二維矩陣怎麼構造呢?直接舉個例子吧:"bab"和"caba"(當然我們現在一眼就可以看出來最長公共子串是"ba"或"ab")
b a b
c 0 0 0
a 0 1 0
b 1 0 1
a 0 1 0
我們看矩陣的斜對角線最長的那個就能找出最長公共子串。
不過在二維矩陣上找最長的由1組成的斜對角線也是件麻煩費時的事,下面改進:當要在矩陣是填1時讓它等於其左上角元素加1。
b a b
c 0 0 0
a 0 1 0
b 1 0 2
a 0 2 0
這樣矩陣中的最大元素就是 最長公共子串的長度。
在構造這個二維矩陣的過程中由於得出矩陣的某一行後其上一行就沒用了,所以實際上在程式中可以用一維陣列來代替這個矩陣。
最長公共子序列
最長公共子序列與最長公共子串的區別在於最長公共子序列不要求在原字串中是連續的,比如ADE和ABCDE的最長公共子序列是ADE。
我們用動態規劃的方法來思考這個問題如是求解。首先要找到狀態轉移方程:
等號約定,C1是S1的最右側字元,C2是S2的最右側字元,S1‘是從S1中去除C1的部分,S2'是從S2中去除C2的部分。
LCS(S1,S2)等於下列3項的最大者:
(1)LCS(S1,S2’)
(2)LCS(S1’,S2)
(3)LCS(S1’,S2’)--如果C1不等於C2; LCS(S1',S2')+C1--如果C1等於C2;
邊界終止條件:如果S1和S2都是空串,則結果也是空串。
下面我們同樣要構建一個矩陣來儲存動態規劃過程中子問題的解。這個矩陣中的每個數字代表了該行和該列之前的LCS的長度。與上面剛剛分析出的狀態轉移議程相對應,矩陣中每個格子裡的數字應該這麼填,它等於以下3項的最大值:
(1)上面一個格子裡的數字
(2)左邊一個格子裡的數字
(3)左上角那個格子裡的數字(如果 C1不等於C2); 左上角那個格子裡的數字+1( 如果C1等於C2)
舉個例子:
G C T A
0 0 0 0 0
G 0 1 1 1 1
B 0 1 1 1 1
T 0 1 1 2 2
A 0 1 1 2 3
填寫最後一個數字時,它應該是下面三個的最大者:
(1)上邊的數字2
(2)左邊的數字2
(3)左上角的數字2+1=3,因為此時C1==C2
所以最終結果是3。
在填寫過程中我們還是記錄下當前單元格的數字來自於哪個單元格,以方便最後我們回溯找出最長公共子串。有時候左上、左、上三者中有多個同時達到最大,那麼任取其中之一,但是在整個過程中你必須遵循固定的優先標準。在我的程式碼中優先級別是左上>左>上。
下圖給出了回溯法找出LCS的過程:
#include<iostream>
#include<string>
#include<stack>
#include<utility>
#define LEFTUP 0
#define LEFT 1
#define UP 2
using namespace std;
int Max(int a,int b,int c,int *max){ //找最大者時a的優先級別最高,c的最低.最大值儲存在*max中
int res=0; //res記錄來自於哪個單元格
*max=a;
if(b>*max){
*max=b;
res=1;
}
if(c>*max){
*max=c;
res=2;
}
return res;
}
//呼叫此函式時請注意把較長的字串賦給str1,這主要是為了在回溯最長子序列時節省時間。如果沒有把較長的字串賦給str1不影響程式的正確執行。
string LCS(const string &str1,const string &str2){
int xlen=str1.size(); //橫向長度
int ylen=str2.size(); //縱向長度
if(xlen==0||ylen==0) //str1和str2中只要有一個為空,則返回空
return "";
//pair<int,int> arr[ylen+1][xlen+1];
pair<int,int> **arr=new pair<int,int>* [ylen+1]; //構造pair二維陣列,first記錄資料,second記錄來源
for(int i=0;i<ylen+1;i++)
arr[i]=new pair<int,int>[xlen+1];
for(int i=0;i<=xlen;i++) //首行清0
arr[0][i].first=0;
for(int j=0;j<=ylen;j++) //首列清0
arr[j][0].first=0;
for(int i=1;i<=ylen;i++){
char s=str2.at(i-1);
for(int j=1;j<=xlen;j++){
int leftup=arr[i-1][j-1].first;
int left=arr[i][j-1].first;
int up=arr[i-1][j].first;
if(str1.at(j-1)==s) //C1==C2
leftup++;
int max;
arr[i][j].second=Max(leftup,left,up,&arr[i][j].first);
// cout<<arr[i][j].first<<arr[i][j].second<<" ";
}
// cout<<endl;
} /*矩陣構造完畢*/
//回溯找出最長公共子序列
stack<int> st;
int i=ylen,j=xlen;
while(!(i==0&&j==0)){
if(arr[i][j].second==LEFTUP){
if(arr[i][j].first==arr[i-1][j-1].first+1)
st.push(i);
--i;
--j;
}
else if(arr[i][j].second==LEFT){
--j;
}
else if(arr[i][j].second==UP){
--i;
}
}
string res="";
while(!st.empty()){
int index=st.top()-1;
res.append(str2.substr(index,1));
st.pop();
}
return res;
}
int main()
{
string str1="GCCCTAGCG";
string str2="GCGCAATG";
string lcs=LCS(str1,str2);
cout<<lcs<<endl;
return 0;
}