问题描述
题目分析
这道题目也是一个比较经典的回溯法问题,但是这个问题和我们之前说的装载问题
不同的是,这里面的每一种作业我们都要安排,而不是找其中的子集,而装载问题才是一个找子集的问题。所以根据回溯法框架里的内容,对于这种解空间中必须包含全部而不是找子集的情况,我们应该使用的解空间是排列树。结构如下图所示:
我们可以将这些序列看成是在x=(1,2,3)的基础上经过各种不同作业的两两交换而得到的不同调度方案。在回溯法的框架中我们也提到了,解空间为排列树的问题是通过不断交换来进行选择不同情况的,在回溯的时候还需要将二者交换回来。
在这里面我们需要声明一个f2的一维数组表示机器2完成的时间,f1表示机器1完成的时间,M是一个二维数组,表示所有作业的调度时间。f2之所以用数组表示,是因为我们其实在过程中f2存储的是机器1完成的时间+机器2完成的时间(因为机器1完成之后机器2才能完成,所以可以统一存储)。但是后一项作业的结束时间取决于上一项作业的结束时间,如果上一项还没完成,则机器2一定被占用着,当前工作也就不能完成,这都实现中的一些细节,等下我们会看到具体如何展开。可以看代码中的注释.
代码
#include <iostream>
using namespace std;
class Flowshop()
{
//参数是作业调度时间表,作业数和当前的做优调度
friend Flow(int **,int,int []);
private:
void Backtrack(int i);//参数是现在要执行的作业次序,不是具体的哪个作业
int **M,//各作业所需的处理时间
int *x,//作业的调度
int *bestx,//当前的最优作业调度
int *f2,//机器2完成处理时间,这里之所以声明为数组是因为f2和i有关系,我们会记录下某一工作结束后的完成时间储存在f2[i]里
int f1,//机器1完成处理时间
int f,//完成时间和
int bestf,//当前最优值,
int n;//作业数
};
void Flowshop::Backtrack(int i)
{
if(i > n)
//到达了叶节点,我们需要更新当前最优调度和最优值
{
for(int j = 1;j < n;j++)
{
bestx[j] = x[j];
}
bestf = f;
}
else
{
//这里的重点是这个j
//j表示的是具体的工作,而i是作业分量,即我们现在正在要做第i个作业,于是我们选择作业j作为第i个要进行的作业
for(int j = i;j < n;j++)
{
//机器1完成时间我们只需要简单进行加和
f1 += M[x[j]][1];
//机器2完成时间取决于上一个工作的完成时间,i-1不完成我们无法完成i
f2[i] = ((f2[i-1] > f1) ? f2[i-1] : f1) + M[x[j]][2];
f += f2[i];
if(f < bestf)
{
swap(x[i],x[j]);
Backtrack(i + 1);
swap(x[i],x[j]);
}
f1 -= M[x[j]][1];
f -= f2[i];
}
}
}
int Flow(int **M,int n,int bestx[])
{
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 = bestx;
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.Backtrack(1);
delete[] X.x;
delete[] X.f2;
return X.bestf;
}
总结
由于这是一个排列树解空间,所以我们的时间复杂应该为O(n!)
(排列组合问题)