算法:流水作业调度

问题:

n个作业 N={1,2,…,n}要在2台机器M1和M2组成的流水线上完成加工。每个作业须先在M1上加工,然后在M2上加工。M1和M2加工作业 i 所需的时间分别为 ai 和bi,每台机器同一时间最多只能执行一个作业。

流水作业调度问题要求确定这n个作业的最优加工顺序,使得所有作业在两台机器上都加工完成所需最少时间。最优调度应该是:

  1. 使M1上的加工是无间断的。即M1上的加工时间是所有ai之和,但M2上不一定是bi之和。

  2. 使作业在两台机器上的加工次序是完全相同的。
    算法:流水作业调度_第1张图片
    则得结论:仅需考虑在两台机上加工次序完全相同的调度。
    最优子解结构
    在这里插入图片描述
    机器M1开始加工S中作业时,机器M2还在加工其他作业,要等时间 t 后才可利用,则:

1. 则完成S中作业所需的最短时间记为T(S,t)

2. 完成所有作业所需的最短时间为T(N,0)

3. T(N,0)=min{ai + T(N-{i}, bi)}, i∈N。

ai:选一个作业i先加工,在M1的加工时间。

T(N-{i},bi}:剩下的作业要等bi时间后才能在M2上加工。注意这里函数的定义,因为一开始工作i是随机取的,M1加工完了ai之后,要开始加工bi了,这里M1是空闲的可以开始加工剩下的N-i个作业了,但此时M2开始加工bi,所以要等bi时间之后才能重新利用,对应到上面函数T(s,t)的定义的话,这里就应该表示成T(N-{i},bi), 所以最优解可表示为T(N,0)=min{ai + T(N-{i}, bi)}, i∈N,即我们要枚举所有的工作i,使这个式子取到最小值。这里顺便吐槽一句:算法中会利用很多数学知识,一定要搞清楚函数的意义以及每个数学符号所代表的含义,这样不至于各种懵比。继续分析T(S,t)可得:

T(S,t)={ai + T(S-{i}, bi+max{t-ai,0})}, i∈S

其中:T(S-{i}, bi+max{t-ai,0}):剩下的作业等bi+max{t-ai,0}才能在M2加工,至于这里是怎么推导出来的呢?见下面推导:
   算法:流水作业调度_第2张图片
   最优子结构的证明(问题最优解包括子问题最优解):

最优子结构:设π是N的一个最优调度,其加工顺序为π1,…, πn,其所需的加工时间为 aπ1+T’(即第一个作业π1在M1上加工的时间和其它的加工时间)。记S=N-{π1},则T’=T(S, bπ1)。

证明:由T的定义知T(S, bπ1)是对S最优的,故T’>=T(S, bπ1)。若T’>T(S, bπ1),设π’是作业集S在机器M2的等待时间为bπ1情况下的一个最优调度。则π1,π’2, …,π’n是N的一个调度,且该调度所需的时间为aπ1+T’ > aπ1+T(S, bπ1)。这与π是N的最优调度矛盾。故T’<=T(S, bπ1), 从而T’=T(S, bπ1)。最优子结构的性质得证。

分析:

这段证明我开始有点云里雾里的,简单来说就是要证明问题的最优解包含子问题的最优解就行了,那么这里的证明思路是先假设一个最优调度,对于他的子调度T’,因为T(S,t)被定义为是完成S中作业所需的最短时间记为T(S,t),所以有T’>=T(S, bπ1),那么如果这个子调度这里不是最优解的话即T’>T(S, bπ1),会得出aπ1+T’ > aπ1+T(S, bπ1)即原来假设的最优调度不符和最优调度的标准,矛盾,从而推出 T’是一定等于T(S, bπ1),即这个子调度也是最优调度。

问题是:虽然满足最优子结构性质,也在一定程度满足子问题重叠性质。N的每个非空子集都计算一次,共2n-1次,指数级的。
算法:流水作业调度_第3张图片
为了解决这个问题引入Johnson不等式:
算法:流水作业调度_第4张图片
算法:流水作业调度_第5张图片
算法:流水作业调度_第6张图片

结论:先把所有作业的ai和bi放在一起,
从这之中选个最小的,
如果是bi的话这个作业i就放最后
,如果是ai的话这个作业就放最前,
把这个已经安排好的作业从作业集中删除。
重复上述步骤即可。

源代码:

#include
using namespace std;
const int N = 5;

class Jobtype
{
	public:
		int key,index;//key为机器所用时间,index为作业序号
		bool job;//job为ture表示M1,false表示M2
		inline bool operator <(const Jobtype &a) const //重载 <=
		{
			return(key<a.key);
		}
};

int FlowShop(int n,int a[],int b[],int c[]);

int main()
{
	int a[] = {2,4,3,6,1};
	int b[] = {5,2,3,1,7};
	int c[N];

	int minTime =  FlowShop(N,a,b,c);

	cout<<"作业在机器1上的运行时间为:"<<endl;
	for(int i=0; i<N; i++)
	{
		cout<<a[i]<<" ";
	}
	cout<<endl;
	cout<<"作业在机器2上的运行时间为:"<<endl;
	for(int i=0; i<N; i++)
	{
		cout<<b[i]<<" ";
	}
	cout<<endl;

	cout<<"完成作业的最短时间为:"<<minTime<<endl;
	cout<<"编号从0开始,作业调度的顺序为:"<<endl;
	for(int i=0; i<N; i++)
	{
		cout<<c[i]<<" ";
	}
	cout<<endl;
	return 0;
}

int FlowShop(int n,int a[],int b[],int c[])
{
	Jobtype *d = new Jobtype[n];
	for(int i=0; i<n; i++)
	{
		d[i].key = a[i]>b[i]?b[i]:a[i];//按Johnson法则分别取对应的b[i]或a[i]值作为关键字
		//找作业在两台机器上处理时间小的那个作业
		d[i].job = a[i]<=b[i];//给符合条件a[i]
		d[i].index = i;
	}

	sort(d,d+n);//对数组d按关键字升序进行排序 快排

	int j = 0,k = n-1;

	for(int i=0; i<n; i++)
	{
		if(d[i].job)//N1集合,ai<=bi
		{
			c[j++] = d[i].index;//将排过序的数组d,取其中作业序号属于N1的从前面进入
		}
		else//N2集合,ai>bi
		{
			c[k--] = d[i].index;//属于N2的从后面进入,从而实现N1的非减序排序,N2的非增序排序
		}
	}

	j = a[c[0]];//第一个作业在M1上的处理时间
	k = j+b[c[0]];//第一个作业处理完所需时间
	for(int i=1; i<n; i++)
	{
		j += a[c[i]];//M1在执行c[i]作业的同时,M2在执行c[i-1]号作业,最短执行时间取决于M1与M2谁后执行完
		k = j<k?k+b[c[i]]:j+b[c[i]];//计算最优加工时间
	}

	delete d;
	return k;
}


运行效果:
算法:流水作业调度_第7张图片

你可能感兴趣的:(算法,C++)