【算法设计与分析】流水作业调度问题 动态规划算法与代码实现 超详细

题目描述

“加工顺序问题”又被称为“批处理作业调度问题”。

设有n个工件需要在机器M1和M2上加工,每个工件的加工顺序都是先在M1上加工,然后在M2上加工。t1jt2j分别表示工件j在M1,M2上所需的加工时间(j=1,2,···,n)

问:应如何在两机器上安排生产,使得第一个工件从在M1上加工开始到最后一个工件在M2上加工完所需的总加工时间最短?关于此(类)问题的回溯法求解被作为经典案例在很多教材或参考文献中出现,现要求设计求解此问题的动态规划算法

请用数学语言对“加工顺序问题”加以抽象,在此基础上给出动态规划求解该问题的递归公式。要求对所给公式中的符号意义加以详细说明,并简述算法求解步骤。用一种你熟悉的程序设计语言加以实现。

动态规划思路

流水作业调度问题要求确定这n个作业的最优加工顺序,使得从第一个作业在机器M1上开始加工,到最后一个作业在机器M2上加工完成所需的时间最少。

直观上,一个最优调度应使机器M1没有空闲时间,且机器M2的空闲时间最少。在一般情况下,机器M2上会有机器空闲和作业积压两种情况。

设全部作业的集合为N={1,2,...,n}S⊆N,S是N的一个子集。在一般情况下,机器M1开始加工S中作业时,机器M2还在加工其他作业,要等时间t后才可利用。这种情况下,完成S中作业所需的最短时间记为T(S,t),流水作业调度问题的最优值为T(N,0)
在这里插入图片描述
证明最优子结构性质

因为公式手写比较方便,所以暂时使用手写,待转电子版。
在这里插入图片描述

递归计算最优值

由流水作业最优子结构的性质可知,全部作业集合N的最优调度时间T(N,0)计算方式如下:

  • 注意:作业i是从全部作业集合中任意抽取的,作业i被作为第一个加工的作业。

在这里插入图片描述
推广到一般情形下,便有递归函数:
在这里插入图片描述
max{t-ai,0} 这一项是由于:在作业i完成M1上的加工,该转到M2上加工的时候,M2的状态可能是空闲/占用

  • 如果作业i在M1上加工完毕后,M2是空闲的,则不需要等待,M2直接加工作业i即可。作业i占用M2的时间为bi+0
  • 如果作业i在M1上加工完毕后,M2仍然被之前的作业占用,则作业i在M2上需要等待的时间为t-ai。作业i占用M2的时间为bi+(t-ai)

上述递归函数的更详细的解释如下图:
在这里插入图片描述
为了便于理解,手动走一下递归↓(更完整的手动递归过程,见页面最下附录)
在这里插入图片描述
然后就是代码了。关于如何存储求解的小问题,避免重复运算,下面是思考过程。

脑洞大开过程↓

一开始,我想模仿矩阵连乘问题求解,用二维数组表示被加工的工件,横坐标表示工件的起点,纵坐标表示工件的终点。但是发现本问题中的工件不连续(5个工件就有32种不同的组合),无法用二维数组表示。

既然工件组合不连续,我又考虑模仿0-1背包问题的解法求解。0-1背包是建立一个二维数组,横坐标是背包的容量,纵坐标是可以装入的物品编号。然后从装入物品编号为0开始,将将背包容量从0开始,一点一点扩大,计算能装入的最大的物品价值。当可装入的物品编号为总物品个数的情况下,当背包容量达到实际容量时,能装入物品的最大价值就是整个问题的最优解。
但是流水作业调度问题和背包问题差别仍然很大。
1、交换0-1背包问题的放入顺序,最优解仍然是最优解,而交换流水作业的加工顺序,最优解可能就不是最优了。
2、0-1背包息壤得到装入的最大价值;流水作业希望得有最少的加工时间。
于是放弃模仿0-1背包问题。

考虑后发现,流水线作业调度问题具有两个特点:
1、子问题的工件不连续,可跳跃
2、一般情况下,交换两个工件的加工顺序后,总加工时间会改变。

又受到某五子棋人机对战算法的启发(此处省略五子棋算法),具体是将每种赢的状态用0和1表示出来(整个棋盘二维数组上只有5个连续的1,其余全是0,可以表示其中一种赢的状态)。

类比可得,本流水线调度问题也可以用0和1表示所有可能的工件集合(每一个工件被加工或不被加工的状态)。

比如说有5个工件,每一个工件都可能被加工或不被加工,因此共有2^5=32种状态(组合方式)

又考虑到在每种状态下,如果想要求最小加工时间,还要知道该状态下机器M2上的等待时间。于是建立一个类JiHe,实例化为JiHe[32],里面设置minTime[]属性,存储在当前组合方式下,等待时间为数组下标时的最短加工时间。

这样,就成功的存储了子问题的最优解。求解顺序就是:从加工零个工件开始填写minTime[],一直到加工完所有工件。

代码思路+详细运行过程

首先,我们假设有5个工件待加工,分别为:J0,J1,J2,J3,J4,J5
它们在两个机器上加工时间如下:
在这里插入图片描述
1、定义一个类GongJian,记录每个工件两个加工步骤分别需要的时间(程序运行时用户输入)。
在这里插入图片描述
输入时间后
在这里插入图片描述
2、定义一个类JiHe,表示一种状态(组合方式),表示有哪些工件被加工。在这里插入图片描述
用类似于二进制加一的方式,填写所有可能的工件组合。比如,5个工件有2^5=32种组合方式。
在这里插入图片描述
之后遍历32种工件组合JiHe[32],把每种组合下,被加工的总工件个数保存在jihe[i].num中,此操作便于后续根据总工件个数(0,1,2,3,4,5),按顺序进行自底向上计算。

3、自底向上,计算每个状态的最短时间

  • 先计算0个工件被加工时,等待时间为0,1,2,3,…,100时的最短加工时间
  • 再计算1个工件被加工时,等待时间为0,1,2,3,…,100时的最短加工时间
  • 最后计算5个工件被加工时,等待时间为0,1,2,3,…,100时的最短加工时间

5个工件被加工时,等待时间为0时的最短加工时间即为整个流水作业调度问题的解。
在这里插入图片描述
以下为逐步调试过程:

初始状态下,数组值都为0↓
在这里插入图片描述
0个工件被加工时,等待时间为0,1,2,3,…,100时的最短加工时间↓
在这里插入图片描述
1个工件被加工时,等待时间为0,1,2,3,…,100时的最短加工时间↓
在这里插入图片描述
2个工件被加工时,等待时间为0,1,2,3,…,100时的最短加工时间↓
在这里插入图片描述
3个工件被加工时,等待时间为0,1,2,3,…,100时的最短加工时间↓
在这里插入图片描述
4个工件被加工时,等待时间为0,1,2,3,…,100时的最短加工时间↓
在这里插入图片描述
5个工件被加工时,等待时间为0,1,2,3,…,100时的最短加工时间(至此,全部计算完成)↓
在这里插入图片描述
此时,jihe[31].minTime[0]存放的就是加工完全部工件所需的最短时间,也就是在加工5个工件,且M2等待时间为0时,加工所需要的最短时间(最优解)。

最后将此时间输出即可。

测试用例

注意:使用时,要在代码的宏定义中修改工件总数NUM,手动计算并填写POWNUM = 2^NUM
如下图所示,工件总数为6,2^6=64,因此宏定义NUM=6 POWNUM=64
在这里插入图片描述
输入1

2 5 7 3 6 2 4 7 6 9 8 2

输出1

最短时间:35

输入2

2 5 4 2 3 3 6 1 1 7

输出2

最短时间:19

运行效果

在这里插入图片描述

代码

工件总数根据宏定义可变,需要在运行前,填写宏定义的NUM POWNUM
各个工件在机器上的加工时间运行时由用户录入

#include<iostream>
#include<algorithm>
#include<math.h>
#define NUM 5		//工件总数
#define POWNUM 32	//2^NUM 状态总数

using namespace std;

//2 5 7 3 6 2 4 7 6 9 8 2
//答案:35

//2 5 4 2 3 3 6 1 1 7
//答案:19

class JiHe
{
public:
	int a[NUM];			//1:被加工 0:不被加工
	int num;			//当前状态下 被加工的工件个数
	int minTime[100];	//数组下标是等待时间t
};

class GongJian
{
public:
	int t1;				//该工件在 M1 上加工需要的时间
	int t2;				//该工件在 M2 上加工需要的时间
};

GongJian gongjian[NUM];
JiHe jihe[POWNUM];

//寻找最小时间的递归函数
int findMinTime(JiHe jihe, int t)
{
	if (jihe.num == 0)			//集合中无元素时 等待时间就是加工时间
	{
		return t;
	}

	int i;
	int curMinTime;
	int mintime = 1000;			//初始化巨大值
	JiHe withoutI = jihe;

	for (i = 0; i < NUM; i++)	//i放在第一个加工 循环找到最小值情况下的i 但是i没有被记录
	{
		withoutI = jihe;
		if (jihe.a[i] == 1)
		{
			withoutI.a[i] = 0;
			withoutI.num = jihe.num - 1;
			curMinTime = gongjian[i].t1 + findMinTime(withoutI, gongjian[i].t2 + max(t - gongjian[i].t1, 0));
			if (curMinTime < mintime)
			{
				mintime = curMinTime;
			}
		}
	}
	return mintime;
}


int main()
{
	//每个工件的时间状况
	cout << "请输入工件在机器M1 M2上的加工时间:\n";
	for (int i = 0; i < NUM; i++)
	{
		cout << "工件序号" << i << "的加工时间\n";
		cout << "t1 = ";
		cin >> gongjian[i].t1;
		cout << "t2 = ";
		cin >> gongjian[i].t2;
	}



	//填写每个工件在与不在的状态 32个
	int i;
	int x[NUM];
	for (int i = 0; i < NUM; i++)//用于二进制计数
	{
		x[i] = 1;
	}
	int num;
	int t, p;
	for (i = 0; i < POWNUM; i++)
	{
		//二进制计数 罗列所有工件组合
		for (t = NUM - 1, p = 0; t >= 0; t--, p++)
		{
			if ((i % (int)pow(2, t)) == 0)
			{
				x[p] = -x[p];
			}
		}
		/*if (i % 32 == 0)x[0] = -x[0];
		if (i % 16 == 0)x[1] = -x[1];
		if (i % 8 == 0)x[2] = -x[2];
		if (i % 4 == 0)x[3] = -x[3];
		if (i % 2 == 0)x[4] = -x[4];
		if (i % 1 == 0)x[5] = -x[5];*/

		for (t = 0; t < NUM; t++)//把-1改成0
		{
			jihe[i].a[t] = (x[t] > 0 ? x[t] : 0);
		}
		/*jihe[i].a[0] = (x[0] > 0 ? x[0] : 0);
		jihe[i].a[1] = (x[1] > 0 ? x[1] : 0);
		jihe[i].a[2] = (x[2] > 0 ? x[2] : 0);
		jihe[i].a[3] = (x[3] > 0 ? x[3] : 0);
		jihe[i].a[4] = (x[4] > 0 ? x[4] : 0);
		jihe[i].a[5] = (x[5] > 0 ? x[5] : 0);*/

		//填写当前状态下有几个工件被加工 num
		num = 0;
		for (int j = 0; j < NUM; j++)
		{
			if (jihe[i].a[j] == 1)num++;
		}
		jihe[i].num = num;
	}

	//自底向上 计算每个状态的最短时间
	int k;
	int workNum;
	for (workNum = 0; workNum <= NUM; workNum++)
	{
		// 0,1,2,...,workNum 个工件被加工
		for (i = 0; i < POWNUM; i++)
		{
			if (jihe[i].num == workNum)
			{
				//等待时间k
				for (k = 0; k < 100; k++)
				{
					jihe[i].minTime[k] = findMinTime(jihe[i], k);
				}
			}
		}
	}

	cout << "最短时间:" << jihe[POWNUM - 1].minTime[0] << endl;
	system("pause");
}

附:手动计算的部分调度结果

空:T({},x) = x 集合中无元素时 等待时间就是加工时间

作业编号	M1M2
1			2 5
2			4 2
3			3 3
4			6 1
5			1 7

T({1},0) = 2+T({},5) = 2+5 = 7
T({2},0) = 4+T({},2) = 4+2 = 6
T({3},0) = 3+T({},3) = 3+3 = 6
T({4},0) = 6+T({},1) = 6+1 = 7
T({5},0) = 1+T({},7) = 1+7 = 8

T({1},0) = 2+T({},5+(0-2) = 2+5 = 7
T({1},1) = 2+T({},5+(1-2) = 2+5 = 7
T({1},2) = 2+T({},5+(2-2) = 2+5 = 7
T({1},3) = 2+T({},5+(3-2) = 2+6 = 8
T({1},4) = 2+T({},5+(4-2) = 2+7 = 9
T({1},5) = 2+T({},5+(5-2) = 2+8 = 10

T({2},0) = 4+T({},2+(0-4) = 4+2 = 6
T({2},1) = 4+T({},2+(1-4) = 4+2 = 6
T({2},2) = 4+T({},2+(2-4) = 4+2 = 6
T({2},3) = 4+T({},2+(3-4) = 4+2 = 6
T({2},4) = 4+T({},2+(4-4) = 4+2 = 6
T({2},5) = 4+T({},2+(5-4) = 4+3 = 7
T({2},6) = 4+T({},2+(6-4) = 4+4 = 8

T({3},0) = 3+T({},3+(0-3) = 3+3 = 6
T({3},1) = 3+T({},3+(1-3) = 3+3 = 6
T({3},2) = 3+T({},3+(2-3) = 3+3 = 6
T({3},3) = 3+T({},3+(3-3) = 3+3 = 6
T({3},4) = 3+T({},3+(4-3) = 3+4 = 7
T({3},5) = 3+T({},3+(5-3) = 3+5 = 8
T({3},6) = 3+T({},3+(6-3) = 3+6 = 9
T({3},7) = 3+T({},3+(7-3) = 3+7 = 10
T({3},8) = 3+T({},3+(8-3) = 3+8 = 11

T({4},0) = 6+T({},1+(0-6) = 6+1 = 7
T({4},1) = 6+T({},1+(1-6) = 6+1 = 7
T({4},2) = 6+T({},1+(2-6) = 6+1 = 7
T({4},3) = 6+T({},1+(3-6) = 6+1 = 7
T({4},4) = 6+T({},1+(4-6) = 6+1 = 7
T({4},5) = 6+T({},1+(5-6) = 6+1 = 7
T({4},6) = 6+T({},1+(6-6) = 6+1 = 7
T({4},7) = 6+T({},1+(7-6) = 6+2 = 8
T({4},8) = 6+T({},1+(8-6) = 6+3 = 9

T({5},0) = 1+T({},7+(0-1) = 1+7 = 8
T({5},1) = 1+T({},7+(1-1) = 1+7 = 8
T({5},2) = 1+T({},7+(2-1) = 1+8 = 9
T({5},3) = 1+T({},7+(3-1) = 1+9 = 10
T({5},4) = 1+T({},7+(4-1) = 1+10 = 11
T({5},5) = 1+T({},7+(5-1) = 1+11 = 12
T({5},6) = 1+T({},7+(6-1) = 1+12 = 13

T({1,2},0) = min	i={1}: 2+T({2},5) = 2+7 = 9
					i={2}: 4+T({1},2) = 4+7 = 11
			= 9

T({1,2},3) = min	i={1}: 2+T({2},5+(3-2)) = 2+T({2},6) = 2+8 = 10
					i={2}: 4+T({1},2+(3-4)) = 4+T({1},2) = 4+7 = 11
			= 10
			
T({2,3},0) = min	i={2}: 4+T({3},2) = 4+6 = 10
					i={3}: 3+T({2},3) = 3+6 = 9
			= 9
			
T({3,4},0) = min	i={3}: 3+T({4},3) = 3+7 = 10
					i={4}: 6+T({3},1) = 6+6 = 12
			= 10

T({4,5},0) = min	i={4}: 6+T({5},1) = 6+8 = 14
					i={5}: 1+T({4},7) = 1+8 = 9
			= 10
			
T({2,3},5) = min	i={2}: 4+T({3},2+(5-4)) = 4+6 = 10
					i={3}: 3+T({2},3+(5-3)) = 3+7 = 10
			= 10	

T({1,3},2) = min	i={1}: 2+T({3},5+(5-2)) = 2+T({3},8) = 2+11=12
					i={3}: 3+T({1},3+(5-3)) = 3+T({1},5) = 3+10=13
			= 12			
			
			
T({1,2,3},0) = min	i={1}: 2+T({2,3},5) = 2+10 = 12
					i={2}: 4+T({1,3},2) = 4+12 = 16
					i={3}: 3+T({1,2},3) = 3+11 = 14
			= 12


评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值