zoukankan      html  css  js  c++  java
  • 动态规划

    动态规划与分治法的区别

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

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

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

    动态规划算法的步骤

    我们通常按如下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 0
    q=-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 arrar
    for 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=0
    else 
        q=-for i=1 to n
            q=max(q,p[i]+MEMOIZED-CUT-ROD-AUX(p,n-1,r))
    r[n]=q
    return 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     else
    15     {
    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 array
    r[0]=0
    for j=1 to n
        q=-for i=1 to j
            q=max(q,p[i]+r[j-i])
        r[j]=q
    return 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 arrays
    r[0]=0
    for 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]=q
    return 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
  • 相关阅读:
    [转]C#中抽象类和接口的区别
    [转]OO设计原则总结
    [转]MVC3快速搭建Web应用(三)实例篇
    原生内存(堆外内存)
    使用SAX解析XML实例根据属性查找
    Cannot forward after response has been committed问题解决及分析
    dubbo服务化实施整理
    bean:write 标签不能显示出 换行符的解决方案
    Dubbo原理解析监控
    thread之1:java与线程
  • 原文地址:https://www.cnblogs.com/runnyu/p/4682771.html
Copyright © 2011-2022 走看看