算法设计——流水作业

描述:作业集合p={p[0],p[1],...,p[n-1]},每一项作业都有两个任务需要在两台机器上完成,且每一个作业p[i],都需要先经过机器1处理,再经过机器2处理。设作业p[i]经过机器1、2处理所需的时间分别为a[i]和b[i],这样时间集合为a={a[0],a[1],...,a[n-1]},b={b[0],b[1],...,b[n-1]}。求一个作业调度顺序,使作业集合p经过机器1,2处理所需的时间最短。


回溯法:

回溯法是很容易想到的方法,假设作业编号是0,1,2,那么调度顺序只有0,1,2;0,2,1;1,0,2;1,2,0;2,0,1;2,1,0这6种,分别对这些调度顺序求时间然后再取最小的就可以了,很明显这个解空间是一个排列树,如下:


对于迭代排列树的解空间,有一个代码框架:

void func (int *p, int n, int pos) {
	int i;
	if (pos >= n) {
		......
		return;
	}
	for (i = pos; i < n; ++i) {
		swap (p[pos], p[i]);
		func (p, n, pos+1);
		swap (p[pos], p[i]);
	}
}
其中p是序号数组,n是p数组长度,pos是函数当前处理到底基层,对应于上图中的层数。当pos>=n时,说明函数处理完最后一层,也就是在p数组中生成了一个排列。

有了上面迭代排列数解空间的代码,我们很容易想到一个思路,即每次在pos>=n时,我们利用p数组中生成的排列,计算这个排列情况下,作业集合经过机器1,2处理后的时间,与当前最小的时间比较,如果小于最小时间则替换。

这样只需要设计一个函数能够计算以p数组中序号为顺序,经过机器1,2处理时间分别为数组a={a[0],a[1],...,a[n-1]}和数组b={b[0],b[1],...,b[n-1]}的作业集合,所需要的时间即可,我设计函数如下:

int caculate (int *p, int *a, int *b, int n) {
	int sum = 0, dlt = 0, i;
	for (i = 0; i < n; ++i) {
		if (a[i]+dlt < 0) {
			sum += b[p[i]];
			dlt = dlt + a[p[i]] - b[p[i]];
		}
		else {
			sum += (dlt + a[p[i]] + b[p[i]]);
			dlt = 0 - b[p[i]];
		}
	}
	return sum;
}
其中p是序号数组,p[0],p[1],p[2]...分别表示排在第0,1,2位置的作业序号。a数组是第一台机器处理各作业的时间,b数组表示第二台机器处理各作业的时间,n表示数组的长度。

函数中有局部变量sum和dlt,sum处理的总时间,dlt是当第一台机器处理到某个作业i时,第二台机器还有多久才能处理完作业i之前的所有作业。

有了上面的函数我们带入到前面提到的遍历排列树解空间的代码框架中得到如下代码:

void flowshop (int *p, int *a, int *b, int n, int pos, int *sum, int *r) {
	int i, tmp;
	if (pos >= n) {
		tmp = caculate (p, a, b, n);
		if (*sum > tmp) {
			*sum = tmp;
			memcpy (r, p, sizeof(int)*n);
		}
		return;
	}
	for (i = pos; i < n; ++i) {
		swap (p[pos], p[i]);
		flowshop (p, a, b, n, pos+1, sum, r);
		swap (p[pos], p[i]);
	}
}
当然最容易想到的,往往都是性能最低的,上面的解法,处理了所有排列的时间,完全没有判断在处理某种排列时,某种排列的局部时间已经超过了当期的最少时间,这种排列的解空间分支应当剪掉。于是做了一下改进,在深度遍历解空间的过程中计算当前的时间,如果时间大于当前最小时间,就没有必要再继续往下遍历了(该函数不需要用到前面设计的caculate函数)。

void flowshop (int *p, int *a, int *b, int n, int pos, int cur, int *sum, int dlt, int *r) {
	int i;
	if (pos >= n) {          // 限界
		if (cur < *sum) {
			*sum = cur;
			memcpy (r, p, sizeof(int)*n);
		}
		return;
	}
	for (i = pos; i < n; ++i) {
		int tmpdlt, tmpcur;
		swap (p[pos], p[i]);
		if (dlt+a[p[pos]] < 0) {
			tmpcur = cur + b[p[pos]];
			tmpdlt = dlt + a[p[pos]] - b[p[pos]];
		}
		else {
			tmpcur = cur + dlt + a[p[pos]] + b[p[pos]];
			tmpdlt = 0 - b[p[pos]];
		}
		if (tmpcur < *sum) {  // 剪枝
			flowshop (p, a, b, n, pos+1, tmpcur, sum, tmpdlt, r);
		}
		swap (p[pos], p[i]);
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值