如果一个问题可以分为多个子问题,这若干个子问题如果是相互独立的,那么我们可以使用分治的思想解决,如果分解的子问题之间并不是独立的,那么就可以使用动态规划来解决了。
动态规划原理:
一个最优策略的决策过程中产生的子问题的子策略必然也是最优的,简单的一个说明就是,最终的策略最优,必然是在前一个最优策略的基础上做出的最优策略。
动态规划的一般步骤:
1)描述最优解的结构
2)对最优解的值进行递归定义
3)按照自底向上的方法计算最优解的值
4)根据3)计算出的结果构造一个最优解
一个例子:
有两条生产线1,2,标记为i,每个生产线有n道工序,标记为j,每条生产线上的第j道工序的功能相同,但是消耗的时间不同,当产品进入生产线之后,可以在任意一道工序j完成之后将产品转移到另一条生产线的第j+1道工序,转换线路会消耗时间Tij,表示在第i条生产线上完成了第j道工序之后转移到另一条生产线所消耗的时间。ei表示进入第i条生产线所消耗的时间,xi表示从第i条生产线离开时消耗的时间。画图表示为:
其中:
entraA是进入第1条生产线消耗的时间
entraB是进入第2条生产线消耗的时间
aij表示第i条生产线的第j道工序消耗的时间
tij表示从第i条生产线的第j道工序转移到另一条生产线的第k+1道工序过程中消耗的时间
exitA表示离开第1条生产线消耗的时间
exitB表示离开第2条生产线消耗的时间
分析:
1 如果j=1,那么只有一条路径,我们可以比较entraA和entraB的大小即可,如果j>=2,有两种可能,一是产品直接从同一条生产线的前一道工序过来,进入了第j道工序,二是产品是从另一条生产线转移过来,这个过程花费了tij-1的时间。但是已经确定的是从产品进入生产线 一直到出了第j-1道工序必定是最快的路径。这是我们递推的假设,我们就这样分析出了子问题,即,要求第j道工序的最快路径,我们可以先求第j-1道工序的最快路径。
2 定义表达式
fi[j]表示一个产品从起点进入生产线一直到第i条生产线的第j道工序的最快时间
f*表示全程的最快路径,问题可以表示为如下方程:
f* = min(f1[n]+exitA,f2[n]+exitB)
n为总共的工序
初始化条件为:
f1[1]=entraA+a1,1 (1)
f2[1]=entraB+a2,1 (2)
当j>=2时,我们就需要考虑1中分析的两种情况了
f1[j]=min(f1[j-1]+a1,j,f2[j-1]+t2,j-1+a1,j) (3)
f2[j]=min(f2[j-1]+a2,j,f1[j-1]+t1,j-1+a2,j) (4)
java实现:
/**
* @param a 每道工序消耗的时间
* @param t 转移到另一条生产性消耗的时间
* @param line 用于记录最优线路
* @param entraA 进入生产线1的时间
* @param entraB 进入生产线2的时间
* @param exitA 离开生产线1消耗的时间
* @param exitB 离开生产线2消耗的时间
* @param n 工序的总数
*/
public static void fastWayTime(int[][] a, int[][] t, int[][] line, int entraA, int entraB, int exitA, int exitB, int n) {
int minTime;
int[][] f = new int[2][3];
f[0][0] = entraA + a[0][0]; //(1)的表示 初始化
f[1][0] = entraB + a[1][0]; //(2)的表示
for(int j = 1;j<n;j++) {
if(f[0][j-1] + a[0][j] <= f[1][j-1] + t[1][j-1] + a[0][j]) { //(3)
f[0][j] = f[0][j-1] + a[0][j];
line[0][j] = 0; //line的第0位不记录生产线,因为它只能从起点进入第一道工序
} else {
f[0][j] = f[1][j-1]+t[1][j-1]+a[0][j];
line[0][j] = 1; //line的第0位不记录生产线,因为它只能从起点进入第一道工序
}
if(f[1][j-1] + a[1][j] <=f[0][j-1]+t[0][j-1]+a[1][j]) { //(4)
f[1][j] = f[1][j-1] + a[1][j];
line[1][j] = 1; //line的第0位不记录生产线,因为它只能从起点进入第一道工序
} else {
f[1][j] = f[0][j-1] +t[0][j-1] +a[1][j];
line[1][j] = 0; //line的第0位不记录生产线,因为它只能从起点进入第一道工序
}
}
if(f[0][n-1]+exitA <= f[1][n-1] +exitB) { //f数组中记录了各条线路的各道工序的最短时间,因此只需要比较最后一道工序就行了
minTime = f[0][n-1] + exitA;
} else {
minTime = f[1][n-1] + exitB;
}
System.out.println("最快路径耗时: "+minTime);
fastWayPath(line, lastStation, n);
}
public static void fastWayPath(int[][] line, int lastStation, int n) {
int i = lastStation;
Stack<Integer> path = new Stack<Integer>();
path.push(i);
for(int j=n;j>1;j--) {
i = line[i][j-1]; //line记录的是第j道工序的最优解的前一道工序是从哪条线路过来的,因此必须从后往前读取
path.push(i);
}
int k=1;
while(!path.isEmpty()&& k<=n) {
System.out.println("线路"+(path.pop()+1)+": 工序"+k);
k++;
}
}
public static void main(String[] args) {
int[][] a = {{4,3,2},{2,2,3}};
int entraA = 2;
int entraB = 1;
int exitA = 2;
int exitB = 3;
int[][] t = {{4,1},{3,2}};
int n = 3;
int[][] line = new int[2][3];
fastWayTime(a, t, line, entraA, entraB, exitA, exitB, n);
}