文章目錄
動態規劃並不是一種演算法,而是一種解決問題的思路。典型的動態規劃問題,如最長公用子序列(LCS),最長單調子序列(LIS)等。
動態規劃分為四個步驟:1.判斷問題是否具有最優子結構
這裡以LCS為例,X={x1,x2,...,xi};Y={y1,y2,...,yj}。最長公用子序列Z={z1,z2,...,zk};
①如果xi=yj,那麼zk=xi=yj,且Zk-1是序列Xi-1和Yj-1的LCS;
②如果xi≠yj,那麼zk≠xi;且Zk是序列Xi-1和Yj的LCS;
③如果xi≠yj,那麼zk≠yj;且Zk是序列Xi和Yj-1的LCS;
可以用反證法證明上述子結構的成立。
2.一個遞迴解
設c[i,j]是Xi和Yj的LCS的長度,i=0或j=0時,c[i,j]=0;
c[i,j]=0 (i=0或j=0)
c[i,j]=c[i-1,j-1] (i,j>0;xi=yj)
c[i,j]=max{c[i-1,j],c[i,j-1]} (i,j>0;xi≠yj)
3.計算LCS的長度
c[i,j]為遞迴解,那麼有多少個不同的遞迴解呢?O(m*n)。即要有重疊子問題,而不是每次都要解決新的問題。至於重疊子問題,需從底向上求。每次只需索引之前較小規模的子問題即可。
從底向上,求解c[i,j]。還需維護b[i][j]以簡化最優解的結構。
例如,設所給的兩個序列為X=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>。由演算法LCS_LENGTH和LCS計算出的結果2所示。
| |
j |
|
0 |
|
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
| i |
|
|
yj |
|
B |
|
D |
|
C |
|
A |
|
B |
|
A |
|
| |
|
┌ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
┐ |
| 0 |
xi |
│ |
|
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
|
0 |
│ |
| |
|
│ |
|
|
↑ |
|
↑ |
|
↑ |
|
|
|
|
|
|
│ |
| 1 |
A |
│ |
0 |
|
0 |
|
0 |
|
0 |
|
1 |
← |
1 |
|
1 |
│ |
| |
|
│ |
|
|
|
|
|
|
|
|
↑ |
|
|
|
|
│ |
| 2 |
B |
│ |
0 |
|
1 |
← |
1 |
← |
1 |
|
1 |
|
2 |
← |
2 |
│ |
| |
|
│ |
|
|
↑ |
|
↑ |
|
|
|
|
|
↑ |
|
↑ |
│ |
| 3 |
C |
│ |
0 |
|
1 |
|
1 |
|
2 |
← |
2 |
|
2 |
|
2 |
│ |
| |
|
│ |
|
|
|
|
↑ |
|
↑ |
|
↑ |
|
|
|
|
│ |
| 4 |
B |
│ |
0 |
|
1 |
|
1 |
|
2 |
|
2 |
|
3 |
← |
3 |
│ |
| |
|
│ |
|
|
↑ |
|
|
|
↑ |
|
↑ |
|
↑ |
|
↑ |
│ |
| 5 |
D |
│ |
0 |
|
1 |
|
2 |
|
2 |
|
2 |
|
3 |
|
3 |
│ |
| |
|
│ |
|
|
↑ |
|
↑ |
|
↑ |
|
|
|
↑ |
|
|
│ |
| 6 |
A |
│ |
0 |
|
1 |
|
2 |
|
2 |
|
3 |
|
3 |
|
4 |
│ |
| |
|
│ |
|
|
|
|
↑ |
|
↑ |
|
↑ |
|
|
|
↑ |
│ |
| 7 |
B |
│ |
0 |
|
1 |
|
2 |
|
2 |
|
3 |
|
4 |
|
5 |
│ |
| |
|
└ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
─ |
┘ |
void LCS_LENGTH(char x[],char y[],const int len_x,const int len_y,int c[][ROW],int b[][ROW]){int i,j;for(i=1;i<len_x;i++){for(j=1;j<len_y;j++){if(x[i]==y[j]){c[i][j]=c[i-1][j-1]+1;b[i][j]=3;//3==""}elseif(c[i-1][j]>=c[i][j-1]){c[i][j]=c[i-1][j];b[i][j]=1;//1=="↑"}else{c[i][j]=c[i][j-1];b[i][j]=2;//2=="←"}}//for j}//for i}
4.構造一個LCS
根據表b[i][j]構造LCS,
3=="",表示x[i]在Zk中;
1=="↑",表示x[i]不在Zk中,且向c[i][j]=c[i-1][j];
2=="←",表示x[i]不在Zk中,且向c[i][j]=c[i][j-1];
void PRINT_LCS(int b[][ROW],char x[],int m,int n){if(m==0||n==0)return;if(b[m][n]==3){PRINT_LCS(b,x,m-1,n-1);printf("%c\t",x[m]);}else if(b[m][n]==1){PRINT_LCS(b,x,m-1,n);}else{PRINT_LCS(b,x,m,n-1);}}