贪婪算法
- 教室调度问题
- 背包问题
- 集合覆盖问题
- NP完全问题
教室调度问题
假设有如下课程,要尽可能多地将它们安排在一间教室上:
| 课程 | 开始时间 | 结束时间 |
|---|---|---|
| 美术 | 9:00 | 10:00 |
| 英语 | 9:30 | 10:30 |
| 数学 | 10:00 | 11:00 |
| 计算机 | 10:30 | 11:30 |
| 音乐 | 11:00 | 12:00 |
贪婪算法的做法:
- 选出结束最早的课,它就是要在这间教室上的第一堂课(美术 9:00-10:00)
- 在与已选课不冲突的课中,选出结束最早的课(数学 10:00-11:00)
- 重复,选出音乐 11:00-12:00
这个贪婪策略得到了最优解。贪婪算法寻找局部最优解,企图以这种方式获得全局最优解。
背包问题
假设你是个贪婪的小偷,背着可装 35 磅重东西的背包,可盗窃的商品有:
- 音响:30 磅,3000 美元
- 笔记本电脑:20 磅,2000 美元
- 吉他:15 磅,1500 美元
贪婪策略:先偷最贵的(音响 3000 美元),但背包没有空间装其他东西了。然而偷笔记本电脑和吉他的总价为 3500 美元!贪婪策略显然不能获得最优解。
启示:在有些情况下,完美是优秀的敌人。有时候只需找到一个能够大致解决问题的算法,此时贪婪算法正好可派上用场,因为它们实现起来很容易,得到的结果又与正确结果相当接近。
集合覆盖问题
假设你办了个广播节目,要让全美 50 个州的听众都收听得到,需要决定在哪些广播台播出。每个广播台覆盖特定的州,不同广播台的覆盖区域可能重叠。
精确解:列出每个可能的广播台集合(幂集),选出覆盖全 50 个州的最小集合。可能的子集有 2ⁿ 个,运行时间为 O(2ⁿ),广播台一多就无法在合理时间内计算。
近似算法(贪婪算法):
- 选出覆盖了最多未覆盖州的广播台(即便覆盖了一些已覆盖的州也没关系)
- 重复第一步,直到覆盖了所有的州
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()
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):
best_station = station
states_covered = covered
states_needed -= states_covered
final_stations.add(best_station)
print final_stations # set(['ktwo', 'kthree', 'kone', 'kfive'])
集合操作:
>>> fruits = set(["avocado", "tomato", "banana"])
>>> vegetables = set(["beets", "carrots", "tomato"])
>>> fruits | vegetables # 并集
set(["avocado", "beets", "carrots", "tomato", "banana"])
>>> fruits & vegetables # 交集
set(["tomato"])
>>> fruits - vegetables # 差集
set(["avocado", "banana"])
贪婪算法的运行时间为 O(n²),比 O(2ⁿ) 快得多。
判断近似算法优劣的标准:速度有多快;得到的近似解与最优解的接近程度。
NP完全问题
旅行商问题和集合覆盖问题都属于 NP 完全问题——以难解著称的问题。很多非常聪明的人都认为,根本不可能编写出可快速解决这些问题的算法。
旅行商问题详解:旅行商要前往 n 个城市,找出最短路径。可能的路径数为 n!:
- 2 个城市:2 条
- 3 个城市:6 条
- 4 个城市:24 条
- 5 个城市:120 条
- 10 个城市:3 628 800 条
可能的路线数增加得非常快,城市很多时根本无法找出正确解。
如何识别 NP 完全问题:
- 元素较少时算法的运行速度非常快,但随着元素数量增加,速度会变得非常慢
- 涉及「所有组合」的问题通常是 NP 完全问题
- 不能将问题分成小问题,必须考虑各种可能的情况
- 如果问题涉及序列(如城市序列)且难以解决,可能是 NP 完全问题
- 如果问题涉及集合(如广播台集合)且难以解决,可能是 NP 完全问题
- 如果问题可转换为集合覆盖问题或旅行商问题,那它肯定是 NP 完全问题
小结
- 贪婪算法寻找局部最优解,企图以这种方式获得全局最优解
- 对于 NP 完全问题,还没有找到快速解决方案
- 面临 NP 完全问题时,最佳做法是使用近似算法
- 贪婪算法易于实现、运行速度快,是不错的近似算法