1.问题分析
有n个机器零件{j1,j2,j3,……,jn},每个零件必须先由机器1处理,再由机器2处理。零件ji需要机器1、机器2处理的时间为t1i,t2i。如何安排零件加工顺序使第一个零件从机器1上加工开始到最后一个零件在机器2上加工完成,所需的总加工时间最短?
根据问题的描述,不同的加工顺序,加工完所有零件所需要的时间肯定不同。
例如:现在有三个机器零件j1,j2,j3
在机器1加工时间是:2,5,4
在机器2加工时间是:3,1,6。
(1)如果按照{j1,j2,j3}的顺序加工,如图所示:
(2)如果按照{j1,j2,j3}的顺序加工,如图所示:
我们可以看到,第一台机器可以连续加工,而第二台机器开始加工的时间是当前第一台机器的下线时间和第二台机器下线时间的最大值。
3个机器零件有3的全排列种可能性的排列:
1 2 3
1 3 2
2 1 3
2 3 1
3 2 1
3 1 2
我们要找的就是其中一个加工顺序,使第一个零件从机器1上加工到最后一个零件在机器2上加工完成所需要的总加工时间最短。
实际上就是找到n个零件的一个全排列,使得总加工时间最短。那么每个排列都是一个可行解,解空间是一颗排列树。
例如3个零件的解空间树如图所示:
从根到叶子结点的路径就是机器零件的一个加工顺序。
现在已经知道这个解空间是一个排列树,排列树种从根到叶子结点都是一个可行解,但不一定是最优解,如何得到最优解呢?这就需要我们在搜索排列树的时候,定义限界函数得到最优解。
2.算法设计
(1)定义问题的解空间。
机器零件加工问题解的形式是n元组:{x1,x2,x3,…,xi,…,xn}。分量xi表示第i个零件号,n个零件组成的集合是S={1,2,3,……,n}。xi的取值是S-{x1,x2,x3,…,x(i-1)}。
(2)解空间的组织结构。
是一棵排列树,例子如上图所示。
(3)搜索空间
a.约束条件:
由于任何一种零件加工次序不存在无法调度的情况,都是合法的。因此任何一个排列都表述一个可行解。
b.限界条件(剪枝函数):
用 f2 表示当前已经完成的零件在第二台机器加工结束所需要的时间,用 bestf 表示当前找到的最优加工方案的完成时间。显然,继续向深处搜索时,f2不会减少,只会增加。因此当 f2 ≥ bestf 时,没有继续向深处搜索的必要。限界条件可以描述为:f2<bestf,f2初值是0,bestf的初值是无穷大。
c.搜索过程:
扩展结点沿着某个分支扩展时,需要判断限界条件,如果满足,则进入深一层继续搜索;如果不满足,则剪掉该分支。搜索到叶子节点时,即找到最优解。搜索直到全部的活结点变成死结点为止。
3.伪代码
(1)数据结构:
我们用一个结构体node来存储机器零件在第一台机器上的加工时间x和第二胎机器上的加工时间y。定义一个这样的结构体数组T[ ]来存储所有的机器零件的加工时间。
例如第三个机器零件在第一台机器上的加工时间是5,在第二台机器上加工时间是2,那么T[3].x=5,T[3].y=2。
struct node { int x;//在第一台机器加工的时间 int y;//在第二台机器加工的时间 }T[MX]; //结构体数组存储所有机器加工时间。
(2)按限界条件搜索求解:
t表示当前扩展结点在t层。f1表示当前第一台机器上加工的完成时间,f2表示当前第二台机器上加工的完成时间。
如果t>n,表示已经到达叶子结点,记录最优值和最优解,返回。否则,分别判断每个分支是否满足约束条件,如果满足则进入下一层backtrack(t+1);如果不满足则反操作复位,考查下一个分支。
void backtrack(int t) { if(t>n) { for(int i=1;i<=n;i++) { bestx[i]=x[i];//记录最优排列 } bestf=f2; //更新最优值. return ; } for(int i=t;i<=n;i++) //枚举 { f1+=T[x[i]].x; int temp=f2; f2=max(f1,f2)+T[x[i]].y; if(f2<bestf) //限界条件(剪枝函数) { swap(x[t],x[i]); backtrack(t+1);//继续深搜 swap(x[t],x[i]);//复位,反操作 } f1-=T[x[i]].x; f2=temp; //复位,反操作。 } }
4.源代码
#include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <cmath> using namespace std; const int INF=999999999; const int MX=11111; int n; //机器零件的个数 int bestf; //最优加工零件时间 int f1; //在机器1加工的时间 int f2; //在机器2加工的时间 int x[MX]; //记录最优方案零件的编号 int bestx[MX]; //记录最优方案的加工顺序 struct node { int x;//在第一台机器加工的时间 int y;//在第二台机器加工的时间 }T[MX]; //结构体数组存储所有机器加工时间。 void backtrack(int t) { if(t>n) { for(int i=1;i<=n;i++) { bestx[i]=x[i];//记录最优排列 } bestf=f2; //更新最优值. return ; } for(int i=t;i<=n;i++) //枚举 { f1+=T[x[i]].x; int temp=f2; f2=max(f1,f2)+T[x[i]].y; if(f2<bestf) //限界条件(剪枝函数) { swap(x[t],x[i]); backtrack(t+1);//继续深搜 swap(x[t],x[i]);//复位,反操作 } f1-=T[x[i]].x; f2=temp; //复位,反操作。 } } int main() { cout << "请输入零件的个数n"<< endl; cin >> n; cout << "请依次输出每个零件在第一台机器加工的时间x和在第二台机器加工的时间y"<< endl; for (int i=1;i<=n;i++) { cin >> T[i].x >> T[i].y; x[i]=i; //零件的编号 } bestf=INF; //赋予∞ f1=0; f2=0; memset(bestx,0,sizeof(bestx)); //清空数组 memset(x,0,sizeof(x)); backtrack(1); //从排列树第一个结点开始搜索 cout << "最优的机器零件加工顺序为:"<< endl; for (int i=1;i<=n;i++) { cout << bestx[i] <<" "; } cout << endl; cout << "最优的机器零件加工的时间为:"<< endl; cout << bestf << endl; return 0; }