回溯法之批处理作业调度
1. 问题描述
n个作业集合{1, 2, ..., n}。每个作业先由机器1处理,再由机器2处理。作业i需要机器j处理的时间为Mij 。
Mij
机器1
机器2
作业1
2
1
作业2
3
1
作业3
2
3
对于一个确定的作业调度,设Fij 是作业i在机器j上完成的具体时间。所有作业在机器2上完成的具体时间(时刻)之和f称为该作业调度的完成时间和。
要求:对于给定的n个作业,制定最佳作业调度方案(一个排列),使其完成时间和达到最小
2. 问题分析
3个作业的6种可能的调度方案是1-2-3;1-3-2;2-1-3;2-3-1;3-1-2;3-2-1;它们所相应的完成时间和分别是19,18,20,21,19,19。易见,最佳调度方案是1-3-2,其完成时间和为18。
Fi1、Fi2表示事件i分别在机器1和机器2上完成的时间。
作业顺序
Fi1
Fi2
f
begin
0
0
0
①
2
3
3
③
4
7
10
②
7
8
18
假设此时1-2-3的作业调度已经完成计算,遍历到了1-3-2的序列。
首先是①,将调用f1 += M[x[j]][1];表示作业1在机器1需要2时间才能完成,接下来使用f2[i] = ((f2[i - 1] > f1) ? f2[i - 1] : f1) + M[x[j]][2];比较F02 和 F11 的大小,即需要等待前一个作业在机器2完成计算才能在机器2开始本次作业,再使用F02 和 F11的较大值F11+作业2在机器2的工作时间1得到F12为3,在使用f += f2[i],将3赋值给f。
到③,调用f1 += M[x[j]][1];,F21变为2+2=4,再比较F12和F21,将较大的F21+3=7赋值给F22。f变为10。
到②,F31变为7,比较F22和F31,将7+1=8复制给F32,最终将f变为18,得到最终结果。
3. 代码求解
该问题的解空间:排列树:
使用到的全局变量:
M 各作业所需的处理时间
x 当前作业调度
bestx 当前最优作业调度
f2 机器2完成的处理时间
f1 机器1完成的处理时间
f 完成时间和
bestf 当前最优值
n 作业数
核心求解代码:
void BackTrack(int i) {
// i > n表示已经访问到叶子结点,即一个作业调度遍历完成
// 将此时的最优解暂时保存到bestx中
if (i > n) {
for (int j = 1; j <= n; j++) {
bestx[j] = x[j];
bestf = f;
}
} else {
// 若没找到此时的最优解,则对当前的求解树的子节点遍历求解
for (int j = i; j <= n; j++) {
// 上述的问题求解过程
f1 += M[x[j]][1];
f2[i] = ((f2[i - 1] > f1) ? f2[i - 1] : f1) + M[x[j]][2];
f += f2[i];
// 如果f小于当前的最小值,代表这个分支可以继续运算,否则直接裁去该分支,达到优化算法的目的
if (f < bestf) {
swap(i, j);
// 递归求解
BackTrack(i + 1);
// 递归运算后,需要回退到上一层排列树
swap(i, j);
}
// 本条路径求解结束后回退到原本的状态
f1 -= M[x[j]][1];
f -= f2[i];
}
}
}
4. 完整代码
/**
* 回溯法求解批处理作业调度问题
*
* 主要由BackTrack函数递归调用求解问题
* 使用bsetf保存最优解,bestx保存最优的作业调度序列
**/
#include
#include
#define MAXROW 4
#define MAXCOL 3
/**
* M 各作业所需的处理时间
* x 当前作业调度
* bestx 当前最优作业调度
* f2 机器2完成的处理时间
* f1 机器1完成的处理时间
* f 完成时间和
* bestf 当前最优值
* n 作业数
**/
int M[MAXROW][MAXCOL], x[MAXROW], bestx[MAXROW], f2[MAXROW], f1, f, bestf, n;
// 值交换函数
void swap(int i, int j) {
int tmp = x[i];
x[i] = x[j];
x[j] = tmp;
}
void BackTrack(int i) {
// i > n表示已经访问到叶子结点,即一个作业调度遍历完成
// 将此时的最优解暂时保存到bestx中
if (i > n) {
for (int j = 1; j <= n; j++) {
bestx[j] = x[j];
bestf = f;
}
} else {
// 若没找到此时的最优解,则对当前的求解树的子节点遍历求解
for (int j = i; j <= n; j++) {
f1 += M[x[j]][1];
f2[i] = ((f2[i - 1] > f1) ? f2[i - 1] : f1) + M[x[j]][2];
f += f2[i];
if (f < bestf) {
swap(i, j);
BackTrack(i + 1);
swap(i, j);
}
f1 -= M[x[j]][1];
f -= f2[i];
}
}
}
void main() {
n = 3;
bestf = INT_MAX;
M[1][1] = 2;M[1][2] = 1;
M[2][1] = 3;M[2][2] = 1;
M[3][1] = 2;M[3][2] = 3;
for (int i = 0; i <= n; i++) {
f2[i] = 0;
x[i] = i;
}
BackTrack(1);
printf("%d\n", bestf);
for (int i = 1; i <= n; i++) {
printf("%d ", bestx[i]);
if (i != n)
printf("-> ");
}
printf("\n");
system("pause");
}