动态规划与分支方法相似,都是通过组合子问题的解求解原问题
其步骤分为:
(1):刻画一个最优解的结构特征。
(2):递归地定义最优解的值。
(3):计算最优解的值,通常采取自底向上的方法。
(4):利用计算出的信息构造一个最优解。
## 钢条切割问题 ##
CUT-ROD(int *p,n)
if(n==0)
return 0;
int q=-INF;//作为标记
for(int i=1;i<=n;i++)
{
int q=max(q,p[i]+CUT-ROD(p,n-i));
}
return q;
过程CUT-ROD以价格数组p[1…n]和整数n作为输入,返回长度为n的钢条的最大收益,若N=0,不可能有任何收益,所以返回0,
然后将最大收益初始化为负无穷,下面就是计算结果。。运用递归的思想。
## 带备忘的自顶向下法 ##
此方法按照自然的递归形式编写过程,但过程会保存每个子问题的解。当需要一个子问题的解时,过程首先检查是否已保存过此解。如果是,则直接返回保存的值,从而节省了计算时间,否则按照通常的方式计算这个问题.所以我们称这个递归过程是带备忘的,因为他记住了之前已经计算出的结果。
MEMORIZD-CUT-ROD(int *p,int n)
{
int r[n];
for(int i=0;i<n;i++)
{
r[i]=-INF;
}
return MEMORIZED-CUT-ROD-AUX(p,n,r);
}
MEMORIZED-CUT-ROD-AUX(int *p,int n,int *r)
{
if(r[n]>=0)
{
return r[n];
}
if(n==0)
int q=0;
else
{
int q=-INF;
for(int i=1;i<n;i++)
{
q=max(q,p[i]+MEMORIZED-CUT-ROD-AUX(p,n-i,r));
}
}
r[n]=q;
return q;
}
伪代码解释
## 自底向上版本 ##
BOTTOM-UP-CUT-ROD(int *p,int n)
{
int r[n]//new array
r[0]=0;
for(int j=0;j<n;j++)
{
int q=-INF;
for(int i=1;i<=j;i++)
{
q=max(q,p[i]+r[j-i])
}
r[j]=q;
}
return r[n];
}
## 伪代码解释(自底向上) ##
创建一个新数组r[]来保存子问题的解,将其第一个元素初始化为0;因为长度为0的钢条没有收益。接下来,解释按照升序求解每个规模为J的子问题,方法同上,只是现在直接访问数组元素r[j-i]来获得规模为j-i的子问题的解,而不必进行递归调用,然后将规模为J的子问题存入r[j]。最后返回r[n],即最优解r_n;
## 重构解 ##
前文给出的钢条切割问题的动态规划返回最优解的收益值,但并未返回解本身,我们可以扩展动态规划算法,使之对每个子问题不仅保存最优收益值,还能保存对应的切割方案
于是下面代码的作用是得到最大收益值,还能得到对应的第一段钢条的切割产长度。
EXTENDED-BOTTOM-UP-DUT-ROD(int *p,int n)
{
int r[n];
int s[n];
r[0]=0;
for(int j=1;j<n;j++)
{
q=-INF;
for(int i=1;i<j;i++)
{
if(q<p[i]+r[j-i])
{
q=p[i]+r[j-i];
s[j]=i;
}
}
r[j]=q;
}
return r && s;
}
下面的过程接受两个参数:价格表P和钢条长度N然后调用上面的函数来计算切割下来的每段钢条的长度S[1…N],然后输出长度为N的钢条的完整的最优切割方案:
PEINT-CUT-ROD-SOLUTION(int *p,int n)
{
(r,s)=EXTENDED-BOTTOM-UP-CUT-ROD(p, n);
while(n>0)
{
printf s[n];
n=n-s[n];
}
}
程序结果: