贪心算法:作业排序问题

Posted 中学生编程与信息学竞赛自学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了贪心算法:作业排序问题相关的知识,希望对你有一定的参考价值。

本课程是从少年编程网转载的课程,目标是向中学生详细介绍计算机比赛涉及的编程语言,数据结构和算法。编程学习最好使用计算机,请登陆 www.3dian14.org (免费注册,免费学习)。


上一堂课程,我们学习了如何选择活动,使得在不发生冲突的前提下,能参与尽可能多的活动,这里没有考虑到不同的活动是否会带来不同的收益,假设它们的收益是一样的。


现在我们把问题修改下:


有一系列作业,每个作业必须在各自的截止时间点之前完成,并且已知完成每个工作需要花费的时间以及带来的收益。为了简单起见,假设完成每个工作都需要相等的单位时间,这样作业的截至时间点也就是单位时间的倍数。再假设一次只能安排一个作业,完成一个作业后才能继续下一个作业。问:如何选择作业并安排次序使它们能带来的总收益为最大?


我们用例子A来说明。


【输入】有4个作业a,b,c,d。它们各自的截止时间和能带来的收益如下:

【输出】请找出一种能带来最大收益的作业执行次序。


在例子中,下列作业次序能带来最大的收益:{c, a}


完成作业c,能带来的收益是40;因为做作业c,截止时间为1,它必须安排在0-1之间执行。这样已经错过了b或d的截止时间点(1),因此无法再选择b或d,只能选择作业a,a的截至时间点为4,来得及完成。做作业a的收益是20,因此完成作业c和a能带来的总收益是 40+20=60。而选择其他次序的作业,都无法超过收益60。   


参见下面的动图:


贪心算法(4):作业排序问题


我们再看一个例子B


【输入】有5个作业a,b,c,d,e。它们各自的截止时间和能带来的收益如下:


贪心算法(4):作业排序问题

请你选择和安排能获取最大收益的作业次序,先不给出答案,请你先思考,如何解答,然后再继续阅读。


贪心算法(4):作业排序问题



贪心算法(4):作业排序问题


算法分析


一个简单粗暴的算法就是生成给定作业集合的所有子集,并检查每一个子集以确定该子集中作业的可行性,然后找出哪个可行子集可以产生最大收益。 这是一个典型的贪心算法。


算法思路如下


1)根据收益递减顺序对所有作业进行排序。

2)将结果序列初始化,并把已排序作业中的第一个作业加入

3)依次按下面的规则处理剩余的n-1个作业

     如果把当前作业加入结果序列,不会错过它的截止时间,那么把它加入结果序列;

     否则忽略当前的作业。


该算法中最费时的操作是为作业找一个可用的作业空闲时间窗口,要保证完成尽可能多的高收益作业。可以采取的策略是我们为每个作业,遍历作业窗口并分配可用的最晚空闲时间窗口(当然必须在截止时间之前)。例如,假设作业J1的截止时间为t = 5,我们可以为此作业指定最晚的时间窗口(该时间窗口是空闲的,小于截止时间),就是4-5之间的空闲时间段。 之后另一个截止时间为5的作业J2再加入进来,为它分配的最晚时间窗口将会是3-4,因为已经将4-5分配了给作业J1。


那么为什么一定要为作业分配最晚的空闲时间窗口呢?而不是随意为它分配一个可用的时间窗口呢?


例如:假设作业J1的截止时间d1 = 5,产生效益40;作业J2的截止时间d2 = 1,产生效益20。假设对于作业J1,我们分配了0-1之间的时间窗,因此我们将在该时间窗口执行作业J1,导致现在无法执行作业J2(因为J1无法在截止时间d2=1之前完成了)。但是,如果为J1分配4-5之间的时间窗,则J2可以执行。因此为作业分配最晚的空闲时间窗口可以尽可能多地执行其他作业。


贪心算法(4):作业排序问题

 实例分析


我们采用该算法来分析例子B。

1)先按收益从大到小排序

贪心算法(4):作业排序问题

2)把最大收益的作业a加入结果序列,得到{a},完成a需要1个单位时间,它的截止时间是2,因此可以为它安排的最晚时间窗口是1-2


贪心算法(4):作业排序问题


3)接着看作业c, 它的截止时间也为2,不过此时1-2之间已经被a占用了,因此它最晚只能被安排在0-1之间执行。把c加入结果序列 ,得到{a,c},


贪心算法(4):作业排序问题


4)接着看作业d, 它的截止时间为1,无法为它安排执行了,因为它唯一能执行的时间窗口0-1已经被作业c占据了


贪心算法(4):作业排序问题


5)同样作业b, 它的截止时间为1,也不能加入结果序列


贪心算法(4):作业排序问题


6)  最后看作业e, 它的截止时间为3,可以为它安排的最晚空闲执行时间段为2-3,可以加入结果序列,得到{a,c,e}


贪心算法(4):作业排序问题


因此例子B的答案是 {a,c,e},您答对了吗?


请参考下面的动图:



算法实现


算法的C++代码实现如下:

#include<iostream> 

#include<algorithm> 

using namespace std; 

  

// 采用结构类型表示作业 

struct Job 

   char id;      // 作业编号 

   int dead;    // 截止时间 

   int profit;  // 完成作业获取的收益

}; 

  

// 该函数用于按收益大小排序 

bool comparison(Job a, Job b) 

     return (a.profit > b.profit); 

  

//  按收益最大化选择作业并进行排序

void schedulingJob(Job arr[], int n) 

    // 按收益递减次序对作业队列排序 

    sort(arr, arr+n, comparison); 

  

    int result[n]; // 保存结果序列,记录对应的作业数组下标 

    bool slot[n];  // 记录对应时间窗口是否被占用 

  

    // 把所有时间窗口初始化为空闲状态 

    for (int i=0; i<n; i++) 

        slot[i] = false; 

  

    // 对排好序的作业队列依次执行 

    for (int i=0; i<n; i++) 

    { 

      //试图为当前作业找到可用的最晚的空闲时间窗口 

       //注意是从晚到早依次检查 

       for (int j=min(n, arr[i].dead)-1; j>=0; j--) 

       { 

          // 找到一个空闲时间窗口 

          if (slot[j]==false) 

          { 

             result[j] = i;  // 把找到的作业加入结果序列 

             slot[j] = true; // 标记该时间窗口被占用 

             break; 

          } 

       } 

    } 

  

    // 输出结果 

    for (int i=0; i<n; i++) 

       if (slot[i]) 

         cout << arr[result[i]].id << " "; 

   

int main() 

    Job arr[] = { {'a', 2, 100}, {'b', 1, 19}, {'c', 2, 27}, 

                   {'d', 1, 25}, {'e', 3, 15}}; 

    int n = sizeof(arr)/sizeof(arr[0]); 

    cout << "The maximum profit sequence of job:"; 

    schedulingJob(arr, n); 

    return 0; 


请点击阅读原文来观看动画交互课件

以上是关于贪心算法:作业排序问题的主要内容,如果未能解决你的问题,请参考以下文章

分治、贪心五大算法

贪心算法1001

贪心算法(贪婪算法)

动态规划与贪心算法的本质区别

贪心算法的复习和总结

算法刷题-数组排序(图算法算法高阶)螺旋矩阵(数组矩阵)分发糖果(贪心数组)