回溯法——批作业调度问题
问题:
给定n个作业的集合{J1,J2,…,Jn}。每个作业必须先由机器1处理,然后由机器2处理。所有作业在机器2上完成处理的时间和称为该作业调度的完成时间和。
批处理作业调度问题要求对于给定的n个作业,制定最佳作业调度方案(给出作业的加工顺序),使其完成时间和达到最小。
分析:
所有可能的解为各个任务的一个全排列,因此是一个子集树问题。约束函数没有明确的要求,限界函数为当前已经完成的工件的总的完成时刻和小于最优解。
关键是求出每种情况下对应的结果。工件i在机器2上的结束时间等于开始时间+做工时间,开始时间是i在机器1上的结束时间和i-1在机器2上的结束时间中较大的那个,由此就构成了递推计算式。
代码:
#include <bits/stdc++.h>
using namespace std;
#define MAXN 105
#define INF 0x3f3f3f
// 任务在机器1,2上耗费的时间
int F1[MAXN];
int F2[MAXN];
// 记录最优调度
int res[MAXN];
// 当前最优解
int bestnow = INF;
// 当前所完成的工件在机器1上的总的完成时间之和,只记录下当前的就可以了
int M1 = 0;
// 当前所完成的工件在机器2上的总的完成时刻之和,每个都记录下来方便计算
int M2[MAXN];
// 计算当前解
int sum(int pos){
int tmp = 0;
for(int i = 1;i <= pos;i++){
tmp += M2[i];
}
return tmp;
}
// 限界函数
// Mi1= M(i-1)1 + F1i; Mi2 = max{M(i-1)2, Mi1} + F2xi;
bool bound(int pos){
return sum(pos) < bestnow;
}
// 递归法计算最优调度
void arrangeTraceback(int pos,int n){
if(pos > n){
bestnow = sum(n);
}else{
for(int i = pos;i <= n;i++){
swap(res[pos],res[i]);
// 计算
M1 += F1[res[pos]];
M2[pos] = max(M2[pos-1],M1) + F2[res[pos]];
if(bound(pos)){
arrangeTraceback(pos+1,n);
}
// 恢复现场,只需恢复M1即可,M2不需要恢复,反正pos后面的也用不到
M1 -= F1[res[pos]];
swap(res[pos], res[i]);
}
}
}
int main(){
int n = 3;
memset(F1, 0, sizeof(F1));
memset(F2, 0, sizeof(F2));
memset(M2, 0, sizeof(M2));
for(int i = 0;i <= n;i++){
res[i] = i;
}
F1[1] = 2;
F1[2] = 3;
F1[3] = 2;
F2[1] = 1;
F2[2] = 1;
F2[3] = 3;
arrangeTraceback(1, n);
cout<<bestnow<<endl;
}
复杂度:
排列树,O(n!)