• 《算法导论》第15章 动态规划 (1)装配线调度



    动态规划通常用于有很多种可行解,而找出最优解的问题。

    具体可分为4个步骤:
    1)描述最优解的结构。
    2)递归定义最优解的值。
    3)自底向上计算最优解的值。
    4)由最优解的值构造出最优解。

    下面通过一个具体问题来看究竟如何用动态规划算法来解决问题。

    Colonel汽车公司在有两条装配线的工厂里生成汽车。每一条装配线上有n个装配站,
    两条生产线上相同位置的装配站功能相同,但所需时间不同,并且汽车底盘在两条
    装配线间转移要花费一定的时间。如下图所示两条生产线。



    这里首先尝试下下一章的贪心算法,在每一步都取最省时间的装配站。首先进入装配线1时间为2 + 7
    小于装配线2的4 + 8,因此进入装配线1。之后装配站2的时间9大于转移到装配线2的时间2 + 5,因此
    转移到装配线2上。以此类推可以得到下图中标红的路线:



    可以清楚地看出,在这个问题上采用贪心的策略是不对的,那么哪里出了问题呢?问题的关键就
    在于两条装配线间转移是需要不同时间的。以装配站Station-1,3为例,虽然选择进入Station-1,4保证
    了眼前的最优(Station-1,4的时间4大于转移到装配线2的时间1 + 4),但是接下来在Station-1,4至少
    要耗费时间为8,一共需要时间为4 + 8 = 12。但若在Station-1,3时转移到了装配线2的Station-2-4,
    花费时间1 + 4 = 5,接下来直接进入Station-2,5,那么一共需要时间5 + 5 = 10。

    这就是问题的关键!在Station-1,3处只能看到眼前的两种选择哪个更节省时间,却没法知道后来的情况。
    这也就是“步步最优不等于全局最优”的道理。然而所有路线的可能性为2 ^ n,其中n为每条装配线上
    装配站的个数。因此当有很多装配站时,使用Brute force生成比较各条路线的值几乎是不可能的。
    那么现在就要请出动态规划来帮忙了。

    e1和e2表示进入装配线1和2所需时间,x1和x2表示出装配线时间。
    a1和a2表示各个装配站花费时间,t1和t2则表示装配线间转移花费的时间。
    装配线1和2的最优路线保存到数组L1和L2中用于构造一个最优解。
    下面来看具体实现代码。

    #include <stdio.h>
    #include <stdlib.h>
    #define SIZE 6
    
    int e1 = 2, e2 = 4;
    int a1[] = { 7, 9, 3, 4, 8, 4 };
    int a2[] = { 8, 5, 6, 4, 5, 7 };
    int t1[] = { 2, 3, 1, 3, 4 };
    int t2[] = { 2, 1, 2, 2, 1 };
    int x1 = 3, x2 = 2;
    
    void fastest_way(void)
    {
         int f1[SIZE], f2[SIZE];
         int l1[SIZE], l2[SIZE];
    
         f1[0] = e1 + a1[0];
         f2[0] = e2 + a2[0];
    
         int j;
         for (j = 1; j < SIZE; j++) {
              if (f1[j - 1] < f2[j - 1] + t2[j - 1]) {
                   f1[j] = f1[j - 1] + a1[j];
                   l1[j] = 0;
              } else {
                   f1[j] = f2[j - 1] + t2[j - 1] + a1[j];
                   l1[j] = 1;
              }
              if (f2[j - 1] < f1[j - 1] + t1[j - 1]) {
                   f2[j] = f2[j - 1] + a2[j];
                   l2[j] = 1;
              } else {
                   f2[j] = f1[j - 1] + t1[j - 1] + a2[j];
                   l2[j] = 0;
              }
         }
         
         int f, l;
         if (f1[j - 1] + x1 < f2[j - 1] + x2) {
              f = f1[j - 1] + x1;
              l = 0;
         } else {
              f = f2[j - 1] + x2;
              l = 1;
         }
    
         // 构造最优解
         printf("%d\n", f);
         print_path(&l1, &l2, l, SIZE - 1);          
    }
    
    void print_path(int *l1, int *l2, int l, int j)
    {
         if (j > 0) {
              if (l == 0)
                   print_path(l1, l2, l1[j], j - 1);
              else
                   print_path(l1, l2, l2[j], j - 1);
         }
         printf("%d line, %d station\n", l, j);
    }
    
    int main(void)
    {
         fastest_way();
         return 1;
    }

    原来动态规划也是从装配站1开始到N逐步计算,但与贪心法不同的是,它用数组f1[j]和f2[j]记录了通过
    装配站a1[j]和a2[j]的最优解。以a1[j]为例,计算通过a1[j]的最优解时,不是像贪心法那样只通过前一个
    装配站a1[j-1]和a2[j-1]+t2[j-1]谁更省时间,而是比较了f1[j-1]和f2[j-1]+t2[j-1]来决定到底是从a1[j-1]直接
    运送到a1[j],还是由a2[j-1]转移到装配线1。这样可以明显看出动态规划与贪心算法的区别,贪心算法只顾
    眼前(前一个装配站的情况),而动态规划则是根据前面所有装配站的情况(f1和f2保存了前面所有装配站
    的最优解)。重点是理解f1和f2的意义,从而明白这个问题的最优子结构是如何定义。

    下图中标红的路线才是最优路线。



    在使用L1和L2构造最优解时要注意,要从后向前处理,因为我们只知道最优路线是从装配线1中出来。
    所以在这里可以采用递归地方式,来正序打印最佳路线。这也是习题15.1-1的答案。



  • 相关阅读:
    Caused by: 元素类型为 "package" 的内容必须匹配 "(result-types?,interceptors?,default-interceptor-ref?,default-action-ref?,default-class-ref?,global-results?,global-exception-mappings?,action*)"
    web.xml中的url-pattern映射规则
    基于Bootstrap的超酷jQuery开关按钮插件
    jQuery实例-记住登录信息
    java对cookie的操作
    jQuery插件 -- Cookie插件jquery.cookie.js(转)
    分布式系统架构师必须要考虑的四个方面
    初八回杭州的路上
    再说项目经历
    写项目经历的注意事项
  • 原文地址:https://www.cnblogs.com/xiaomaohai/p/6157846.html
Copyright © 2020-2023  润新知