有n个工件,j1, j2, …, jn, 每个工件有一个给定的最小加工时间pj , 以及一个权重wj
1<=j <=n。有一个用来加工这些工件的机器。假设这个机器一次可以加工任意多的工件, 同时加工的工件组成一个批B。在同一批B里加工的工件必须同时开始,同时结束,即它们有同样的开始时间sj, 同样的结束时间Cj= sj + p(B),j∈B, 这里p(B)=max{pj} j∈B。 从一批工件开始加工到结束中间不允许中断。
请问:如何给这些工件分批,以及如何对这些批进行排序使得所有工件的加权完成时间之和 Σwj*Cj (j=1到n) 最小。
请设计一个算法并求解下面的实例:
n = 50
最小加工时间pj依次是:
1,5,2,4,5,9,5,10,5,6,6,7,8,7,11,6,6,7,11,10,7,12,8,8,8,6,5,13,14,9,8,9,3,3,11,2,9,13,4,6,4,4,8,9,6,15,5,7,9,3
权重wj依次是:
2,3,2,5,4,4,6,8,5,7,7,4,6,9,12,4,4,3,5,5,3,6,9,5,9,12,3,7,12,11,11,3,4,5,3,12,2,6,6,4,9,15,12,4,8,31,1,12,3,4
思考:
第一步:按照最小加工时间 ,先从小到大排序(思考:为什么按照最小加工时间呢? 如果不是按照最小时间从小到大,举个例子,如果有一个时间为1,权重为15的没有在最开始的时候加工,那么它的加权时间一定要大于其在最开始完成的加权时间,(因为1是最小的时间片段,所以最开始加工的话不会耽误更多的时间。)那么这样肯定就不会让总的加权时间和最小了。)
第二步:
求状态转移方程
minTime[] 存放每一步得到的最小的加权时间和
finish[] 存放每一步得到的结束时间
N=1时,p:1 ,w:2; p1 time(1)=w1*p1 = 2 c(1)=1
N=2,p:1,2 w:2,2 time(2)=min{time(1)+[c(1)+p2]*w2 ,p2*(w1+w2)} = min{8,8} = 8//如果相同,取第二种,因为第二种前面用的总时间要少 c(2)=2
N=3,p:1,2,2 w:2,2,2 time(3) = min{time(2)+[c(2)+p3]*w3 ,time(1)+[c1+p3]*(w2+w3),p3*(w1+w2+w3)} = min{16,14,12} = 12 c(3)= 2
N=n, time(n)=min{time(n-1)+[c(n-1)+pn]*wn,time(n-2)+[c(n-2)+pn]*[w(n-1)+wn],……,time(1)+[c1+pn]*(wn+w(n-1)+……+w2),pn*(w1+w2+w3+…+wn)}
代码如下:
package test;
import java.util.Stack;
/*
* 思路
* 1.按照最小加工时间从小到大排序
* 2.找到状态转移方程
*/
public class DP_Schedule {
static int [] time ={1,5,2,4,5,9,5,10,5,6,6,7,8,7,11,6,6,7,11,10,7,12,
8,8,8,6,5,13,14,9,8,9,3,3,11,2,9,13,4,6,4,4,8,9,6,15,5,7,9,3};
static int [] weight = {2,3,2,5,4,4,6,8,5,7,7,4,6,9,12,4,4,3,5,5,3,6,
9,5,9,12,3,7,12,11,11,3,4,5,3,12,2,6,6,4,9,15,12,4,8,31,1,12,3,4};
// static int [] index=new int[time.length];
static job [] jobs = new job[time.length];
static int finish[] =new int[jobs.length]; //记录有i个工件时完成时候的时间
static int minTime[] =new int[jobs.length]; //记录有i个工件时需要的最小时间
static int index [] = new int[jobs.length]; //记录第i个工件那批的上一批的最后一个的下标(排好序之后的下标)
public static void main(String[] args) {
// TODO Auto-generated method stub
job [] jobs = generate(time,weight);
sort(jobs);
System.out.println();
// for(int i=0;i<index.length;i++){
// System.out.print(index[i]+" ");
// }
DPsolve();
divide(index,jobs.length-1);
// for(int i=0;i<index.length;i++){
// System.out.print(index[i]+" ");
// }
// System.out.println();
for(int i=0;i<minTime.length;i++){
System.out.print(minTime[i]+" ");
}
System.out.println();
// for(int i=0;i<finish.length;i++){
// System.out.print(finish[i]+" ");
// }
System.out.println(minTime[jobs.length-1]);
}
//动态规划算法求解问题
public static void DPsolve(){
minTime[0] = jobs[0].time * jobs[0].weight;
finish[0] = jobs[0].time;
index[0] = 0;
for(int i = 1;i<jobs.length;i++){
int w = jobs[i].weight;
//第一个 初始化
minTime[i] = minTime[i-1] + (finish[i-1]+jobs[i].time)*w;
index[i] = i-1;
finish[i] = finish[i-1] + jobs[i].time;
//合并两个工件以上 一直到 合并i-1个工件
for(int j=1;j<i;j++){
w = w + jobs[i-j].weight;
int min = minTime[i-1-j] + (finish[i-1-j]+jobs[i].time)*w;
if(min < minTime[i]){
minTime[i] = min;
index[i] = i-1-j;
finish[i] = finish[i-1-j]+jobs[i].time;
}
}
//全部放在一起的情况
w = w+jobs[0].weight;
int m = w*jobs[i].time;
if(m<minTime[i]){
minTime[i] = m;
index[i] = 0;
finish[i] = jobs[i].time;
}
}
}
//逐步寻找这些批的分割的那一个下标
public static void divide(int [] arr,int n){
Stack<Integer> s =new Stack<Integer>();
//用一个栈循环将这些工件的每一批的最后一个的下标加入,就会找到分批的位置
s.push(n);
while(arr[n] != 0){
s.push(arr[n]);
// System.out.println(arr[n]);
n = arr[n];
// System.out.println(arr[n]);
}
int groupNum = 1,count = 1;
int start = 0; //输出的位置
while(!s.empty()){
// System.out.println(s.size());
int len = s.pop();
// System.out.println(len);
System.out.println("**********************************************************************");
System.out.println("第 "+groupNum+" 批如下:");
for(int i =start;i<=len ;i++){
System.out.println("第 "+jobs[i].index +" 个工件: 所需时间: "+jobs[i].time+" 权重: "+jobs[i].weight+" "+count++);
}
start = len+1; //下一次从len这个位置开始输出
groupNum++; //批次加一
}
}
//计算最小值的选择表达式
// public static void cal(int n){
// int
// }
//根据输入的时间、权重两个数组建立一个job的数组
public static job [] generate(int time[] ,int weight[]){
for(int i = 0;i<time.length;i++){
jobs[i] = new job(time[i],weight[i],i+1);
}
return jobs;
}
//排序
public static void sort( job [] arr){
int temp = 0;
job jb =new job(1,1,1);
//当对time排序时,记录下标的位置变换,对arr进行同样的下标变换
for(int i = 0 ;i<time.length;i++){
for(int j = 0;j<time.length - i - 1;j++){
if(time[j] > time[j+1]){
temp = time [j+1];
time[j+1] = time[j];
time[j] = temp;
jb = arr[j+1];
arr[j+1] = arr[j];
arr[j] = jb;
}
}
}
for(int i=0;i<arr.length;i++){
System.out.print(arr[i].time+" ");
}
System.out.println();
for(int i=0;i<arr.length;i++){
System.out.print(arr[i].weight+" ");
}
System.out.println();
for(int i=0;i<arr.length;i++){
System.out.print(arr[i].index+" ");
}
}
// public static void
static class job{
int time;
int weight;
int index;
public job(int time ,int weight,int index){
this.time = time;
this.weight = weight;
this.index = index;
}
}
}
仅仅是我自己的见解,如果有什么地方不对或者可以改进的地方,欢迎大家提出来~~~
分割线
<hr />
经过同学的讨论与建议,我发现上面的算法是错误的,少算了一种情况,
举个例子:
比如前面有两个零件,最优解是两个零件分开来做,我在加入第三个的时候,没有考虑下面这种情况:把前两个零件合起来作为一批然后第三个作为一批。这种情况必须要考虑,因为前两个零件合起来的话结束时间是要比两个分开来做小的,尽管对于只有前面两个零件时的情况两个分开做是最优。下面是我改进后的代码:
public class DP_Schedule {
static int [] time ={1,5,2,4,5,9,5,10,5,6,6,7,8,7,11,6,6,7,11,10,7,12,
8,8,8,6,5,13,14,9,8,9,3,3,11,2,9,13,4,6,4,4,8,9,6,15,5,7,9,3};
static int [] weight = {2,3,2,5,4,4,6,8,5,7,7,4,6,9,12,4,4,3,5,5,3,6,
9,5,9,12,3,7,12,11,11,3,4,5,3,12,2,6,6,4,9,15,12,4,8,31,1,12,3,4};
static job [] jobs = new job[time.length];
static int [][] res =new int[jobs.length][jobs.length];
static int [][] fin = new int[jobs.length][jobs.length];
static int [][] pre=new int[jobs.length][jobs.length];
public static void main(String[] args) {
job [] jobs = generate(time,weight);
sort(jobs);
DPsolve2();
int [] result = findMIn(50-1);
System.out.println("*******************************************");
System.out.println(result[0]);
System.out.println("注意:批次的顺序是从下往上的");
divide2(pre, result[1], result[2]);
}
//往右走是0
//往下走是1
public static void DPsolve2(){
for(int i=0,weight=0;i<jobs.length;i++){
weight+=jobs[i].weight;
res[0][i] =jobs[i].time *weight;
pre[0][i]=0;
fin[0][i] =jobs[i].time;
}
pre[0][0]=-1; //设置第一个点的前面的节点为空,即把第一个点是来自的方向设置为-1
for(int i=1,time=0;i<jobs.length;i++){
time+=jobs[i].time;
res[i][0] =res[i-1][0] + time *jobs[i].weight;
pre[i][0]=1;
fin[i][0] =time;
}
for(int j=1;j<jobs.length;j++){
for(int k=1;k<jobs.length-j;k++){
if(res[j-1][k]<=res[j][k-1]){
res[j][k] = res[j-1][k]+(fin[j-1][k]+jobs[j+k].time)*jobs[j+k].weight;
pre[j][k] =1;
fin[j][k] =fin[j-1][k]+jobs[j+k].time;
}else{
int x=k-1;
int we =jobs[j+x].weight+jobs[j+k].weight;
while(pre[j][x]==0&&x>0){
x--;
we +=jobs[x+j].weight;
}
// we +=jobs[x+k].weight;
res[j][k] = res[j-1][x]+we*(jobs[j+k].time+fin[j-1][x]);
fin[j][k] = jobs[j+k].time+fin[j-1][x];
pre[j][k] =0;
}
}
}
System.out.println("**********************************");
for(int i=0;i<res.length;i++){
for(int j=0;j<res[0].length;j++){
System.out.print(res[i][j]+"\t");
}
System.out.println();
}
System.out.println("**********************************");
}
public static int[] findMIn(int n){
int [] ret = new int[3];
int min =res[0][n];
//System.out.println(min);
ret[0] =min;
for(int i=1;i<=n;i++){
if(res[i][n-i]<min){
min=res[i][n-i];
ret[0] = min;
ret[1] = i;
ret[2] = n-i;
}
}
System.out.println(ret[0]+"=========="+ret[1]+"--------------"+ret[2]);
return ret;
}
//根据动态规划2求解批次
public static void divide2(int [][] pre,int i,int j){
if(pre[i][j] ==0){
System.out.print(jobs[i+j].time+" ");
divide2(pre,i,j-1);
}else if(pre[i][j] ==1){
System.out.println(jobs[i+j].time+" ****第 "+(i+1)+"批****");
divide2(pre, i-1, j);
}else if(pre[i][j]==-1){
System.out.println(jobs[i+j].time+ " ****第1"+"批****");
return;
}
// while(pre[i][j]!= -1){
// System.out.print(jobs[i+j].index+" ");
// while(pre[i][j]==0){
// j--;
// System.out.print(jobs[i+j].index+" ");
// }
// System.out.println();
// i--;
// }
}
//根据输入的时间、权重两个数组建立一个job的数组
public static job [] generate(int time[] ,int weight[]){
for(int i = 0;i<time.length;i++){
jobs[i] = new job(time[i],weight[i],i+1);
}
return jobs;
}
//排序
public static void sort( job [] arr){
int temp = 0;
job jb =new job(1,1,1);
//当对time排序时,记录下标的位置变换,对arr进行同样的下标变换
for(int i = 0 ;i<time.length;i++){
for(int j = 0;j<time.length - i - 1;j++){
if(time[j] > time[j+1]){
temp = time [j+1];
time[j+1] = time[j];
time[j] = temp;
jb = arr[j+1];
arr[j+1] = arr[j];
arr[j] = jb;
}
}
}
for(int i=0;i<arr.length;i++){
System.out.print(arr[i].time+"\t");
}
System.out.println();
for(int i=0;i<arr.length;i++){
System.out.print(arr[i].weight+"\t");
}
System.out.println();
for(int i=0;i<arr.length;i++){
System.out.print(arr[i].index+"\t");
}
System.out.println();
}
static class job{
int time;
int weight;
int index;
public job(int time ,int weight,int index){
this.time = time;
this.weight = weight;
this.index = index;
}
}
}
上图省略了输出的一些数组的信息。