c2java Greedy 之任务调度

最近调试一个java工程的时候,我遇到不是期望的输出结果时,是这么干的:
A1注释掉抛出的异常; A2加打印对比异常输入和正常输入; A3进一步加打印缩小范围。
其实只需:B1静下心来仔细观察异常栈帧。

    A1是希望改了使程序走完正常流程,得到期望的结果。这是一个局部最优选择。
在某些情形下,我们没得选择,只能根据当前得到的部分信息做判断。说白了就是不断试错的过程。比如在荒郊野外的夜晚,我们要找些干柴生火,就是一个单源多目的地最短路径问题。

可行的例子:
 找零钱 min C = \sum Ci s.t. V = \sum Vi * Ci。
 霍夫曼码 min BitLength = \sum Freq(c) * depth(c)。
 分数背包 max V =  \sum Vi * Fi s.t. \sum Wi * Fi <= W。
 带起止时间的活动选择 A(i,j) = max{A(i,k) + a(k) + A(k,j)}
不可行的例子:n个任务分配给n个人,整数背包。


拟阵(matroid)是(S,I)满足
1. S 是有限集合。
2. I是S的独立子集集合,即如果B \in I, A \subset B, 则A \in I。
3. 交换性质:如果A,B \in I, |A| < |B|, 则存在某个x \in B - A, 使得A\nion{x} \in I。
这个概念来自:
Hassler Whitney 的matric matroids, 其中S的元素是矩阵的行,
矩阵行是独立的如果它们是通常意义下的线性独立的。
无向图(V,E), S = E, E的子集A是独立的如果A是无环的。
称(S,I)是加权的,如果S中每个元素都赋予一个正数。


GREEDY(S,I,W) //返回加权最大的子集
 A = \empty
 根据W对S排降序
 for x \in S
  if A \nion {x} \in I
    A = A \nion {x}
 return A
可以证明拟阵有optimal substructure性质,上面的贪心算法是正确的。
设检测需要f(n);整个复杂度为O(n*log(n) + n * f(n))

单处理器上带截止时间和过期惩罚的单位时间任务调度。
scheduling unit-time tasks with deadlines and penalties for a single processsor。
求任务集合S的任务如何排列,使得惩罚值最小。

转换为子集问题:
对于一个给定的排列,总可以保持惩罚值,把过期的a放在没有过期的后面b,因为把a调到后面仍然过期,把b调到前面仍然更不过期;类似地,在非过期任务中,总可以按过期时间降序排。
因为惩罚值的总和是常数,所以:过期惩罚最小 <==> 不过期惩罚最大。
转换为原问题的解:
把不过期按截止时间升序排,然后过期随便排在后面。


应用拟阵框架:
子集A称为独立的,如果存在一个调度使得没有过期的任务。记所有独立子集为I。
随便给定一个子集A,我们如何判断它是独立的?
记N(t,A)为A中截止时间不超过t的任务个数。由于截止日期从1开始,所以N(0,A)=0。
引理: A 是独立的 <==> N(t, A) <= t, for t = 0,1,...,n。(A)

定理:系统(S,I)是拟阵。
应用GREEDY算法,如果直接按(A), 总的计算复杂度为O(n^3)。

下面通过例子来探索如何减少计算量。
任务号a: 1, 2, 3, 4, 5, 6, 7
惩罚值p:70,60,50,40,30,20,10
过期值d: 4, 2, 4, 3, 1, 4, 6
N(1,{4}) = {4}中<=1 的个数 = 0 < 1 
N(1,{4,2}) = {4,2}中<=1的个数 = 0 
N(2, {4,2}) = {4,2}中<=2的个数 = 1
A={4,2,4}:    N(1,A)=0, N(2,A)=1, N(3,A)=1。
A={4,2,4,3}:  N(1,A)=0, N(2,A)=1, N(3,A}=2, N(4,A)=4。
A={4,2,4,3,1}:N(1,A)=1, N(2,A)=2, N(3,A)=3, N(4,A)=5, N(5,A)=6。
规律:
N(t,A+{x}) = N(t,A) + (x<=t? 1 : 0) for t = 1,2,..,|A|
"N(t,A+{x}) = N(t-1, A+{x}) + (x<=t? 1 : 0) for t = |A|+1
开设一个数组记录当前的部分解A对应的N值,由于上面例子中的1不可加入,得想办法
如何维护这个数组的语义。显然N(t,A)关于t单调增,如果我们一开始从

t=|A|+1计算就可排除,如果不可排除则更新数组。"这个是错的, 对于上面倒数第二组不成立。


采用计数排序的想法,从左到右计算N(t,A)是部分和累加的过程。

另一种方案:
一种理想情形是第i个任务的截止时间恰好是i,对于所有i=1,2,...,n。
显然答案是直接把第i个任务放在时间槽i上就行了,且惩罚值为零。
扩展为一般情形,对于第i个任务,截止时间槽d[i]上要么为空,或者不空。
对于前者,直接直观想法是直接放上去就是,如果它前面还有空的话,放在d[i]显然比放在它前面更好; 对于后者,从它开始往前找到第一个空位放上,如果它前面一个空位都没有,那只好被罚了,放在最后面吧。
计算结果为:4,2,3,1,5,7,6, 其中5,6超时,处罚值为50。和上面的拟阵方案完全一样,是巧合么?
这种方案如果直接计算,总复杂度为n^2, 其中一个n是每次找空位的时间,我们用并查集来可以优化到几乎是一个小于10的常数。
这里省略掉。。。

最后来发一些哲学上感概。
世俗意义上,贪婪是形容一个人的品性的贬义词。特别是那些搞垮有不少劳工公司的银行家们。
道德经也从这个角度上说不要满,要有些缺憾,要大成若缺。
但是从任务调度,最短路这两个例子来说,它们似乎是内在的物理属性,就像水之趋下,原子总右从高能轨道到低能稳态的趋势。
并且一旦给它披上了数学外衣,就感觉它不再是人们的主观意愿了,而成了客观定律了。

Ref
[1] 系统调度综述其中的"Since Linux 2.6.23"这节 

http://en.wikipedia.org/wiki/Scheduling_(computing)

import java.util.Arrays;

class Node implements Comparable
   
   
    
    
{
	int t; /*任务编号*/
	int d; /*截止时间>=1*/
	int p; /*过期惩罚值*/
	public Node(int t, int d, int p)
	{
		this.t = t; this.d = d; this.p = p;
	}

	public int compareTo(Node b)
	{
		return b.p - this.p;	
	}
}

public class Greedy
{
	Node[] a;

	int n; /*当前解的个数*/
	int[] N; /*[1..n]*/

	int checkAndUpdate_(int x)
	{/*检查x是否可加入,如果可以则更新数组,并返回1*/
		int tmp, tmp2, t = n;	

		for(tmp = 1; tmp <= n; ++tmp)System.out.printf("%d ", N[tmp]);
		System.out.printf(" n = %d, x = %x%n", n, x);
		
		/*当第一次调时n=0, 如果x总大于0并且N[0]初始化为0,则无需特殊处理*/
		tmp = N[t] + (x <= t ? 1 : 0); //1 + 3 <= 3 = 2; 
		tmp2 = tmp + (x <= t+1 ? 1 : 0);//2 + 3 <= 4 = 3;
		if(tmp2 > n+1)return 0;

		N[n+1] = tmp2; N[n] = tmp;	
		for(t = 1; t < n; ++t){N[t] += (x <= t ? 1 : 0);}
		return 1;
	}

	int checkAndUpdate(int x)
	{
		int i, s = 0;
		N[x]++;
		for(i = 1; i <= N.length-1; ++i){
			s += N[i]; 
			if(s > i){//N(t, A) > t
				--N[x];
				return 0;
			}	
		}
		return 1;
	}

	int calculateSchedule()
	{
		int i, sum = 0;
		Arrays.sort(a);
		n = 0;
		for(i = 0; i < a.length; ++i){
			Node x = a[i];
			if(1 == checkAndUpdate(x.d)){
				System.out.printf(" %d", x.t);
				n++;				
			}else{
				sum += x.p;
			}
		}
		System.out.printf(" penalty = %d%n", sum);
		return sum;
	}	

	public Greedy()
	{
		int[] p = {70, 60, 50, 40, 30, 20, 10};
		int[] d = {4,   2,  4,  3,  1,  4,  6};
		int i, n = d.length;
		a = new Node[n];
		for(i = 0; i < n; ++i){
			a[i] = new Node(i+1, d[i], p[i]);
		}
		N = new int[n+1];
	}

	public static void main(String[] arg)
	{
		Greedy g = new Greedy();
		g.calculateSchedule();	
	}
}

/*
$ javac Greedy.java && java Greedy 
 1 2 3 4 7 penalty = 50
*/

   
   


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值