贪心算法下的两大经典问题:最优装载问题、最小延迟调度问题

上一篇主要介绍了贪心算法的内容和活动选择问题。本篇主要介绍最优装载问题和最小延迟调度问题

1、最优装载问题

什么是最优装载问题

类似于0-1背包问题那样,有n个集装箱1,2,…,n装上轮船,集装箱i的质量wi,轮船装载重量限制为C,无体积限制。问如何装使得上船的集装箱最多?

这个和前面介绍的完全背包和0-1背包问题差不多一样。然而那些问题是有重量和价值的。这个最优装载问题是只关乎于重量对价值没有要求。而完全背包和0-1背包问题的时间复杂度就是指数量。而对于它们的子问题就存在多项式时间算法,例如,这个最优装载问题就存在一个多项式时间算法。

下面对于这个问题举个例子
有5个物品:w1=4,w2=6,w3=2,w4=8,w5=5。轮船装载重量限制为11
这个问题的解是:1,3,5

下面对问题进行建模:
设<x1,x2,…,xn>表示解向量,xi=0,1,xi=1当且仅当第i个集装箱装上船
那么目标函数就是装的箱子的个数的最大值,而约束条件就是装的箱子重量的和不能超过重量限制

接下来用贪心算法来解最优装载问题

用贪心算法来解最优装载问题

大家可以想想,用什么贪心策略来解这个问题。我相信大家一定是有想法的。

大家想的是不是这个贪心策略:轻者优先。

也就是先将集装箱进行升序排序,按照标号由小到大装箱,直到装入下一个箱子将使得集装箱总重超过轮船装在重量限制,则停止。

下面我们对这个贪心策略进行证明

数学归纳法证明贪心策略

首先将集装箱从轻到重记为1,2,…,n

首先进行归纳基础:
当k=1时,证明存在最优解包含物品1。
如若不然,我们找到一个物品j替换物品1。使其成为最优解A
我们用1来替换j,使其成为最优解A’
因为w1<=wj,如果A是最优解。那么A’也是最优解。因为这个升序之后的1号箱子质量最小。所以是可以放进去的。
因此归纳基础成立

下面我们进行归纳步骤:
假设命题对k为真,证明对k+1也为真
算法执行到第k步,选择了i1=1,i2,…,ik,根据归纳假设存在最优解包括i1,i2,…ik。然后我们对这个归纳假设进行改造。
我们把i1提出去,将ik+1加进去。因此得到i2,i3,…,ik+1这k个箱子对于命题成真。
我们将i2,i3,…,ik+1设为I’,根据归纳假设I’是一个最优解,因此我们有I=I’∪{1}。下面我们证明这个I是原始输入的最优解。
如若不然,则存在一个包含1的关于N的最优解I1,(如果I1中没有1,则用1替换I1中的第一个元素得到的解也是最优解),且|I1|>|I|;因此就会有|I1-{1}|>|I-{1}|。好,下面我们把I1和I的1号箱子全部取走

因为有|I1-{1}|>|I-{1}|,而|I-{1}|是I’。大家看我们在上面已经证明了I’是一个最优解了。而I1-{1}>I’。这显然是和归纳假设相违背。因此不存在一个I1是问题的最优解。因此I就是原问题的最优解。得证。

故贪心策略是正确的

下面我们对代码进行分析:
这个也是先排序,然后如果箱子的重量小于等于最大负载量,就把这个箱子加进去,然后负载量就要减去这个箱子的重量。
下面直接上代码

public class Commodity {
	int index;  //货物编号
	int weight;  //货物重量
	
	public Commodity() {
		// TODO Auto-generated constructor stub
	}

	public Commodity(int index, int weight) {
		super();
		this.index = index;
		this.weight = weight;
	}
	
	
}

public static void main(String[] args) {
		// TODO Auto-generated method stub
		Commodity commodity[]=new Commodity[10];
		commodity[0]=new Commodity(1, 2);
		commodity[1]=new Commodity(2, 8);
		commodity[2]=new Commodity(3, 7);
		commodity[3]=new Commodity(4, 6);
		commodity[4]=new Commodity(5, 3);
		commodity[5]=new Commodity(6, 10);
		commodity[6]=new Commodity(7, 13);
		commodity[7]=new Commodity(8, 6);
		commodity[8]=new Commodity(9, 4);
		commodity[9]=new Commodity(10, 2);
		int maxLoading=20;
		
		//对货物重量进行升序排序
		Arrays.sort(commodity,new Comparator<Commodity>() {
			public int compare(Commodity o1, Commodity o2)  {
				return o1.weight-o2.weight;
			};
		});
		
		List<Integer> optimalloadingTracing = OptimalloadingTracing(commodity, maxLoading);
		
		System.out.print("可以装载的货物的编号是:");
		for(int i=0;i<optimalloadingTracing.size();i++) {
			System.out.print(optimalloadingTracing.get(i)+"   ");
		}
		
	}
	
	//贪心法解最优装载问题
	public static List<Integer> OptimalloadingTracing(Commodity commodityWeight[],int maxLoading){
		List<Integer> list=new ArrayList<Integer>();
		for(int i=0;i<commodityWeight.length;i++) 
			if(commodityWeight[i].weight<=maxLoading) {
				list.add(commodityWeight[i].index);
				maxLoading=maxLoading-commodityWeight[i].weight;
			}
			else
				break;
		return list;
	}

下面我们对贪心法解最优装载问题进行分析

贪心法解最优装载问题进行分析

我们看到啊,这里排序是O(nlogn)
选择算法是一层for循环,在最坏条件下,是将所有箱子都装进去了。因此是O(n)
故时间复杂度就是O(nlogn)+O(n)

因此贪心法解最优装载问题的时间复杂度就是O(nlogn)

以上是贪心法解最优装载问题的内容

2、最小延迟调度问题

什么是最小延迟调度问题

客户集合A,对于任意的i属于A,ti为服务时间,di为要求完成时间,ti,di为正整数,一个调度f:A->N,f(i)为客户i的开始时间,求最大延迟达到最小的调度,

也就是说给出每项可调度作业时间,和客户要求每项作业完成的期限。现在我们要进行调度这些作业。要求只能允许一项作业在工作,不能让两个作业同时工作。但可以让两个作业无缝连接,就是一项作业完成的同时可以让下一个作业续上进行工作。如果这个作业超过了期限,那么结束时间减去这个期限就是第i个作业的延迟调度时间。当n个作业全部调度完毕的时候,那个最大的延迟调度时间就是这n个作业的延迟调度时间。而如何让这个时间变小就是这个最小延迟调度问题要研究的内容。

我们可以得出这样的式子

在上面那个图中f(i)是对第i个任务开始调度前的起始时间,现在我们要开始调度第i个任务,而f(i)+ti表示的是作业调度完成时的结束时间。而di就是第i个任务的期限,或者是说截止时间。而超过了di,就说明了这个第i个作业有延迟,它的延迟时间就是f(i)+ti-di

下面举个例子:
作业调度时间T=<5,8,4,10,3>,对应每一个作业调度的截止时间D=<10,12,15,11,20>
如果我们顺序安排这些任务调度次序

因为第一个作业是在规定时间完成的,所以第一个作业没有延迟
d1=0
第二个作业是从5开始的,结束时间是13,而第二个任务的截止时间是12。故d2=13-12=1
第三个作业是从13开始的,结束时间是17,而第三个任务的截止时间是15.故d3=17-15=2
同理d4=16,d5=10
我们根据上边的计算,找到了一个最大延迟调度16,因此这几个作业的调度得到的最大延迟就是16

如果我们按照截止时间从前到后安排
我们得到另一个结果

我们通过像上面那样分析,得出d1=0,d2=11,d3=12,d4=4,d5=10
我们根据上边的计算,找到了一个最大延迟调度12,因此这几个作业的调度得到的最大延迟就是12

我们看到变换不同的作业次序就能让n个作业的延迟调度时间降下来。

下面我们用贪心算法解最小延迟调度问题

用贪心算法解最小延迟调度问题

我们根据上边的例子,找到了一个贪心策略:将可调度的作业次序按截止时间从前到后安排

下面我们对这个贪心策略进行证明

交换论证证明贪心策略

交换论证证明思路:
1、分析一般最优解与算法解的区别
2、设计一种转换操作,替换成分或者交换次序,可以在逐步将一个普通最优解转换成算法的解
3、上述每步转换都不降低解的最优性质

在刚才的贪心策略的性质是:
没有空闲时间,没有逆序(di<dj,使得f(i)>f(j),也就是谁截止时间大,谁就先进行调度)

我们首先思考一种情况,如果连续m个作业的的截止时间一样,那么我们要对这连续的m个作业进行调度。这连续的m个作业的次序可以随便调整。
像这样的

我们看到啊,从1到k这些个任务的截止时间是一样的,而现在从t0开始陆续执行这些个任务。无论次序有何变化,最后执行完毕时间就是t。而且最后一个任务产生了最大延迟,因为在截止时间一样的安排下,最后一个任务的完成时间距截止时间较长,如果它们的截止时间都是d的话,那最后一个任务就产生了t-d的最大延迟时间

我们可以根据上边的思考进行证明

从没有一个空闲的最优解出发,逐步转变成没有逆序的解。这个解和算法解具有相同的最大延迟
没有逆序的解最优的话,算法的解就是最优的。
如果一个最有调度存在逆序,那么存在一个(i,i+1)构成一个逆序,则将这种情况成为相邻逆序
交换相邻逆序,证明得到的解仍旧最优

假设i的截止时间大于j的截止时间(di>dj),如果作业安排次序是i,j则就出现了逆序。我们要将i,j互换来消除逆序。

互换之后我们能得出交换i,j之后,会增加i的延迟,而不会增加j的延迟;而且交换i,j之后对其他作业延迟时间没有任何关系。

来,我们看一个图

我们可以对上边计算j的延迟时间-i的延迟时间,即yj-yi
设ti是第i个作业完成时间,tj是第j个作业完成时间
yi=ti-di
yj=tj-dj
yj-yi=tj-dj-ti+di=tj-ti+di-dj
由于tj-ti>0(从图中可以看到)
di-dj>0(i的截止时间大于j的截止时间)
因此yj>yi
所以我们可以得出一个结论:上面的那一段中j的延迟比i大,故上面的那一段的最大延迟是来自于j中

下面我们对下面那一段进行分析:
交换后,j的延迟会相应减少(也就是说下面j延迟时间小于上面j延迟时间),因为把j放在了前面。而对于i的延迟会增加。
也就是说如果下面那一段的最大延迟还是来源于j,那么下面那一段的最大延迟相比上面那一段会减少(因为下面的yj小于上面的yj)。
如果下面那一段的最大延迟来源于i,那么下面的最大延迟是yi=s+tj+ti-di;而上面的最大延迟来源于j,则yj=s+tj+ti-dj
那么yi-yj=dj-di<0
因此下面的那一段最大延迟要小于上面那一段的最大延迟

综上下面的那一段最大延迟要小于上面那一段的最大延迟

就这样消除逆序,至多经过n(n-1)/2次交换得到一个没有逆序的调度,而且我们在上面已经证明了没有逆序的延迟调度是比较小的。故证明了没有逆序是一个最优调度,即贪心策略就是算法的解。得证。

下面我们来分析代码
首先对截止时间按升序排序,然后就是通过计算每一项调度的进行完成的时间与截止时间的差值的最大值就是问题的解
下面上代码

public class Scheduling {
	int index;  //任务编号
	int time;  //服务时间
	int deadTime;  //截止时间
	
	public Scheduling() {
		// TODO Auto-generated constructor stub
	}

	public Scheduling(int index, int time, int deadTime) {
		super();
		this.index = index;
		this.time = time;
		this.deadTime = deadTime;
	}
	
	
}

public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scheduling scheduling[]=new Scheduling[5];
		scheduling[0]=new Scheduling(1,5,10);
		scheduling[1]=new Scheduling(2,8,12);
		scheduling[2]=new Scheduling(3,4,15);
		scheduling[3]=new Scheduling(4,10,11);
		scheduling[4]=new Scheduling(5,3,20);
		
		//将截止时间按升序排序
		Arrays.sort(scheduling,new Comparator<Scheduling>() {
			public int compare(Scheduling o1, Scheduling o2) {
				return o1.deadTime-o2.deadTime;
			};
		});
		
		int minimumdelayScheduling = MinimumdelayScheduling(scheduling);
		System.out.println("最小延迟调度时间是:"+minimumdelayScheduling);
	}
	
	//贪心法解最小延迟调度问题
	public static int MinimumdelayScheduling(Scheduling scheduling[]) {
		int time=0;
		int delayTime=0;
		for(int i=0;i<scheduling.length;i++) {
			time+=scheduling[i].time;
			delayTime=Math.max(delayTime, time-scheduling[i].deadTime);
		}
		return delayTime;
	}

下面对这个问题进行分析

贪心法解最小延迟调度问题的分析

我们看到,排序是O(nlogn),而选择算法是O(n)
所以这个问题的时间复杂度就是O(nlogn)

以上就是最小延迟调度问题的内容

            罪莫大于可欲,咎莫大于欲得,祸莫大于不知足。故知足之足,恒足矣。《道德经》
  • 6
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值