题目描述
假定有一个n个活动的集合S={a1,a2,……,an},这些活动使用同一个资源,而这个资源在某个时刻只能供一个活动使用。每个活动有一个开始时间si和一个结束时间fi,其中0<=si<fi<∞。如果被选中,任务ai发生在半开时间区间[si,fi)期间。如果两个活动ai和aj满足[si,fi)和[sj,fj)不重叠,则称他们是兼容的.也就是说,若si>=fj或sj>=fi,则ai和aj是兼容的。
在活动选择问题中,我们希望选出一个最大兼容活动集。
假定活动已按结束时间的单调递增顺序排序:
f1<=f2<=f3<=……<=f(n-1)<=fn
如下表
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
si | 1 | 3 | 0 | 5 | 3 | 5 | 6 | 8 | 8 | 2 | 12 |
fi | 4 | 5 | 6 | 7 | 9 | 9 | 10 | 11 | 12 | 14 | 16 |
贪心选择
什么是贪心算法,我的理解是只选择目前来看最优的一个事件,选完这个之后剩下是原问题的子问题,那么每次只选择当前最优的事件这样的情况是不是原问题的最优解呢
即:
1、对其做出一次选择之后,只剩下一个子问题需要求解;
2、证明做出贪心选择后,原问题总是存在最优解,即贪心选择总是安全的。
3、证明做出贪心选择后,剩余的字问题满足性质:其最优解与贪心选择组合即可得到原问题的最优解,这样就得到了最优子结构。
对于这道题,我们应该选择一个最早结束的活动——即a1,因为越早结束,剩下的资源就可供它之后的尽量多的活动使用。
做出上述贪心选择后,只剩下一个子问题供我们考虑:寻找在a1之后开始的行动。
那么上述所寻找的一定是最优解吗?即 贪心选择——最早结束的活动——总是最优解的一部分吗?
我们可以采用反证法来证明:如果上述贪心选择不是最优解,那么一定有最优解A'k的活动数多于贪心解Ak。也即存在活动,ai,aj,ak在A'k中不在Ak(... ai, am...)中。
但是我们的贪心选择是每次选择ai后最早结束的活动,所以不存在ai,aj,ak在A'k中不在Ak中,也即是,最优解A'k一定等于贪心解Ak。
让我们回到该问题
递归总是容易理解的,我们先用递归的思想来考虑一下。
RECURSIVE-ACTIVITY-SELECTOR(s, f, k, n){ //其中s是开始时间数组,f是结束时间数组,k是指当前选择到活动号,n是n个活动
m = k+1;
while (m<=n&&s[m]<f[k])
m=m+1; //找到f[k]之后的最早结束的第一个活动
if(m<=n)
return {Am}∪RECURSIVE-ACTIVITY-SELECTOR(s, f, m, n)
else return NULL
我们来分析一下上述算法的执行过程。
我们第一次穿入的参数分别是开始时间数组,结束时间数组,当前“指针”的位置以及活动的个数(数组的长度)。
m=k+1,因为a0一定是在最优解中的,所以我们找它之后开始并且最先结束的一个活动,既是while循环的逻辑。
如果找到该活动,即m≤n,当前活动是最优解中的一个活动,并且递归找它后边的最优子结构。
如果不存在该活动,即找到了递归出口。
我们假设这些活动已经按照结束时间递增有序,那么上述算法的时间复杂度为O(n)。
这个算法的逻辑也很容易可以转换成迭代形势。过程RECURSIVE-ACTIVITY-SELECTOR几乎就是“尾递归”:它以一个对自身的递归调用再接一次并集操作结尾。将一个尾递归过程改为递归形势是非常直接的。
其迭代形式伪代码如下:
GREEDY-ACTIVITY-SELECTORY(s, f)
n = s.length
A = {a1}
k=1
for m = 2 to n
if s[m]≥f[k]
A = A∪{am}
k=m
return A
该算法的时间复杂度与递归版本的一样。
补充
最优子结构:如果一个问题的最优解包含其子问题的最优解,则称此问题具有最优子结构性质。
同样具有贪心思想的还有哈夫曼树、最小生成树以及迪杰斯特拉最短路径。