动态规划(dynamic programming)是通过组合子问题的解而解决整个问题的。分治算法是指将问题划分为一些独立的子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的解。动态规划适用于子问题不是独立的情况,也就是各子问题包含公共的子子问题。在这种情况下,若用分治法则会做许多不必要的工作,即重复地求解公共的子子问题。动态规划对每个子子问题只求解一次,将结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案。
动态规划通常应用于最优化问题。此类问题可能有很多种可行解,每个解有一个值,我们希望找出一个具有最优(最大或最小)值的解,称这样的解为该问题的一个最优解(而不是确定的最优解),因为可能存在多个取最优解的值。
动态规划算法的设计可分为如下4个步骤:
(1)描述最优解的结构。
(2)递归定义最优解的值。
(3)按自底向上的方式计算最优解的值。
(4)由计算出的结果构造一个最优解。
装配线调度
Colonel汽车公司在有两条装配线的工厂内生产汽车。一个汽车底盘在进入每一条装配线后,在一些装配站中会在底盘上安装部件,然后,完成的汽车在装配线的末端离开。每一条装配线上有n个装配站,编号为j=1,2,…,n。将装配线i(i为1或2)的第j个装配站表示为。装配线1的第j个站()和装配线2的第j个站()执行相同的功能。然而,这些装配站是在不同的时间建造的,并且采用了不同的技术,因此,每个站上所需的时间是不同的,即使在两天不同装配线相同位置的装配站上也是这样。把在装配站上所需的装配时间记为。底盘进入装配线i的进入时间为ei,装配完的汽车离开装配线i的离开时间为xi。
正常情况下,一旦一个底盘进入一条装配线后,它只会经过该条装配线。在相同的装配线中,从一个装配站到下一个装配站所花的时间可以忽略。偶尔会来一个特别急的订单,客户要求尽可能快地制造这些汽车。对这些加急的订单,底盘仍然依序经过n个装配站,但工厂经理可以将部分完成的汽车在任何装配站上从一条装配线移到另一条装配线上。把已经通过装配站的一个底盘从装配线i移走所花的时间为,其中i=1,2,j=1,2,…,n-1(因为在第n个装配站后,装配已经完成)。问题是要确定在装配线1内选择哪些站以及在装配线2内选择哪些站,以使汽车通过工厂的总时间最小。
解题报告:
步骤1:描述最优解的特征。
对于装配线调度问题,一个问题(找出通过装配站的最快路线)最优解包含了子问题(找出通过或的最快路线)的一个最优解。称这个性质为最优子结构,这是是否可以应用动态规划方法的标志之一。可以利用子问题的最优解来构造原问题的一个最优解。
步骤2:一个递归的解。
利用子问题的最优解来递归定义一个最优解的值。选择在两条装配线上通过装配站j的最快路线问题作为子问题,j=1,2,…,n。
令表示一个底盘从起点到装配站的最快可能时间,为底盘通过工厂的所有路线的最快时间,则,其中,。进一步得出:
步骤3:计算最快时间。
整个过程花费时间。
步骤4:构造通过工厂的最快路线。
参考代码如下:
1 #include <iostream> 2 using namespace std; 3 4 typedef struct 5 { 6 int **a; 7 int **t; 8 int *e; 9 int *x; 10 int n; 11 }LineCondition; 12 13 void FastestWay(const LineCondition *line, int **&l, int **&f, int &ftotal, int &lend) 14 { 15 int n = line->n; 16 17 f[0][0] = line->e[0] + line->a[0][0]; 18 f[1][0] = line->e[1] + line->a[1][0]; 19 20 for (int i = 1; i < n; ++i) 21 { 22 if (f[0][i - 1] + line->a[0][i] <= f[1][i - 1] + line->t[1][i - 1] + line->a[0][i]) 23 { 24 f[0][i] = f[0][i - 1] + line->a[0][i]; 25 l[0][i] = 1; 26 } 27 else 28 { 29 f[0][i] = f[1][i - 1] + line->t[1][i - 1] + line->a[0][i]; 30 l[0][i] = 2; 31 } 32 33 if (f[1][i - 1] + line->a[1][i] <= f[0][i - 1] + line->t[0][i - 1] + line->a[1][i]) 34 { 35 f[1][i] = f[1][i - 1] + line->a[1][i]; 36 l[1][i] = 2; 37 } 38 else 39 { 40 f[1][i] = f[0][i - 1] + line->t[0][i - 1] + line->a[1][i]; 41 l[1][i] = 1; 42 } 43 } 44 45 if (f[0][n - 1] + line->x[0] <= f[1][n - 1] + line->x[1]) 46 { 47 ftotal = f[0][n - 1] + line->x[0]; 48 lend = 1; 49 } 50 else 51 { 52 ftotal = f[1][n - 1] + line->x[1]; 53 lend = 2; 54 } 55 } 56 57 void PrintStations(int **l, int lend, int n) 58 { 59 int *a = new int[n]; 60 int i = lend - 1; 61 int m = n; 62 a[--m] = i + 1; 63 64 for (int j = n - 1; j > 0; --j) 65 { 66 i = l[i][j] - 1; 67 a[--m] = i + 1; 68 } 69 70 for (int i = 0; i < n; ++i) 71 { 72 cout << "line " << a[i] << ", station " << i + 1 << endl; 73 } 74 75 delete [] a; 76 } 77 78 79 int main(void) 80 { 81 LineCondition line; 82 83 cout << "请输入装配站数目n:"; 84 cin >> line.n; 85 86 line.a = new int *[2]; 87 for (int i = 0; i < 2; ++i) 88 { 89 line.a[i] = new int [line.n]; 90 } 91 for (int i = 0; i < 2; ++i) 92 { 93 cout << "请输入装配线" << i + 1 << "上每个装配站耗费时间a[i][j]:"; 94 for (int j = 0; j < line.n; ++j) 95 { 96 cin >> line.a[i][j]; 97 } 98 } 99 100 line.t = new int *[2]; 101 for (int i = 0; i < 2; ++i) 102 { 103 line.t[i] = new int [line.n - 1]; 104 } 105 for (int i = 0; i < 2; ++i) 106 { 107 cout << "请输入装配线t[" << i << "][j]:"; 108 for (int j = 0; j < line.n - 1; ++j) 109 { 110 cin >> line.t[i][j]; 111 } 112 } 113 114 line.e = new int [2]; 115 cout << "请输入e1,e2:"; 116 cin >> line.e[0] >> line.e[1]; 117 118 line.x = new int [2]; 119 cout << "请输入x1,x2:"; 120 cin >> line.x[0] >> line.x[1]; 121 122 int **l = new int *[2]; 123 int **f = new int *[2]; 124 for (int i = 0; i < 2; ++i) 125 { 126 l[i] = new int [line.n]; 127 f[i] = new int [line.n]; 128 } 129 130 int lend, ftotal; 131 FastestWay(&line, l, f, ftotal, lend); 132 PrintStations(l, lend, line.n); 133 134 return 0; 135 }
按照如下图片输入相关数据:
测试结果如下: