动态规划主要用于求解最优化问题,方法与分治法类似,也是将原问题分解成多个子问题,通过递归的方法求解子问题。不同之处就是动态规划会通过增加程序空间复杂度的方式来将时间复杂度为指数级降低为多项式,通俗的讲就是动态规划会利用数组记录下子问题的结果,当再需要计算该子问题时直接调用该结果即可,就不用再去计算,从而大大降低了程序的时间复杂度。
通常按照如下4个步骤来设计一个动态规划算法:
1.刻画一个最优解的结构特征。换句话讲就是你怎么将原问题分解成多个子问题。
2.递归的定义最优解的值。就是你怎样将多个子问题的结果合并成为原问题的解,得到最优值。
3.计算最优解。就是你采用什么方法来计算最优解。有自上而下和自下而上,通常采用自下而上,原因后面讲。
4.利用计算出的信息构造一个最优解。就是求解得到最优值对应的因变量。
下面举四个例子来具体分析:
1.钢条切割
具体问题如下:
serling公司购买长钢条,将其切割为短钢条出售,切割工序本身没有成本支出。公司管理层希望知道最佳切割方案。钢条长度与价格关系如下:
按照动态规划的步骤:
1.找到最优子结构
假设在长度为k处进行切割,可以得到最优解。我们就可以将原问题长度为[1,n]的钢条最优解问题分解为长度[1,k],[k,n]的钢条切割的子问题。
2递归定义最优解的值
对于两个子问题,可以假定长度[1,k]的钢条不再切割,而[k,n]的钢条在进行切割,这样我们就可以将收益公式写出:
r(n)=max(pi+r(n-i))(1<i<n)
3.计算最优解
计算最优解有两种方法,一种是自上而下,一种是自下而上,两种实现方法的时间复杂度都为多项式级别的,但是自上而下算法可能没有递归到所有的可能,而自下而上的算法比较好理解,而且时间复杂度通常有更小的系数。下面具体讲一下两个方法:
自上而下:采用倒推的方式,我们想知道长度为[1,n]的最优解就必须知道底层[k,n](这里仅仅说明一下,k为切割点)的最优解,相应的我们要知道[k,n]的最优解,就必须知道下一底层的最优解,最后推到最下层结构时,所有的最优解就都出来了。
自下而上:该方法是先算底层的最优解,由底层的最优解推到上层数据的最优解,最终得到长度为[1,n]的最优解。
最后实现的C代码如下:
#include"stdio.h"
#define length 10
int memoized_cut_rod(int *p,int n);
int bottom_down_cut_rod(int *p,int n,int *r);
int max(int a,int b);
int bottom_down_cut_rod(int *p,int n);
int result;
void main()
{
int p[length]={1,5,8,9,10,17,17,20,24,30};
printf("%d\n",memoized_cut_rod(p,length));
printf("%d\n",bottom_down_cut_rod(p,length));
printf("%d",result+1);
getchar();
}
int memoized_cut_rod(int *p,int n) //自上而下
{
int r[length+1];
int i;
for(i=0;i
=0) //由于该条件的存在使时间复杂度由指数变成了多项式
{
return r[n];
}
if(n==0) //不加该条件会导致溢出
{
q=0;
}
else
{
q=-9999;
for(i=0;i
b)
{
return a;
}
else
{
return b;
}
}
int bottom_down_cut_rod(int *p,int n) //自下而上
{
int r[length+1],i=0,j=0;
int q=0;
r[0]=0; //防止循环时候0出现
for(i=1;i<=n;i++)
{
q=-9999;
for(j=0;j
q)
{
result=j;
q=p[j]+r[i-j-1];
}
/*q=max(q,p[j]+r[i-j-1]);
if(q==p[j]+r[i-j-1])*/
}
r[i]=q;
}
return r[n];
}