贪心算法:集合覆盖问题

贪心算法


贪心算法是一种解决问题的思路:每一步选择局部最优解,最终也许不会得到最优结果,但是也会接近最优结果。
贪心算法具有以下特点:

  1. 每一步选择局部最优解;
  2. 并非在任何情况下行之有效;
  3. 贪心算法简单易实现;

教室调度问题


假设有如下课程表,你希望将尽可能多的课程安排在某间教室上:

课程开始时间休息时间
美术9 AM10 AM
英语9:30 AM10:30 AM
数学10 AM11 AM
计算机10:30 AM11:30 AM
音乐11 AM12 PM

在这里插入图片描述

你希望在这间教室上尽可能多的课。如何选出尽可能多且时间不冲突的课程呢?贪心算法的做法是:每一步选择价值回报最大的操作。具体做法如下:

  • 选出结束最早的课,它就是要在这间教室上的第一堂课。
  • 接下来,必须选择第一堂课结束后才开始的课。同样,你选择结束最早的课,这将是要在这间教室上的第二堂课。
    重复这样做就能找出答案!最终选择的结果如下:

在这里插入图片描述


背包问题


假设你是个贪婪的小偷,背着可装35磅( 1 磅 ≈ 0.45 千 克 1磅≈0.45千克 10.45)重东西的背包,在商场伺机盗窃各种可装入背包的商品。
贪心算法的策略是:

  1. 盗窃可装入背包的最贵商品;
  2. 再盗窃还可装入背包的最贵商品,以此类推;

以这种方式可能并不能得到最优解。有时候,你只需找到一个能够大致解决问题的算法,此时贪婪算法正好可派上用场,因为它们实现起来很容易,得到的结果又与正确结果相当接近。记住,对有些问题,可能并不存在完美的解,例如 NP 难问题。


集合覆盖问题


集合覆盖问题: 选择最少的集合,覆盖全部的元素。
假设你办了个广播节目,要让全美 50 50 50 个州的听众都收听得到。为此,你需要决定在哪些广播台播出。在每个广播台播出都需要支付费用,因此你力图在尽可能少的广播台播出。现有广播台名单如下。

广播台覆盖的州
(1) KONEID,NV,UT
(2) KTWOWA,ID,MT
(3) KTHREEOR,NV,CA
(4) KFOURNV,UT
(5) KFIVECA,ZA

如何找出覆盖全美 50 50 50 个州的最小广播台集合呢?
最容易考虑到的可能是暴力求解法:列出每一种可能的广播集合(可能的子集有 2 n 2^n 2n个);在这些集合中选出能覆盖全美 50 50 50 个州的最小集合数。假设每秒可筛选出 10 10 10 个子集,其花费的时间如下:

在这里插入图片描述

没有任何算法可以足够快地解决这个问题!怎么办呢?使用贪婪算法可以得到非常接近的解。

  1. 选出这样一个广播台,即它覆盖了最多的未被选择的州。即便这个广播台覆盖了一些已经选择的州,也没有关系;
  2. 重复第一步,直到覆盖了所有的州。

贪婪算法是不错的选择,它们不仅简单,而且通常运行速度很快。在这个例子中,贪婪算法的运行时间为 O ( n 2 ) O(n^2) O(n2),其中 n n n 为广播台数量。

解决上述问题的代码如下:

  • 第一步:准备工作:构建数据结构;
    • 用集合 states_needed 存储所有的州;
    • 用字典表示电视台 stations 的覆盖面;
states_needed = set(['mt', 'wa', 'or', 'id', 'nv', 'ut', 'ca', 'az']) # 包含要覆盖的州

stations = {} # 存储电视台集合
stations["kone"] = set(["id", "nv", "ut"])
stations["ktwo"] = set(["wa", "id", "mt"])
stations["kthree"] = set(["or", "nv", "ca"])
stations["kfour"] = set(["nv", "ut"])
stations["kfive"] = set(["ca", "az"])

final_stations = set() # 存储最终电视台的集合
  • 第二步:利用贪心算法求解最少的电视台
    • 选出这样一个广播台,即它覆盖了最多的未被选择的州。我们将这个广播台存储在 best_station 中;
    • 重复第一步,直到覆盖了所有的州。
while states_needed:
    best_station = None # 最佳电视台:覆盖了最多未覆盖的州
    states_covered = set() # 最佳电视台与未覆盖州集合的交集
    for station, states in stations.items():
        covered = states_needed & states # 当前广播台与未覆盖州集合的交集
        if len(covered) > len(states_covered):
            states_covered = covered
            best_station = station
    
    states_needed -= states_covered # 更新未覆盖州的集合
    final_stations.add(best_station) # 添加贪心算法选择的电台

最终打印的结果可能如下:

print(final_stations)
# set(['ktwo', 'kthree', 'kone', 'kfive'])

暴力求解算法贪心算法 求解时间对比:

在这里插入图片描述


NP完全问题


什么样的问题称为 NP 完全问题呢? 就像集合覆盖问题一样,你需要计算所有的解,并从中选择优的解。NP 完全问题以计算量大无法求得最优解而著称,贪心策略是解决 NP 完全问题一个很重要且有效的方法。它也许不能得到最优解,但是可以得到最接近最优解的结果。


总结


  1. 贪婪算法寻找局部最优解,企图以这种方式获得全局最优解。
  2. 对于NP完全问题,还没有找到快速解决方案。
  3. 面临NP完全问题时,最佳的做法是使用近似算法。
  4. 贪婪算法易于实现、运行速度快,是不错的近似算法。
### 使用MATLAB实现贪心算法解决集合覆盖问题 #### 示例代码解释 为了展示如何使用MATLAB通过贪心算法解决集合覆盖问题,下面提供了完整的示例代码及其详细的说明。 ```matlab function coveredSets = greedySetCover(universe, subsets) % universe 是待覆盖的元素列表 % subsets 是候选子集的元胞数组,每个元素是一个包含该子集中元素的向量 % 初始化已覆盖的元素为空集 coveredElements = []; % 将所有未被覆盖的原始宇宙中的元素加入到剩余需要处理的部分 remainingElements = setdiff(universe, coveredElements); % 存储最终选择出来的子集索引 selectedSubsetsIndices = []; while ~isempty(remainingElements) bestSubsetIndex = -1; maxCoverageCount = 0; for i = 1:length(subsets) currentSubset = subsets{i}; % 计算当前子集能新覆盖多少个之前没覆盖过的元素数量 newCoverage = length(intersect(setdiff(currentSubset, coveredElements), remainingElements)); if newCoverage > maxCoverageCount maxCoverageCount = newCoverage; bestSubsetIndex = i; end end % 更新已经覆盖的元素以及移除已经被完全覆盖掉的目标元素 coveredElements = union(coveredElements, subsets{bestSubsetIndex}); remainingElements = setdiff(remainingElements, subsets{bestSubsetIndex}); % 添加最佳子集索引至结果中 selectedSubsetsIndices(end+1) = bestSubsetIndex; % 移除已被选取的最佳子集以防重复计算 subsets(bestSubsetIndex) = []; end % 返回所选子集对应的原subsets里的实际内容而非仅有的索引位置 coveredSets = {subsets(selectedSubsetsIndices)}; end ``` 这段程序定义了一个名为`greedySetCover`的功能函数,接受两个参数:一个是代表整个目标域(即想要全部覆盖的一组独特项)的变量`universe`; 另外一个是包含了多个不同子集的单元格数组`subsets`. 函数内部采用循环结构不断挑选能够带来最多新增覆盖率的一个子集直到所有的项目都被成功覆盖为止。每一次迭代过程中都会更新那些已经被涵盖进去的数据,并且记录下用来构成解决方案的具体子集们[^4].
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值