动态规划

来源:互联网 发布:js判断偶数 编辑:程序博客网 时间:2024/06/04 18:12
动态规划与分治法的区别

动态规划与分治法相似,都是通过组合子问题的解来求解原问题。

分治法将问题划分为互不相交的子问题,递归地求解子问题,再将它们的解组合起来,求出原问题的解。

与之相反,动态规划应用于子问题重叠的情况。这种情况下,分治算法会反复地求解相同的子问题,而动态规划算法会将这些重复子问题的解保存起来(通常使用数组),避免不必要的计算工作。

 

动态规划算法的步骤

我们通常按如下4个步骤设计一个动态规划算法(最优解跟最优值是不同的概念,下面会举例讲到):

1.刻画一个最优解的结构特征

2.递归地定义最优值

3.计算最优值,通常采用自底向上的方法

4.利用计算出的信息构造出一个最优解

 

钢条切割问题

钢条切割问题是这样的:给定一段长度为n英寸的钢条和一张价格表(如下图所示),求切割钢条方案(最优解),使得销售利益(最优值)最大。

长度为n英寸的钢条共有2^n-1种切割方案,因为每一英寸都可以选择切割或不切割。

如果一个最优解将钢条切割成k端(1≤k≤n)  

那么最优切割方案:

得到的最大利益为:

对于rn(n≥1),我们可以用更短的钢条的最优切割利益来描述它(rk(1≤k≤n)代表长度为k英寸的钢条切割之后的最大利益)

为了求解规模为n的原问题,我们先求解形式完全一样,但规模更小的子问题:

在首次切割后,我们将两端钢条看成两个独立的钢条切割问题的实例。

我们通过组合两个相关子问题的最优解,并在所有可能的两段切割方案中选取组合收益最大者,构成原问题的最优解。

我们称钢条切割问题满足最优子结构性质:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。

 

我们可以使用简单递归的方法来求解这个问题:

将长度为n的钢条分解为左面开始一段,以及剩余的一段(继续分解)。于是我们可以得到下面的公式:

下面过程实现了该公式的计算,它采用的是一种直接的自顶而下的递归方法

CUT-ROD(p,n)if n==0    return 0q=-for i=1 to n    q=max(q,p[i]+CUT-ROD(p,n-i))return q

过程CUT-ROD以价格数组p[1...n]和整数n位输入,返回长度为n的钢条的最大利益。

CUT-ROD的效率很差,因为CUT-ROD反复地用相同的参数值对自身进行递归调用,即反复求解相同的子问题。

 

使用动态规划方法求解钢条切割问题:

动态规划对于每个子问题只求解一次,并将结果保存下来。如果随后再次需要此子问题的解,只需查找保存的结果而不必重新计算。

动态规划有两种等价的实现方法,下面以钢条切割问题为例展示这两种方法:

1.带备忘的自顶向下的方法

下面给出的是自顶向下CUT-ROD过程的伪代码,加入了备忘机制:

//初始化MEMOIZED-CUT-ROD(p,n)let r[0..n] be a new arrarfor i=0 to n    r[i]=-return MEMOIZED-CUT-ROD-AUX(p,n,r)//算法主体MEMOIZED-CUT-ROD-AUX(p,n,r)if r[n]≥0    return r[n]if n==0    q=0else     q=-for i=1 to n        q=max(q,p[i]+MEMOIZED-CUT-ROD-AUX(p,n-1,r))r[n]=qreturn q

每个子问题的解保存在数组r中,其中每个元素初始化为负无穷来表示未知值。

调用MEMOIZED-CUT-ROD-AUX第一步判断所需值是否已知,如果是,则直接返回保存的值,避免了重复计算子问题。

自顶向下的方法可以这样理解:每次的递归调用会将问题分解为子问题,可以从最终的子问题开始着手,本例可以从MEMOIZED-CUT-ROD-AUX(p,0,r)尝试往上推。

下面是该方法的实现代码

 1 #define N 10 2 #include <iostream> 3 #include <algorithm> 4 using namespace std; 5  6 //初始化在main函数中实现  7 int memoized_cut_rod(int p[],int n,int r[]) 8 { 9     if(r[n]>=0)10         return r[n];11     int q;12     if(n==0)13         q=0;14     else15     {16         q=-1;17         for(int i=1;i<=n;++i)18             q=max(q,p[i]+memoized_cut_rod(p,n-i,r)); 19     }20     r[n]=q;21     return q;22 }23 24 int main()25 {26     //数组p从1开始,因此把p[0]设置为0 27     int p[]={0,1,5,8,9,10,17,17,20,24,30};28     int r[N+1];29     for(int i=0;i<=N;++i)30         r[i]=-1;31     for(int i=1;i<=N;++i)32         cout<<"n="<<i<<" r="<<memoized_cut_rod(p,i,r)<<endl;33     return 0;34 }
View Code

2.自底向上法

BOTTOM-UP-CUT-ROD(p,n)let r[0..n] be a new arrayr[0]=0for j=1 to n    q=-for i=1 to j        q=max(q,p[i]+r[j-i])    r[j]=qreturn r[n]

自底向上版本更为简单,先创建一个数组r来保存子问题的解。对j=1,2,...,n按升序求解每个规模为j的子问题。

自底向上的方法似乎更容易理解:钢条长度从1开始每次递增并记录当前长度的最优值。

下面是该方法的实现代码。为了打印最优值,数组r的初始化放在了main函数中,然后作为参数传进botton_up_cut_rod函数中

 1 #define N 10 2 #include <iostream> 3 #include <algorithm> 4  5 using namespace std; 6  7 int botton_up_cut_rod(int p[],int n) 8 { 9     int r[N+1];10     r[0]=0;11     int q;12     for(int j=1;j<=n;++j)13     {14         q=-1;15         for(int i=1;i<=j;++i)16             q=max(q,p[i]+r[j-i]);17         r[j]=q;18         cout<<"n="<<j<<" r="<<r[j]<<endl;19     }    20     return q;21 }22 23 int main()24 {25     int p[]={0,1,5,8,9,10,17,17,20,24,30};26     botton_up_cut_rod(p,N);27     return 0;28 }
View Code

 

重构解

上面给出了使用动态规划方法求解钢条切割问题的最优值。

修改我们的BOTTON-UP-CUT-ROD方法,让它保存最优解的信息(数组s),根据这些信息来构造最优解。

EXTENDED-BOTTOM-UP-CUT-ROD(p,n)let r[0..n] and s[0..n] be new arraysr[0]=0for j=1 to n    q=-for i=1 to j        if q<p[i]+r[j-i]            q=p[i]+r[j-i]            s[j]=i    r[j]=qreturn r and s

跟BOTTON-UP-CUT-ROD有一点不同的是:

在求解规模为j的子问题时将第一段钢条的最优切割长度i保存在s[j]中。EXTENDED-BOTTOM-UP-CUT-ROD(p,10)会返回下面的数组

s[1..n]记录了每条钢条的长度,可以根据数组s构造最优解。

下面是实现代码。为了输出最优值跟最优解,数组r跟数组s的定义都放在main函数中。

 1 #define N 10 2 #include <iostream> 3  4 using namespace std; 5  6 int extened_botton_up_cut_rod(int p[],int r[],int s[],int n) 7 { 8     int q; 9     for(int j=1;j<=n;++j)10     {11         q=-1;12         for(int i=1;i<=j;++i)13         {14             if(q<p[i]+r[j-i])15             {16                 q=p[i]+r[j-i];17                 s[j]=i;18             }19         }20         r[j]=q;21     }    22     return q;23 }24 25 int main()26 {27     int p[]={0,1,5,8,9,10,17,17,20,24,30};28     int r[N+1]={0};29     int s[N+1]={0}; 30     extened_botton_up_cut_rod(p,r,s,N);31     for(int i=1;i<=N;++i)32     {33         cout<<"n="<<i<<" r="<<r[i]<<" ";34         //构造最优解  35         cout<<"solution:";36         int n=i;37         while(n>0)38         {39             cout<<s[n]<<" ";40             n=n-s[n];41         }42         cout<<endl; 43     }44     return 0;45 }
View Code

 

0 0
原创粉丝点击