1 动态规划介绍
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。
动态规划通常应用于最优化问题。此类问题可能有很多种可行解。每个解有一个解,而我们希望找出一个最优解(最大值或最小值)。
动态规划算法的设计可分为如下4个步骤:
(1)描述最优解的结构;
(2)递归定义最优解的值;
(3)按自底向上的方式计算最优解的值;
(4)由计算出的结果构造一个最优解。
第1~3步构成问题的动态规划解得基础,第4步在只要求计算最优解的值时可以略去。如果的确做了第4步,则有时要在第3步的计算中记录一些附加信息,要构造一个最优解变得容易。
2 装配线调度问题描述
一个找出通过工厂装配线的最快方式的制造问题。共有两条装配线,每一条装配线上有n个装配站,编号为j = 0, 1, … , n - 1。装配线i(i = 0或1),在装配站S[i][j]上所需的装配时间记为a[i][j]。一个汽车底盘进入工厂,然后进入装配线i的进入时间为e[i],在通过一条线的第j个装配站后,这个底盘来到任一条线的第(j + 1)个装配站。如果留在相同的装配线上,则没有移动的开销;如果在装配站S[i][j]后,它移动到了另一条线上,则花费时间t[i][j]。在离开一条线的第n个装配站后,完成的汽车离开装配线i的离开时间为x[i] 。
f[i][j]:表示底盘从起点到装配站S[i][j]的最快可能时间。
t[i][j]:表示底盘从装配站S[i][j]移到另一条装配线花费的时间。
a[i][j]:表示底盘在装配站S[i][j]停留的时间。
e[i]:表示底盘进入装配线i的时间。
x[i]:表示底盘完成安装离开装配线花费的时间。
3 问题分析
步骤1:通过工厂最快线路的结构
通过装配站S[1][j]的最快线路只能是以下二者之一:
(1) 通过装配站S[1][j-1]的最快线路,然后直接通过装配站S[1][j]。
(2) 通过装配站S[2][j-1]的最快线路,从装配线2移动到装配线1,然后通过装配站S[1][j]。
当然,通过装配站S[2][j]的最快线路也只能是以下二者之一:
(1) 通过装配站S[2][j-1]的最快线路,然后直接通过装配站S[2][j]。
(2) 通过装配站S[1][j-1]的最快线路,从装配线1移动到装配线2,然后通过装配站S[1][j]。
为了解决装配线调度问题,即寻找通过任意一条装配线上的第j个装配站的最快路线,就可以解决装配线调度问题。
步骤2:一个递归的解
假设已经完成了汽车的装配,那么这些路线的较快者即是:
fast =min{f[1][n] + x[1], f[2][n] + x[2]};
初值为:
f[1][1] =e[1] + a[1][1];
f[2][1] =e[2] + a[2][1];
考虑f[i][j](j>1)的计算,很明显:
f[1][j] =min{f[1][j-2] + a[1][j], f[2][j-1] + t[2][j-1] + a[2][j]};
f[2][j] =min{f[2][j-2] + a[2][j], f[1][j-1] + t[1][j-1] + a[1][j]};
步骤3:计算最快时间
整个过程花费时间为O(n);
步骤4:构造通过工厂的最快路线
为了构造通过工厂的最快路线,需要构造一个新的数组l[i][j],其值为(1或者2),表示装配线编号,装配站j-1被通过S[i][j]的最快路线所使用。
4 实例与编码
1. 实例
e[1]=2, e[2]=4;
x[1]=3; x[2]=2;
a[i][j]的值如下表:
j |
1 |
2 |
3 |
4 |
5 |
6 |
a[1][j] |
7 |
9 |
3 |
4 |
8 |
4 |
a[2][j] |
8 |
5 |
6 |
4 |
5 |
7 |
t[i][j]的值如下表:
j |
1 |
2 |
3 |
4 |
5 |
t[1][j] |
2 |
3 |
1 |
3 |
4 |
t[2][j] |
2 |
1 |
2 |
2 |
1 |
2. 编码实现
针对具体问题的实现代码
/** * fastway - 求解装配线最快线路的时间 * Param.: * @a[][]: 在装配线i上第j个装配站停留的时间(i=0,1;j=0...(n-1)) * @t[][]: 从装配线i上第j-1个装配站移动到另一个装配线的时间 * @e[]: 进入装配线i的时间 * @x[]: 在装配线i上最后一个装配站离开的时间 * @n: 装配站的个数 * @f[][]: 经过装配线i上第j个装配站时的最快时间(被赋值) * @flag[][]: 通过装配站j的最快线路的装配站j-1所在的装配线(被赋值) * @fast: 最快线路的时间(被赋值) * @falg: 最快线路的最后一个装配站所在的装配线(被赋值) * return: * 无 */ void fastway(int a[][6], int t[][5], int e[], int x[], int n, int f[][6], int flag[][6], int *fastp, int *lastflagp) { int j, fast, lastflag; f[0][0] = e[0] + a[0][0]; f[1][0] = e[1] + a[1][0]; for (j = 1; j < n; j++) { /* 计算f[0][j]和flag[0][j] */ if ((f[0][j - 1] + a[0][j]) <= (f[1][j - 1] + t[1][j - 1] + a[0][j])) { f[0][j] = f[0][j - 1] + a[0][j]; flag[0][j] = 0; } else { f[0][j] = f[1][j - 1] + t[1][j - 1] + a[0][j]; flag[0][j] = 1; } /* 计算f[1][j]和flag[1][j] */ if ((f[1][j - 1] + a[1][j]) <= (f[0][j - 1] + t[0][j - 1] + a[1][j])) { f[1][j] = f[1][j - 1] + a[1][j]; flag[1][j] = 1; } else { f[1][j] = f[0][j - 1] + t[0][j - 1] + a[1][j]; flag[1][j] = 0; } } /* 计算最优解fast和flag(表示从哪个装配线上的n装配站完成) */ if ((f[0][n - 1] + x[0]) <= (f[1][n - 1] + x[1])) { fast = f[0][n - 1] + x[0]; lastflag = 0; } else { fast = f[1][n - 1] + x[1]; lastflag = 1; } *fastp = fast; *lastflagp = lastflag; } /** * printway - 求解装配线最快线路的时间 * Param.: * @flag[][]: 通过装配站j的最快线路的装配站j-1所在的装配线(被赋值) * @falg: 最快线路的最后一个装配站所在的装配线(被赋值) * @n: 装配站的个数 * return: * 无 */ void printway(int flag[][6], int *lastflagp, int n) { int i, j; i = *lastflagp; printf("line:%d, station:%d\n", i, n); for (j = n; j >= 1; j--) { i = flag[i][j]; printf("line:%d, stations:%d\n", i, (j - 1)); } }