一、贪心算法概述
贪心算法的核心思想可以总结为:贪心算法总是做出在当前看来最好的选择。
也就是说贪心算法并不从整体最优考虑,它所做出的选择只是在某种意义上的局部最优选择。当然,希望贪心算法得到的最终结果也是整体最优的。
虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解,如单源最短路经问题,最小生成树问题等。虽然在一些情况下,即使贪心算法不能得到整体最优解,但其最终结果却是最优解的很好近似。
二、集合覆盖问题
2.1 问题描述
假设你办了个广播节目,要让国内的 8 个重要城市的听众都收听得到。为此,你需要决定在哪些广播台播出。在每个广播台播出都需要支付费用,因此你力图在尽可能少的广播台播出。现有广播台名单如下。
广播台 | 覆盖地区 |
---|---|
K1 | 北京、上海、天津 |
K2 | 广州、北京、深圳 |
K3 | 成都、上海、杭州 |
K4 | 上海、天津 |
K5 | 杭州、大连 |
如何选择最少的广播台,让所有的城市都可以接收到信号?
2.2 集合覆盖问题的贪心算法
每个广播台都覆盖特定的区域,不通过的广播覆盖的区域可能是重叠的。如何找出覆盖 8 个城市的最小广播台集合呢?
首先我们最容易想到的办法就是穷举法:
- 列出每个可能的广播台的集合,可能的集合有 2ⁿ 个;
- 在这个集合中,选出能够覆盖 8 个城市的最小集合。
上面这个方法确实可以求得最终结果,但是问题在于 n 个广播台可能的集合有 2ⁿ 个,因此运行时间为 O(2ⁿ)。如果广播台不多,比如只有 5~10 个,这个方法倒还可行。但是一旦问题的规模变大,广播台的数量增多,需要的时间将激增。假设每秒可以计算出 10 个集合,那么所需的时间如下表所示:
广播台数量 | 需要的时间 |
---|---|
5 | 3.2 秒 |
10 | 102.4 秒 |
32 | 13.6 年 |
100 | 4 x 10²¹ 年 |
显然,使用穷举所有集合的方法不是一个明智的选择。
针对这类集合覆盖问题,贪心算法是一个比较合适的算法,虽然贪心算法不一定能够得到最优解,但是它可以得到非常接近的解。
对于本题,贪心算法的基本思想如下:
- 优先选择出覆盖了最多未被覆盖的城市的广播台。即使这个广播台覆盖了一些已覆盖的城市也没关系;
- 重复第一步,直到选出的广播台覆盖了所有的城市。
可以看到,使用贪心算法解决集合覆盖问题的思路贯彻着 “选择当下看来最好的选择” 这样的思想。简单来说就是走好每一步路,最终得到的结果必定不会太差。
贪心算法不仅简单,而且运行速度也很快。对于本题,贪心算法的运行时间为 O(n²),其中 n 为广播台的数量。
2.3 代码实现
首先来说一下代码实现的思路:
- 创建一个集合
cities
,存放所有的城市; - 创建一个散列表
broadcast
,广播站作为键,对应的城市作为值; - 创建一个集合
selectedList
,用于存放已选择的广播站; - 遍历散列表,找到覆盖了最多未覆盖的城市的广播站,将其加入到已选择广播站集合
selectedList
中; - 将上一步选择的广播站所覆盖的城市从城市集合
cities
中移除,同时将广播站从散列表中移除; - 重复执行 4、5 步,直至城市集合为空。
完整的代码实现如下 :
public static void main(String[] args) {
HashMap<String, Set> broadcast = new HashMap<>(); // 用于存放广播和覆盖的城市
HashMap<String, Set> selectedList = new HashMap<>(