使用贪心算法与动态规划团灭活动选择问题

本文针对活动选择问题,对不同情况分别使用了贪心算法和动态规划。文中两个问题基本上全部涵盖的活动选择问题,所以说起到了团灭这类问题,看完本文就再也不怕活动选择问题啦。
在这里插入图片描述
(图片来源:一个博主文章里的)我也想用一下,哈哈。

附上Leetcode上和活动选择类似的题,希望大家能举一反三,活学活用。
Leetcode 1235. 规划兼职工作

一 活动选择问题:选择活动的最大个数

1.1 问题背景
在这里插入图片描述
1.2 贪心策略
下面有三种贪心策略,只有第三种是正确的。这是因为策略3:最早结束活动优先
选择最早结束的活动,可以给后面的活动留更大的选择空间。
在这里插入图片描述
在这里插入图片描述
1.3 代码实现
贪心算法的时间复杂度为O(nlogn),也就是排序的时间复杂度

class Solution1: 
    def activitiesSelect(self, activities):
        # 把活动集合按照每个活动的结束时间升序排序
        activities.sort(key=lambda x :x[1])
        # print(activities)
        res = [activities[0]]   # 初始化
        n = len(activities)
        for i in range(1, n):  # 检查每个活动
            if activities[i][0] >= res[-1][1]:  # 下一个开始活动的时间大于等于,前一个活动结束的时间
                res.append(activities[i])
        return res

activities = [[3,5], [0,6],[1,4], [8,11], [8,12], [5,7], [3,9], [5,9], [6,10],  [2,14], [12,16]]
print(Solution1().activitiesSelect(activities))

输出结果

[(1, 4), (5, 7), (8, 11), (12, 16)]

说明:如果活动的开始时间与结束时间是分开的,首先将两者对应合并成如上述activities列表。然后再进行接下来的过程即可。
方法也十分简单,如下:

start = [1,3,0,4,3,5,6,8,8,2,12]  # 活动开始时间
end = [4,5,6,7,9,9,10,11,12,14,16]  # 活动结束时间
activities = []
for i in range(len(start)):
    activities.append([start[i], end[i]])
print(activities)

二 带权活动选择问题

扩展问题:会场出租,选择出租的活动时间不能冲突,活动出租收益各不相同。怎样选让收益总和最大?
2.1 问题定义
这个问题就是在第一个问题的基础上增加了一个活动权重,因此可称为带权活动选择问题。

在这里插入图片描述
在这里插入图片描述
2.2 问题比较
第一个问题可看作权重都为1的带权活动选择问题。那么是否还能继续使用贪心算法呢?从下面的图可知,使用贪心算法是不正确的。
在这里插入图片描述
2.3 从贪心策略到动态规划
存在重叠子问题,使用动态规划求解。但是在这之前首先要进行的是数据预处理。
排序:按活动结束时间升序
求𝒑[𝒊]:在𝒂𝒊开始前最后结束的活动
在这里插入图片描述
排序
在这里插入图片描述

求p[i]
排序后使用用两个循环遍历求p[i]
在这里插入图片描述
2.4 递推关系建立:构造递推公式
在这里插入图片描述
计算实例结果
在这里插入图片描述
2.5 代码实现
时间复杂度是O(n^2), 就是在求p[i]是使用了两层循环。其余具体过程详见代码,注释比较完整。

class Solution2: # 带权活动选择问题
    def rentActiv(self, act):
        # 预处理
        n =len(act)
        act.sort(key=lambda x: x[1])  # 排序
        # print(act)
        p = [0] * (n)  # 在a_i开始前最后结束的活动
        for i in range(1, n):
            for j in range(i-1, -1, -1):
                if act[j][1] <= act[i][0]:  # act[i]的开始时间大于等于act[j]的结束时间,并且要act[j]是最后结束的活动
                    p[i] = j + 1  # 这里为了让活动与下标一样,+1
                    break  # 当第一个满足判断条件的就停止,此时的j就是在act[i]开始前最后结束的活动
        # return p
        # 动态规划
        dp = [0] * (n+1)
        rec = [0] * n
        for j in range(0, n): # D[j] = max(D[p[j]] + w[j], D[j-1])
            if dp[p[j]] + act[j][2] > dp[j]:
                dp[j+1] = dp[p[j]] + act[j][2]
                rec[j] = 1
            else:
                dp[j+1] = dp[j]
                rec[j] = 0
        return dp, p, rec

if __name__ == "__main__":
    activities = [[1,4,1],[3,5,6], [0,6,4], [4,7,7], [3,9,3], [5,9,12], [6,10,2], [8,11,9], [8,12,11], [2,14,8]]
    dp, p, rec = Solution2().rentActiv(activities)
    print("dp:", dp)
    print("rec:", rec)
    print("revenue:",dp[-1])
    selectActivities = []
    k = len(activities)-1
    while k >= 0:
        if rec[k] == 1:
            selectActivities.append(activities[k])
             # 回溯子问题
            k = p[k] - 1 # 这里要减去1,是因为在记录p数组的时候加了1,母的是为了与activities[k]对应
        else:
            k -= 1  # 不选择活动a[k]
    selectActivities.reverse()
    print("选择方案:", selectActivities)

输出结果

dp: [0, 1, 6, 6, 8, 8, 18, 18, 18, 19, 19]
rec: [1, 1, 0, 1, 0, 1, 0, 0, 1, 0]
revenue: 19
选择方案: [[1, 4, 1], [4, 7, 7], [8, 12, 11]]

由输出结果这与计算结果的一样,说明方法是可行的。

附上伪代码
在这里插入图片描述
在这里插入图片描述

参考资源

算法设计与分析—童咏昕 https://www.icourse163.org/learn/BUAA-1449777166?tid=1463474515#/learn/announce

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黄波波19

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值