
贪心算法
第 6 课:时间管理大师——区间与任务安排
上一课,我们认识了活动安排大师。
我们学会了一个非常重要的方法:
按照活动的结束时间从小到大排序,每次选择能够接上的活动。
今天,我们继续进入贪心王国!
这一次,我们不再只是安排活动,而是要帮助一位小小魔法师安排一天的学习和冒险任务。
他一天的时间有限,任务有很多,应该怎样安排才能完成更多任务呢?
今天,我们要把上一课的知识迁移到更丰富的场景中。
一、魔法师的一天:时间不够用了!
小明是一位魔法学校的学生。
今天,他有很多事情要做:
-
练习魔法;
-
制作魔法药水;
-
参加飞行训练;
-
整理魔法书;
-
帮助老师打扫教室;
-
参加魔法比赛。
可是,小明只有一天的时间。
每件事情都有自己的开始时间和结束时间,而且一旦开始,就必须连续完成,中途不能暂停。
他的任务安排如下:
|
A 魔法练习 |
8 |
10 |
|
B 制作药水 |
8 |
9 |
|
C 飞行训练 |
9 |
11 |
|
D 整理魔法书 |
10 |
12 |
|
E 打扫教室 |
11 |
12 |
|
F 魔法比赛 |
12 |
14 |
|
G 寻找宝藏 |
13 |
15 |

小明遇到了一个问题:
如果任务之间不能重叠,怎样安排才能完成最多的任务?
大家先想一想:
是不是应该先做开始时间最早的任务?
是不是应该先做持续时间最短的任务?
还是应该先做结束时间最早的任务?
今天,我们就用贪心算法来解决。
二、先认识什么是区间
在算法中,一个任务可以用两个时间表示:
-
开始时间 start
-
结束时间 finish
例如:
任务A:8点开始,10点结束
我们可以把它看成一个时间区间:

像这样,由开始时间和结束时间组成的一段范围,我们称为区间。
生活中有很多区间:
-
上课时间:8:00—9:00;
-
体育训练:9:00—10:00;
-
看书时间:10:00—10:30;
-
电影播放:14:00—16:00。
它们都有开始和结束。
什么叫区间冲突?
假设:
-
任务 A:8—10;
-
任务 B:9—11。
A 还没有结束,B 就开始了。
两个任务的时间重叠,因此不能同时完成。

如果:
-
任务 A:8—10;
-
任务 B:10—12。
那么 A 在 10 点结束,B 正好在 10 点开始。
这两个任务可以连续完成!
这和上一课的活动安排问题完全一样。
三、第一种任务安排:一天最多完成多少件事?
我们先解决一个最简单的问题:
假设每个任务都一样重要,我们只关心完成的任务数量,最多能完成多少件?
这时,上一课的贪心策略就可以直接使用。
第一步:按照结束时间排序
我们把任务按照结束时间从小到大排列。
原来的任务:
|
A |
8 |
10 |
|
B |
8 |
9 |
|
C |
9 |
11 |
|
D |
10 |
12 |
|
E |
11 |
12 |
|
F |
12 |
14 |
|
G |
13 |
15 |
排序以后:
|
1 |
B |
8 |
9 |
|
2 |
A |
8 |
10 |
|
3 |
C |
9 |
11 |
|
4 |
D |
10 |
12 |
|
5 |
E |
11 |
12 |
|
6 |
F |
12 |
14 |
|
7 |
G |
13 |
15 |
第二步:从前往后选择
我们使用上一课的规则:
如果当前任务的开始时间 >= 上一个任务的结束时间
就选择它;
否则跳过。
逐个检查:
|
B |
8—9 |
第一个任务 |
选择 |
|
A |
8—10 |
8 小于 9 |
跳过 |
|
C |
9—11 |
9 不小于 9 |
选择 |
|
D |
10—12 |
10 小于 11 |
跳过 |
|
E |
11—12 |
11 不小于 11 |
选择 |
|
F |
12—14 |
12 不小于 12 |
选择 |
|
G |
13—15 |
13 小于 14 |
跳过 |
最终选择:

这里要特别提醒大家:
任务安排问题和活动安排问题,本质上是同一个问题。
只是故事换了,算法没有变!
四、第二种任务安排:每件任务都有不同的价值
接下来,难度增加了。
老师告诉小明:
“现在不是所有任务都一样重要了,每完成一件任务,都可以获得不同数量的魔法星星!”
任务变成了这样:
|
A |
8 |
10 |
30 |
|
B |
8 |
9 |
10 |
|
C |
9 |
11 |
50 |
|
D |
10 |
12 |
40 |
|
E |
11 |
12 |
20 |
|
F |
12 |
14 |
60 |
|
G |
13 |
15 |
100 |
这次的目标改变了:
不是完成最多的任务,而是获得最多的魔法星星!
我们还能直接使用刚才的贪心策略吗?
1. 如果仍然按照结束时间排序
我们刚才选出了:
B、C、E、F。
获得的奖励是:
10+50+20+60=140
一共获得 140 颗星星。
2. 如果优先选择奖励最高的任务
G 的奖励最高,有 100 颗星星。
可是 G 的时间是 13—15。
我们可以再选择 B(8—9)和 G(13—15)。
总奖励:
10+100=110
比 140 还少。
3. 如果优先选择奖励最高且能够安排的组合
我们还可以选择:
-
A:8—10,奖励 30;
-
C:9—11,奖励 50;
-
F:12—14,奖励 60;
-
G:13—15,奖励 100。
但是注意,这些任务之间存在时间冲突,不能全部选择。
例如 C 和 A 冲突,G 和 F 冲突。
我们必须在任务数量、时间冲突和奖励之间进行综合考虑。
重要发现
当目标从“完成最多任务”变成“获得最高奖励”时,原来的贪心规则不一定还能得到最优结果。
我们需要考虑不同任务组合的总奖励。
这里就可以自然引出下一阶段要学习的动态规划。
今天先让同学们认识这个区别。
五、第三种任务安排:任务有截止时间
我们再来看看另一种更有趣的情况。
小明接到了老师的 4 个任务。
每个任务都需要 1 个小时完成,而且每个任务都有一个最后完成时间。
|
A |
1 小时 |
1 |
50 |
|
B |
1 小时 |
2 |
20 |
|
C |
1 小时 |
2 |
40 |
|
D |
1 小时 |
3 |
30 |
这里的截止时间表示:
-
A 必须在第 1 个小时结束前完成;
-
B、C 必须在第 2 个小时结束前完成;
-
D 必须在第 3 个小时结束前完成。
我们最多只有 3 个小时。
目标是:
合理安排任务,让获得的奖励尽可能多。
1. 先按照奖励从大到小排序
|
1 |
A |
1 |
50 |
|
2 |
C |
2 |
40 |
|
3 |
D |
3 |
30 |
|
4 |
B |
2 |
20 |
这次,我们优先考虑奖励高的任务。
2. 安排任务
我们可以把时间分成 3 个格子:

为什么 B 没有选?
因为 B 的截止时间是第 2 小时结束,前两个小时已经安排了 A 和 C。
而且 A、C、D 的总奖励是:
50+40+30=120
如果选择 B 替换 C:
50+20+30=100
所以不如原来的安排。
这类问题叫作带截止时间的任务调度问题。
对于“每个任务耗时相同、每个任务只能安排一次”的经典版本,可以使用贪心算法:
按照奖励从大到小排序;
对每个任务,尽量安排在截止时间之前最靠后的空闲时间;
如果找不到空闲时间,就放弃这个任务。
3. C++ 程序
为了让初学者容易理解,我们先使用一个简单的数组模拟时间格子。
#include <iostream>
#include <algorithm>
using namespace std;
struct Task {
int id; // 任务编号
int deadline; // 截止时间
int value; // 奖励
};
// 按照奖励从大到小排序
bool cmp(Task a, Task b) {
return a.value > b.value;
}
int main() {
int n = 4;
Task a[100] = {
{1, 1, 50},
{2, 2, 20},
{3, 2, 40},
{4, 3, 30}
};
sort(a, a + n, cmp);
// 每个时间格子只能安排一个任务
int used[100] = {0};
int ans = 0;
for (int i = 0; i < n; i++) {
// 从截止时间向前找空闲位置
for (int j = a[i].deadline; j >= 1; j–) {
if (used[j] == 0) {
used[j] = a[i].id;
ans += a[i].value;
break;
}
}
}
cout << "总奖励:" << ans << endl;
cout << "任务安排:";
for (int i = 1; i <= 3; i++) {
if (used[i] != 0) {
cout << used[i] << " ";
}
}
return 0;
}
输出:
总奖励:120
任务安排:1 3 4
4. 这段代码最重要的地方
for (int j = a[i].deadline; j >= 1; j–) {
if (used[j] == 0) {
used[j] = a[i].id;
ans += a[i].value;
break;
}
}
我们来逐句理解:
-
j = a[i].deadline:先尝试把任务放在最晚允许的时间。
-
j >= 1:如果这个位置已经被占用,就继续向前找。
-
used[j] == 0:检查这个时间格子是不是空闲。
-
used[j] = a[i].id:把任务放进去。
-
ans += a[i].value:把奖励加起来。
-
break:任务已经安排成功,不需要继续寻找。
为什么要尽量安排在截止时间之前最靠后的空闲位置?
因为这样可以把前面的时间留给那些截止时间更早的任务。
注意:这个贪心方法有严格的适用条件。 上面的程序假设每个任务都恰好需要 1 个时间单位,且时间从 1 开始。任务耗时不同,就不能直接照搬这个程序。
六、三种任务安排问题对比
今天我们接触了三种不同的问题。
|
活动安排 |
完成最多的不冲突活动 |
贪心 |
|
带奖励的区间安排 |
获得最高总奖励 |
动态规划 |
|
单位时间任务调度 |
获得最高总奖励,且有截止时间 |
贪心 |
这里特别强调:
不是所有带奖励的任务都要用动态规划,也不是所有任务安排都能用贪心。
要先看清楚题目的规则和目标,再决定使用什么算法。
七、课堂练习:小小时间管理师
练习 1:最多完成几个任务?
魔法学校有 5 个任务:
|
A |
8 |
9 |
|
B |
8 |
11 |
|
C |
9 |
10 |
|
D |
10 |
12 |
|
E |
12 |
13 |
请回答:
应该按照什么条件排序?
最多可以完成几个任务?
具体选择哪些任务?
练习 2:任务奖励大比拼
现在有 4 个任务,每个任务都需要 1 小时完成。
|
A |
1 |
60 |
|
B |
2 |
30 |
|
C |
2 |
50 |
|
D |
3 |
40 |
请回答:
应该按照什么条件排序?
如何安排任务?
最大总奖励是多少?
练习 3:想一想,应该使用什么算法?
请判断:
|
选择最多个互不冲突的活动 |
? |
|
选择互不冲突的活动,使总奖励最大 |
? |
|
每个任务耗时 1 小时,有截止时间,奖励不同 |
? |
试着完成练习 3
1、选择最多个互不冲突的活动,排序规则是什么?
A、开始时间从小到大
B、结束时间从小到大
C、奖励从大到小
答案: B
2、单位时间任务调度,如何安排?
A、按奖励排序,放到截止时间前最晚的空位
B、按截止时间排序,依次安排
C、随便安排
答案:A
贪心规律:
按照奖励从大到小排序;
对每个任务,尽量安排在截止时间之前最靠后的空闲时间;
如果找不到空闲时间,就放弃这个任务。
3、单位时间任务调度通常使用什么算法?
A、贪心
B、深度优先搜索
C、广度优先搜索
答案:A
八、本课知识总结
今天,我们从活动安排出发,认识了三种任务安排问题。
孩子们需要掌握的重点不是记住所有代码,而是学会先分析题目。
时间安排三步思考法:
第一步:看目标
是想完成最多的任务,还是获得最多的奖励?
第二步:看规则
任务是否允许重叠?每个任务需要多长时间?有没有截止时间?
第三步:选算法
根据目标和规则,判断能不能使用贪心,还是需要动态规划等其他方法。
送给大家一句话:
先看目标,再定规则;
看清条件,选择算法!
下一课:贪心算法第 7 课:魔法工厂的流水线——两台机器怎样配合最快?
怎样安排工件的加工顺序,才能让两台机器尽量少等待,让所有工件尽早加工完成?
网硕互联帮助中心




评论前必须登录!
注册