改进的作业排序--2021WMC上机系列

改进的作业排序

一、问题引出

要了解改进的作业排序是如何实现的,必须对作业排序本身的机制有着透彻的理解。 所以一开始,先给各位复习一遍周一上课讲过的作业排序。

假设:现在有一台计算机,它要处理n个作业,每一个作业只需要花单位时间就可以完成。但是,每一个作业都只有在它自己的截至日期前完成,才可以取得相应的收益。


现在我们给定:假设第i个作业,它的完成期限是di ,而在期限di之前完成它而获得的收益为pi 。现在的问题是,计算机如何安排这些作业的运行,从而保证所获得的收益值最大?

二、原始的带有期限的作业排序策略

证明大家可以自己回去翻课本补习,这里将不再做赘述。

由于我们希望的是最后获得的收益值最大,所以我们需要从收益值较大的作业开始考虑。将这些作业的原始集合按照收益值pi从大到小排好序,然后从第一个作业开始考虑。设置一个用于存储最终执行序列的集合J,先判断当前正在考虑的作业 i 放入当前J集合后能否顺利执行。(即作业 i 能否在规定的时间内完成:集合J按照期限值从小到大的顺序排列,安排新作业 i 进入J时通过插入排序的方式来比对是否允许插入)如果可以的话,作业 i 将作为一个新元素进入J集合中的指定位置。
在这里插入图片描述

三、对现有策略提出改进的原因

一切的改进都不是瞎操作!只有弄清楚原有算法哪里有缺点,才能有针对性的提出改进。

由于第一种算法比较好实现,这里不再给出。但是大家可以观察一下,将新作业 i 插入最终序列J中时,经常性的需要将作业 i 插入位置之后的元素集体向后移动。这给原始策略带来了许多不必要的时间复杂度。所以现在需要设计一种新的策略,使得每次进行插入操作时可以不需要移动大批量的作业。


这里给出的策略是:当在考虑作业 i 时,我们不再像第一种算法一样去与J中元素比较完成期限的先后。取而代之的思路是:

在考虑作业 i 时,我们直接寻找能够在期限内完成该作业的最迟时间。值得注意的是,这个最迟的时间,必须是J中原有元素没有占用过的。这样一来,当新的作业 i 需要插入时,便不需要移动后面的元素,如果前面没有位置的话,那么该作业将会被抛弃。这将会为该算法减少大量的时间复杂度。

四、改进的作业排序 实现

这里采用了集合的思想,不懂的推荐大家滚去复习一下数据结构(doge

① 因为每一个作业都会在单位时间内完成,并且所有的作业都是一个接着一个,紧挨着去运行的,所以最终会占用的时间要么是n,要么是这些作业中最大的那个期限,取这二者的小值,得到一个整数b,并由此确定:最终的序列总共一定只会花费b个单位时间。 现在将这b个单位时间划分为b个可用的时间片,以供作业占用。


② 在作业占用时间片时,我们按照第三部分中所陈述的策略,将在期限内完成该作业的最迟时间所对应的那个时间片分配给这个新的作业。我们换一种描述,叫做离这个时限【可用的最近时间片】。


③ 现在我们把这些时间片按照期限值,分成一些集合。举个例子:
现在有五个时间片1,2,3,4,5可供占用 。当前要插入时限为5的作业,离时限5最近的时间片为5,所以我们占用了时间片5。但是,如果下一个要插入的作业的时限同样为5时,最近的最大时间片将不再是5,而是会变成4。但是请放心,我们还没有插入下一个时限5作业,但是请考虑此时的情况:此时时限为4的作业的【可用的最近时间片】居然和时限为5的作业一样!

为此,我们把这些【可用的最近时间片】相同的时限 所对应的时间片 划作同一集合!并且把已经被占用了的时间片,链接到没被占用的时间片的身上。

void Union(int* Array, int i,int j)//并集:实质上是当【右侧最近空闲时间片被占用】导致【两块区域的最近空闲时间片一致】时,
 								//就会发生集合的合并
{
    
    
 int x = Array[i] + Array[j];
 if (Array[i] > Array[j])//i集合元素个数小于j集合【负数比较相反】,i集合并入j集合
 {
    
    
 	Array[i] = j;
 	Array[j] = x;
 }
 else
 {
    
    
 	Array[j] = i;
 	Array[i] = x;
 }
}

④ 另外,这些时间片组成的集合具有一些特点:(1)初值为-1 (2)当被占用后,会指向自己所对应的时限的下个【可用的最近时间片】,而该【可用的最近时间片】的数字则代表这这一集合的个数。即目前有多少个时限的【可用的最近时间片】是自己。(3)集合用双亲表示法表示,对于集合中的任意一个元素,其所对应的时限的【可用的最近时间片】都为这一集合的根。

int Find(int *Array,int i)//寻找最近空闲时间片
{
    
    
	if (Array[i] <= -1)
		return i;
	else
		return Find(Array, Array[i]);
}

⑤ 另外,我们需要一个数组直接保存各个时限的【可用的最近时间片】在哪个位置。但是需要考虑到一种极端情况,即由于时间片1被占用,却无法有效反馈给下一个寻找到时间片1的作业,而导致的时间片1被反复占用覆盖。所以,我们虚拟一个时间片0,当【可用的最近时间片】为0时便意味着这一个作业应当被抛弃。表现在P数组(时间片集合数组)中即为:当前时限所在集合的根为0


⑥ 如果要调度具有期限d的作业,就需要去寻找包含期限值min{n,d}的那个时间片所在集合的根。如果这个根是 j 且其【可用的最近时间片】不为时间片0 , 则其就是【可用的最近时间片】。在使用了这一时间片后,其根为 j 的集合应与该【可用的最近时间片】的前一个时间片所在的集合合并。

好了,剩下的大家自己体会吧

int* FJS(int n,int *D,int *k)//k作为引用传入,可以在运行结束后得知 J内元素的个数
{
    
    
	int maxD = -1;
	for (int i = 1; i <= n; i++)//取最大期限值
	{
    
    
		if (maxD < D[i])
		{
    
    
			maxD = D[i];
		}
	}
	int b = min(n, maxD);

	int* F = new int[b + 1];
	int* P = new int[b + 1];
	int* J = new int[n];

	for (int i = 0; i <= b; i++)//这里应该是小于等于b,因为①若n比maxD小,则b=n,这里无所谓
								//						   ②若n比maxD大,则b=maxD,即超出b的值是无意义的,并不会统计到
								//							 并且之前创立的F、P数组大小为b,并不支持对比b大的n进行索引
	{
    
    
		F[i] = i;
		P[i] = -1;
	}
	
	*k = 0;

	for (int i = 1; i <= n; i++)
	{
    
    
		int j = Find(P,min(n,D[i]));//距离i最近的空时间片的位置【如果没有选择过该作业,那么此时会是D[i]本身或者】
		if (F[j] != 0)	//当F[j]为0时,则说明时限j之前没有空闲时间片,作业i将被抛弃
		{
    
    
			J[*k] = i;
			int l = Find(P, F[j] - 1);//寻找F[j]被占用后,F中 距离时限j最近的 空时间片位置
			Union(P, l, j);	//在树中将 时限j的最近空时间片位置 向前面转移
			F[j] = F[l];	//更改 时限j的最近空时间片位置
			(*k)++;
		}
	}
	return J;
}

看到这里,请务必三连!(doge

猜你喜欢

转载自blog.csdn.net/qq_38236620/article/details/115289337