动态规划的两个条件是最优子结构和子问题重叠。
最优子结构:一个问题的最优解包含其子问题的最优解。换句话说,用子问题的最优解可以构造原问题的最优解。例如钢条切割问题。
子问题重叠:子问题空间必须足够小,问题的递归算法会反复地求解相同的子问题。如背包问题。
动态规划求解时的方法:带备忘的自顶向下法、自底向上法。
我理解的带备忘的自顶向下法就是比如需要的是子问题的最小值,然后在存储结果的数组中先赋成无穷大,然后在从上到下递归时,若没有这个值,就采取递归来计算它(通常需要遍历所有的情况取最小值);若这个值已经在数组中保存,就直接利用它。“备忘”也就是这个存储之前计算结果的数组。
个人认为自底向上法更好理解一些,即从最小的子问题出发,一步一步将所有的子问题都求解,存在数组中,最后利用这些已经求解的子问题计算出原问题的最优解。
步骤:
1.刻画一个最优解的结构特征。通常也就是将父问题进行划分,看是否满足最优子结构。
2.递归定义最优解的值。一般是将递归式写出来,将m[i,j]用m[i-1,j]或另外的参数之类的表示出来,可能还要分i==j和i < j的条件。
3.用递归式计算最优解的值。
4.用上面的方法构造一个最优解。
钢条切割问题:给定一段长度为n英寸的钢条和一个价格表pi(i=1,2,……,n),求钢条切割方案,使得销售收益rn最大。注意,如果长度为n英寸的钢条的价格pn足够大,最优解可能就是完全不需要切割。
首先,我们需要确定这个问题满足最优子结构。让我们来这样想:将钢条从左边切割下一段长度为i的一段,剩下右边长度为n-i的一段可以递归来进行切割。也就是对左边进行长度为0-n的遍历,选取最大的,即为最优解。
递归式如下:
rn = max{pi + r(n-i)}(1<=i<=n)
算法:
1.自底向上法(迭代) 用r[0..n]保存收最优收益,用s[0..n]来保存第一段切割长度
r[0] = 0
for j = 1 to n
q = -∞
for i = 1 to j
//q = max{q, pi+r[j-i]}
if q < pi+r[j-i]
q = pi+r[j-i]
s[j]=i //用来保存第一段切割长度
r[j] = q
2.带备忘的自顶向下法 (递归)
r[0] = 0
for i = 1 to n
r[i] = -∞
f(p,n,r)
f(p,n,r)
if r[n] >= 0
return r[n]
if n == 0
q = 0
else
q = -∞
for i = 1 to n
if q < pi+f(p,n-i,r)
q = pi+f(p,n-i,r)
s[j] = i
r[n] = q
return q
具体C代码如下:
#include<stdio.h>
#define M 1000
int p[11] = {0,1,5,8,9,10,17,17,20,24,30};
int r[M] = {0};
int s[M] = {0};
int BOTTOM_UP_CUT_ROD(int n)//自底向上法
{
int i,j,q = 0;
for(i = 0; i <= n; i++){
r[i] = -1;//负无穷大
s[i] = 0;
}
r[0] = 0;
for(j = 1; j <= n; j++){
q = -1;//负无穷大
for(i = 1; i <= j; i++){
if(q < p[i]+r[j-i]){
q = p[i]+r[j-i];
s[j] = i;
}
}
r[j] = q;
}
}
int MEMOIZED_CUT_ROD(int n)//带备忘的自顶向下法
{
int i,j;
for(i = 0; i <= n; i++){
r[i] = -1;//负无穷大
s[i] = 0;
}
return MEMOIZED_CUT_ROD_AUX(n);
}
int MEMOIZED_CUT_ROD_AUX(int n)
{
int i,q;
if(r[n] >= 0)
return r[n];
if(n == 0)
q = 0;
else{
q = -1;//负无穷大
for(i = 1; i <= n; i++){
if(q < p[i]+MEMOIZED_CUT_ROD_AUX(n-i)){
q = p[i] + MEMOIZED_CUT_ROD_AUX(n-i);
s[n] = i;
}
}
}
r[n] = q;
return q;
}
void print(int n)
{
printf("对应的切割方案为:");
while(n > 0){
printf("%d ",s[n]);
n = n - s[n];
}
printf("\n");
}
int main()
{
int n,k;
printf("请输入钢条长度:");
scanf("%d",&n);
BOTTOM_UP_CUT_ROD(10);
printf("自底向上:%d\n",r[n]);
print(n);
printf("自顶向下:%d\n",MEMOIZED_CUT_ROD(n));
print(n);
}