活动选择问题

题目描述

假定有一个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

如下表

i1234567891011
si130535688212
fi4567991011121416

贪心选择

什么是贪心算法,我的理解是只选择目前来看最优的一个事件,选完这个之后剩下是原问题的子问题,那么每次只选择当前最优的事件这样的情况是不是原问题的最优解呢

即:

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 

该算法的时间复杂度与递归版本的一样。

补充

最优子结构:如果一个问题的最优解包含其子问题的最优解,则称此问题具有最优子结构性质。

同样具有贪心思想的还有哈夫曼树、最小生成树以及迪杰斯特拉最短路径。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
活动选择问题是经典的动态规划问题,可以使用贪心算法或动态规划算法解决。下面给出一个使用动态规划算法解决活动选择问题的 Python 代码示例: ```python def max_activities(activities, start, finish): # 按结束时间排序 sorted_activities = sorted(zip(finish, activities)) n = len(activities) dp = [0] * n dp[0] = 1 for i in range(1, n): dp[i] = max(dp[j] for j in range(i) if sorted_activities[j][0] <= sorted_activities[i][1]) + 1 return dp[n-1] # 示例 activities = ['A', 'B', 'C', 'D', 'E', 'F'] start = [1, 3, 0, 5, 8, 5] finish = [2, 4, 6, 7, 9, 9] print(max_activities(activities, start, finish)) # 输出 4,即选择 A、B、D、F 这 4 个活动 ``` 在上面的代码中,`max_activities` 函数接受三个参数,分别是 `activities`、`start` 和 `finish`。其中,`activities` 是一个列表,表示各个活动的名称;`start` 是一个列表,表示各个活动的开始时间;`finish` 是一个列表,表示各个活动的结束时间。 在函数中,首先将所有活动按照结束时间排序,然后使用一个 `dp` 列表存储每个活动作为最后一个活动时,能够选择的最多活动数量。初始时,第一个活动的 `dp` 值为 1,然后从第二个活动开始遍历,对于每个活动,找到所有结束时间早于它的活动,计算它们的 `dp` 值,并取最大值加 1 就是当前活动的 `dp` 值。最终,`dp` 列表中的最大值即为能够选择的最多活动数量。 需要注意的是,上面的代码中并没有输出具体选哪些活动,如果需要输出具体解,可以再使用一些技巧进行处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值