1、概念
贪婪算法: 每步都采取最优的做法。
优点: 简单易行。
特点: 得到的结果是最优解或者与最优解相当接近。所以也不是任何情况下行之有效的。可以看成是一个近似算法。
2、教室课表调度问题
课程表如下,如何选出尽可能多且时间不冲突的课程呢?
英语 | 8 :00 AM | 9:00 AM |
---|---|---|
思修 | 8:30 AM | 9:30 AM |
C语言 | 9:00 AM | 10:00 AM |
计算机概论 | 9:40 AM | 10:40 AM |
Python | 10:20 AM | 11:20 AM |
贪婪算法做法具体如下:
- 选出最早结束的课,就是要在这间教室上的第一堂课。
- 接下来,必须选择第一堂课后才开始的课。同样,选择最早结束的课,这将是要在这间教室上的第二堂课。以此类推。
最终贪婪算法求出这间教室课表安排顺序为: 英语、C语言、Python。
3、背包问题
举例背包问题主要讨论贪婪算法并非是在任何情况下行之有效的。
假设你是一个小偷,你背着可装35磅重东西的背包,在商场伺机盗窃各种可装入背包的商品。商场的商品重量和价格如下:
商品名 | 价格 | 重量 |
---|---|---|
音响 | 6000美元 | 30磅 |
笔记本电脑 | 4000美元 | 20磅 |
吉他 | 3000美元 | 15磅 |
采用贪婪策略,具体做法如下:
- 盗窃可装入背包的最贵商品。
- 再盗窃还可装入背包的最贵商品,以此类推。
采用贪婪策略求出只偷到了6000美元的东西,但是如果不偷音响,而是偷笔记本和吉他,那将偷到7000美元的东西。
启示:
在某些情况下,完美是优秀的敌人。有时候,你只需要找到一个能够大致解决问题的算法,此时贪婪算法正好可派上用场,因为他们实现起来很容易,得到的结果又与正确结果相当接近。
4、集合覆盖问题
假设你办了一个广播节目,要让全国的听众都能收听得到,为此你需要决定在哪些广播台播出,在每个广播台都需要支付费用,因此你力图在尽可能少的广播台播出,然而每个广播台都覆盖特定的区域,不同广播台的覆盖区域可能重叠。如何找出覆盖全国34个省级区域的最小广播台集合呢?看起来很简单,但实现起来非常难。具体方法如下:
- 列出每个可能的广播台集合,这个被称为幂集,可能的子集为 2 n 2^n 2n 个。
- 在这些集合中,选出覆盖全中国34个省级区域的最小集合。
此时问题来了,这时算法的运行时间为
O
(
2
n
)
O(2^n)
O(2n) ,如果广播台很多,没有任何算法能足够快地解决这个问题。怎么办呢?
贪婪算法可以化解危机,使用贪婪算法可以得到非常接近的解。
使用贪婪算法步骤如下:
- 选出这样一个广播台,即它覆盖了最多未覆盖州。即便这个广播台覆盖了一些已覆盖的省级区域,也没有关系。
- 重复第一步,直到覆盖了所有的省级区域。
这个算法的运行时间为 O ( n 2 ) O(n^2) O(n2),其中n为广播台数量。
代码(Python3):
-
出于简化考虑,假设省级区域只有江西省、广东省、湖北省、福建省、广西省、云南省、四川省、山东省。可以创建一个列表,其中包含要覆盖的省级区域。
states_needed = set(['江西省','广东省','湖北省','福建省','广西省','云南省','四川省','山东省']) #传入一个列表,将其转换为集合
-
广播台清单,使用散列表来表示。
stations = {} stations['频道1'] = set(['江西省','广东省','福建省']) stations['频道2'] = set(['四川省','山东省','广西省']) stations['频道3'] = set(['山东省','湖北省','福建省']) stations['频道4'] = set(['云南省','湖北省','江西省'])
-
最后使用一个集合来存储最终选择的广播台。
final_stations = set()
-
开始计算,遍历所有的广播台,从中选择覆盖了最多的未覆盖的广播台。将这个广播台存储在best_station中。把该广播台覆盖的所有未覆盖的省级区域放入states_covered集合。for循环每个广播台,并确定它是否是最佳的广播台。for循环结束后将best_station添加到最终的广播台列表中。
best_station = None states_covered = set() for station,states_for_station in stations.items(): covered = states_needed & states_for_station #取交集 if len(covered) > len(states_covered): best_station = station states_covered = covered final_station.add(best_station)
-
由于覆盖了一些省级区域,因此不需要覆盖这些省级区域,还需更新states_needed。
states_needed -= states_covered
-
不断循环,直到states_needed 为空,这个循环完整代码:
while states_needed: best_station = None states_covered = set() for station,states_for_station in stations.items(): covered = states_needed & states_for_station #取交集 if len(covered) > len(states_covered): best_station = station states_covered = covered states_needed -= states_covered final_stations.add(best_station)
-
最后打印final_stations,输出:
5、NP完全问题
NP完全问题: 多项式复杂程度的非确定性问题,简单的理解就是 “难解”,需要计算所有的解,并从中选出最小/最短的那个。所以对于NP完全问题,一般采用近似求解,采用算法如:贪婪算法。
判断是否为NP完全问题:
- 元素较少时算法运行的速度非常快,但随着元素数量的增加,速度会变得非常慢,这时可能是NP完全问题。
- 涉及”所有组合“的问题通常是NP问题。
- 不能将问题分成小问题,必须考虑各种可能的情况。这个可能是NP完全问题。
- 如果问题涉及序列(旅行商问题中的城市序列)且难以解决,它可能就是NP完全问题。
- 如果问题涉及集合(如广播集合)且难以解决,它可能就是NP完全问题。
- 如果问题可转换未集合覆盖或旅行商问题,那它肯定是NP完全问题。
6、总结
- 贪婪算法寻找局部最优解,企图以这种方式获取全局最优解。
- 对于NP完全问题,还没有找到快速解决的方案。
- 面临NP完全问题,最佳的做法是使用近似算法。
- 贪婪算法易于实现、运行速度快,是不错的近似算法。