最大子序列、最長連續公共子串(連續)、最長公共子序列(動態規劃)
原文連結:http://blog.sina.com.cn/s/blog_54f82cc20100zi4b.html
最大子序列
最大子序列是要找出由陣列成的一維陣列中和最大的連續子序列。比如{5,-3,4,2}的最大子序列就是 {5,-3,4,2},它的和是8,達到最大;而 {5,-6,4,2}的最大子序列是{4,2},它的和是6。你已經看出來了,找最大子序列的方法很簡單,只要前i項的和還沒有小於0那麼子序列就一直向後擴充套件,否則丟棄之前的子序列開始新的子序列,同時我們要記下各個子序列的和,最後找到和最大的子序列。
程式碼如下:
#include<iostream> using
namespace
std;
int
MaxSubSeq(
const
int
*arr,
int
len,
int
*start,
int
*end){
int
max=0;
//記錄目前找到的最大子序列的和
int
sum=0; //記錄當前子序列的和
int
begin=0,finish=0;
//記錄當前子序列的起始下標
*start=begin;*end=finish;
//記錄最長子序列的起始下標
for
(
int
i=0;i<len;i++){
sum+=arr[i];
finish=i;
if
(sum>max){
max=sum;
*end=finish;
*start=begin;
}
if
(sum<=0){
sum=0;
begin=i+1;
}
}
return
max;
}
int
main(){
int
arr[6]={5,-3,-2,12,9,-1};
int
start,end;
int
max=MaxSubSeq(arr,6,&start,&end);
cout<<
"The MaxSubSeq is from position "
<<start<<
"to position "
<<end<<
"."
<<endl;
cout<<
"Sum of MaSubSeq: "
<<max<<endl;
return
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
這樣矩陣中的最大元素就是 最長公共子串的長度。
在構造這個二維矩陣的過程中由於得出矩陣的某一行後其上一行就沒用了,所以實際上在程式中可以用一維陣列來代替這個矩陣。
程式碼如下:
#include<iostream>
#include<cstring>
#include<vector>
using
namespace
std;
//str1為橫向,str2這縱向
const
string LCS(
const
string& str1,
const
string& str2){
int
xlen=str1.size();
//橫向長度
vector<
int
> tmp(xlen);
//儲存矩陣的上一行
vector<
int
> arr(tmp);
//當前行
int
ylen=str2.size();
//縱向長度
int
maxele=0;
//矩陣元素中的最大值
int
pos=0;
//矩陣元素最大值出現在第幾列
for
(
int
i=0;i<ylen;i++){
string s=str2.substr(i,1);
arr.assign(xlen,0);
//陣列清0
for
(
int
j=0;j<xlen;j++){
if
(str1.compare(j,1,s)==0){
if
(j==0)
arr[j]=1;
else
arr[j]=tmp[j-1]+1;
if
(arr[j]>maxele){
maxele=arr[j];
pos=j;
}
}
}
// {
// vector<int>::iterator iter=arr.begin();
// while(iter!=arr.end())
// cout<<*iter++;
// cout<<endl;
// }
tmp.assign(arr.begin(),arr.end());
}
string res=str1.substr(pos-maxele+1,maxele);
return
res;
}
int
main(){
string str1(
"21232523311324"
);
string str2(
"312123223445"
);
string lcs=LCS(str1,str2);
cout<<lcs<<endl;
return
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。
在填寫過程中我們還是記錄下當前單元格的數字來自於哪個單元格,以方便最後我們回溯找出最長公共子串。有時候左上、左、上三者中有多個同時達到最大,那麼任取其中之一,但是在整個過程中你必須遵循固定的優先標準。在我的程式碼中優先級別是左上>左>上。
奉上程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
#include<iostream>
#include<cstring>
#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二維陣列,first記錄資料,second記錄來源
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;
}
|
下面給一個Java版本
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public
static
<E> List<E> longestCommonSubsequence(E[] s1,E[] s2){
int
[][] num=
new
int
[s1.length+
1
][s2.length+
1
];
for
(
int
i=
1
;i<s1.length;i++){
for
(
int
j=
1
;j<s2.length;j++){
if
(s1[i-
1
].equals(s2[j-
1
])){
num[i][j]=
1
+num[i-
1
][j-
1
];
}
else
{
num[i][j]=Math.max(num[i-
1
][j],num[i][j-
1
]);
}
}
}
System.out.println(
"lenght of LCS= "
+num[s1.length][s2.length]);
int
s1position=s1.length,s2position=s2.length;
List<E> result=
new
LinkedList<E>();
while
(s1position!=
0
&& s2position!=
0
){
if
(s1[s1position-
1
].equals(s2[s2position-
1
])){
result.add(s1[s1position-
1
]);
s1position--;
s2position--;
}
else
if
(num[s1position][s2position-
1
]>=num[s1position-
1
][s2position]){
s2position--;
}
else
{
s1position--;
}
}
Collections.reverse(result);
return
result;
}
|
std::endl是一個特殊值,稱為操縱符(manipulator),將它寫入輸出流時具有輸出換行的效果,並重新整理與裝置相關聯的緩衝區(buffer)。通過重新整理緩衝區使用者可立即看到寫入到流中的輸出。
我在除錯以上程式碼時在45行(cout<<endl;)處設定斷點,結果發現“43行(cout<<arr[i][j].first<<arr[i][j].second<<"";) 沒有執行”,這就是因為43行末尾沒有加endl,所以使用者沒有立即看到輸出流中的資料。