两类带时限的作业排序问题

1   带时限的作业排序问题

问题描述:

设有一个单机系统、无其它资源限制且每个作业运行相等时间,不妨假定每个作业运行 1 个单位时间。现有 n 个作业,每个作业都有一个截止期限di>0,di 为整数。如果作业能够在截止期限之内完成,可获得 pi>0 的收益。问题要求得到一种作业调度方案,该方案给出作业的一个子集和该作业子集的一种排列,使得若按照这种排列次序调度作业运行,该子集中的每个作业都能如期完成,并且能够获得最大收益。

输入:

第一行输入 n 的值,以下 n 行输入作业号 i,收益 pi,截止期限 di。

输出:

n 个作业的一个最优子集。

示例 

输入:

      4

      1 100 2

      2 10 1

      3 15 2

      4 27 1

      1 4

输出: 0 0 1 1

 

2   带时限的作业排序问题II

问题描述:

带时限的作业排序问题可描述为:设有 n 个作业和一台处理机,每个作业所需的处理时间、要求的时限和收益可用三元组(pi, di, ti),0<=i<n 表示,其中,作业 i 的所需时间为 ti,如果作业 i 能够在时限 di 内完成,将可收益 pi,求使得总收益最大的作业子集 J。

输入:

第一行输入 n 的值;第二行输入 pi;第三行输入 di;第四行输入 ti (i=0,…,n-1 且作业已经按时限非减次序排列)。

输出:

xi(用固定长度 n-元组 xi 表示,xi=0 或 1,i=0,…,n-1)。

示例 

输入:

      4

      5 3 6 10

      1 1 2 3

      1 1 1 2

输出: 0 0 1 1

 

第1题

思路分析

这题的思路比较明显,就是每次都选取作业收益最大的,并把它放在允许的最大的截止时间内完成,符合贪心算法的基本思想。将输入的每个作业按收益从大到小进行排序,用一个数组vis表示某时刻是否已经有作业正在运行,vis[i]=1,表示时间i被占用 。然后从第1个作业开始往后搜索,将该作业安排到vis[d]时间运行,如果d已经有安排,将从d-1开始往前搜索,直到找到一个未被占用的时间点。如果找不到空时间点,则跳过此作业,继续向后搜索直到所有作业都搜索完。

 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <algorithm>
 4 #include <string.h>
 5 
 6 using namespace std;
 7 
 8 #define MAXN 1100
 9 
10 struct Node
11 {
12     int id; //编号 
13     int p;  //收益 
14     int d;  //截止期限 
15 }node[MAXN]; 
16 
17 int n;
18 int vis[MAXN];      //第i个单位时间是否有作业在运行 
19 vector<int> res;    //保存结果 
20 
21 bool cmp(Node a, Node b)
22 {
23     return a.p > b.p;
24 }
25 
26 int main()
27 {
28     scanf("%d", &n);
29     for(int i = 0; i < n; ++i)
30         scanf("%d%d%d", &node[i].id, &node[i].p, &node[i].d);
31     //按照收益从大到小排序 
32     sort(node, node+n, cmp);
33     memset(vis, 0, sizeof(vis));
34     
35     for(int i = 0; i < n; ++i)
36     {
37         // 如果到期时间没有被占用,则直接占用到期时间;
38         // 否则,继续往前找一个未被占用的时间 
39         if(!vis[node[i].d])
40         {
41             vis[node[i].d] = 1;
42             res.push_back(node[i].id); 
43         }
44         else
45         {
46             for(int j = node[i].d-1; j >= 1; --j)
47             {
48                 if(!vis[j])
49                 {
50                     res.push_back(node[i].id); 
51                     vis[j] = 1;
52                     break;
53                 }
54             }
55         }
56     } 
57     
58     for(int i = 0; i < res.size(); ++i)
59         printf("%d ", res[i]);
60 
61     return 0;
62 }

复杂度分析

从代码来看,sort函数排序是O(nlogn)的时间复杂度,最差情况下,d >= n,且每组数据的截止期限都是d时,每次都要往前找一个空的时间点,此时需要找1+2+3+…+n次,所以耗费的时间为O(n2)。由于各个子块不是嵌套的而是顺序的,所以时间复杂度取最高的那个,所以整体来说时间复杂度是O(n2)。

对于空间复杂度,vis[]数组的大小由最大截止日期d决定。所以空间复杂度是O(d)。

第2题

这题和前面这题很相似,但其实解法有很大的差异。这题如果还是按前面那题的贪心思想去解题,是做不出来的,很难找到贪心算法中的最优度量标准。这题是一道比较难解决的问题,我在网上也没找到解决方法。后来询问一些算法比较厉害的同学之后,发现这题其实就是个0-1背包问题。

解题策略就是先选出最大的截止时间dmax,然后从1到dmax,进行0-1背包算法的操作。通过0-1背包的思路就可以找到最优解,这题相较于经典的0-1背包问题还是多了一些内容,那就是要记录背包的路径,最后输出作业的子集的过程其实就是打印背包路径的过程。

 1 #include <iostream>
 2 #include <stdio.h>
 3 
 4 using namespace std;
 5 
 6 int n;
 7 int p[110], d[110], t[110];
 8 int memo[1110];          //memo[i]表示截止期限为i时的最大收益 
 9 int path[1110][1110];   //记录背包路径 
10 
11 //递归打印背包路径 
12 void printPath(int i, int j)
13 {
14     if(i < 0 || j < 0)
15         return;
16     
17     if(path[i][j] == 1)
18         printPath(i-1, j-t[i]); 
19     else
20         printPath(i-1, j);
21     
22     printf("%d ", path[i][j]);
23 
24 }
25 
26 int main()
27 {
28     scanf("%d", &n);
29     int dmax = -1;  //最大截止时间 
30     for(int i = 0; i < n; ++i)
31         scanf("%d", &p[i]);
32     for(int i = 0; i < n; ++i)
33         scanf("%d", &d[i]);
34     
35     //题中已经说明输入按时限非减次序排列,所以d[n-1]就是最大的截止期限 
36     dmax = d[n-1];  
37     
38     for(int i = 0; i < n; ++i)
39         scanf("%d", &t[i]);
40         
41     for(int i = 0; i <= dmax; ++i)
42     {
43         if(i >= t[0])
44         {
45             memo[i] = p[0]; 
46             path[0][i] = 1;   //记录路径
47         }
48         else
49             memo[i] = 0;
50     } 
51     
52     for(int i = 1; i < n; ++i)
53     {
54         for(int j = dmax; j >= t[i]; --j)
55         {
56             if(memo[j] < p[i] + memo[j-t[i]])
57             {   //物品装入背包,记录路径
58                 memo[j] = p[i] + memo[j-t[i]];
59                 path[i][j] = 1;     //记录路径 
60             }
61         }
62     }
63 
64     //递归打印背包路径 
65     printPath(n-1, dmax);
66     
67     return 0;
68 }

复杂度分析

从代码来看,这个程序运行的时间主要消耗在第一次的双重循环中,外层循环的次数为n,内层循环次数为dmax,所以这个算法的平均时间复杂度为O(n*dmax)。

原文地址:https://www.cnblogs.com/FengZeng666/p/13929488.html