动态规划并不是一种算法,而是一种解决问题的思路。典型的动态规划问题,如最长公共子序列(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=="↖"
}
else
if(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); } }