回溯算法 --- 例题2.批处理作业调度问题
一.问题描述
给定n个作业的集合J=(J1, J2, ... , Jn)。每一作业Ji都有两项
任务要分别在2台机器上完成. 每一作业须先由机器l处理, 再由机器2处理. 设tji是作业Ji在机器j上的处理时间, i=1,...,n, j=1, 2.Fji是作业Ji在机器j上完成处理的时间. 所有作业在机器2上完成时间和: f=∑F2i 称为该作业调度的完成时间和.
对于给定的J, 要求制定一个最佳作业调度方案, 使完成时间和最小.
二.解题思路
批处理作业调度问题要从n个作业的所有排列中找出有最小完成时间和的作业调度,所以批处理作业调度问题的解空间是一个排列树.按照回溯法搜发排列数的算法框架,设开始时x = [1,2,...,n]是所给的n个作业,则相应的排列树由x[1:n]的所有排列构成.
设解为n元向量{x1,... ,xn }, xi∈{1,..n}, 用排序树表示解空间
约束条件: 当i ≠ j , xi ≠ xj (元素不能重复选取)
限界函数:
设bestf:为当前最小时间和 f : 当前扩展结点的时间和;
- 当 f >= bestf 时, 将f对应左子树剪去
具体代码如下:
// 批处理作业调度问题
#include<bits/stdc++.h>
using namespace std;
class Flowshop
{
friend int Flow(int **, int);
private:
void Backtrack(int i);
void Show();
int **M, //各作业所需的处理时间
*x, //当前作业调度
*bestx, //当前最优作业调度
*f2, //机器2完成处理时间
f1, //机器1完成处理时间
f, //当前完成时间和
bestf, //当前最优值
n; //作业数
};
void Flowshop::Backtrack(int i)
{
static int k = 1;
if(i > n) //能够到达叶结点的,都是已经经过限界函数的考验,所以可以直接更新答案
{
for(int j=1; j<=n; j++)
{
bestx[j] = x[j];
cout<<x[j]<<" ";
}
bestf = f;
cout<<"第"<<k++<<"可行解"<<endl;
}
else
{
for(int j=i; j<=n; j++)
{
cout<<"当前元素为"<<x[j]<<" ";
f1 += M[x[j]][1]; //f1直接相加
f2[i] = (f2[i-1]>f1 ? f2[i-1]:f1) + M[x[j]][2]; //f2需要考虑是f1大,还是f2[i-1]大 参考图如上
f += f2[i]; //作业调度的完成时间和f的定义,是 Σf2[i], 1<=j<=n
if(f < bestf) //如果当前完成时间和f小于最优值,可以往下深入一层,否则直接剪枝
{
swap(x[i], x[j]);
cout<<"递归深入一层,将到达第"<<i+1<<"层"<<endl;
Backtrack(i+1); //递归下一层
cout<<"递归回退一层,将到达第"<<i<<"层"<<endl;
swap(x[i], x[j]);
}
f1 -= M[x[j]][1]; //递归回来需要撤销操作
f -= f2[i];
}
}
}
void Flowshop::Show()
{
cout<<"最小完成时间和为: "<<bestf<<endl;
cout<<"最优批处理作业调度为: ";
for(int i=1; i<=n; i++) cout<<bestx[i]<<" ";
cout<<endl;
}
int Flow(int **M, int n)
{
int ub = INT_MAX;
Flowshop X;
X.x = new int[n+1];
X.f2 = new int[n+1];
X.M = M;
X.n = n;
X.bestx = new int[n+1];
X.bestf = ub;
X.f1 = 0;
X.f = 0;
for(int i=0; i<=n; i++)
X.f2[i] = 0, X.x[i] = i; //x表示编号
X.Backtrack(1);
X.Show();
delete[] X.x;
delete[] X.f2;
return X.bestf;
}
int main()
{
cout<<"请输入作业个数:";
int n;
while(cin>>n && n)
{
int **M = new int*[n+1];
for(int i=0; i<=n; i++) M[i] = new int[3];
int time1, time2;
for(int i=1; i<=n; i++)
{
cout<<"请输入作业"<<i<<"在机器1以及机器2上的工作时间:";
cin>>time1>>time2;
M[i][1] = time1;
M[i][2] = time2;
}
Flow(M, n);
}
system("pause");
return 0;
}
运行结果如下:
简要分析一下:
第四层即i=4时,已经满足i>n,所以得到一个可行解,这一层是没有实际节点的.
同样的剪枝函数的运用使得很多情况在第三层时已经不满足限界函数 f < bestf了,所以没有继续深入,直接剪枝.
通过上述运行结果我们可以构造出具体的排列树来,数据比较简单,所以看起来很直观.
我的个人看法就是,解决此类问题就是一个全排列的框架,只需要加一些处理函数,以及运用剪枝函数就好了.
构造出的排列树,大家可以自己画一画:
参考毕方明老师《算法设计与分析》课件.
欢迎大家访问个人博客网站---乔治的编程小屋,和我一起加油吧!