《算法图解》——第八章 贪婪算法
第八章 贪婪算法
1 简单的贪婪算法
每步都采取最优的做法,每步都选择局部最优解。
2 背包问题
有些情况下,完美是优秀的敌人。如果你只需要找到一个大致解决问题的算法,贪婪算法挺不错,因为实现容易,结果与正确结果相当接近。
练习
8.1 你在一家家具公司工作,需要将家具发往全国各地,为此你需要将箱子装上卡车。每个箱子的尺寸各不相同,你需要尽可能利用每辆卡车的空间,为此你将如何选择要装上卡车的箱子呢?请设计一种贪婪算法。使用这种算法能得到最优解吗?
选择可以装入卡车中最大的箱子,不断重复,直到不能再装,这种算法得不到最优解。
8.2 你要去欧洲旅行,总行程为7天。对于每个旅游胜地,你都给它分配一个价值——表示你有多想去那里看看,并估算出需要多长时间。你如何将这次旅行的价值最大化?请设计一种贪婪算法。使用这种算法能得到最优解吗?
不断地挑选可以在剩下的时间内完成的价值最大的活动,知道剩下的时间不能够完成任何活动为止。同样这种算法得不到最优解。
3 集合覆盖问题
假设你办了个广播节目,要让全美50个州的听众都收听得到。为此,你需要决定在哪些广播台播出。在每个广播台播出都需要支付费用,因此你力图在尽可能少的广播台播出。每个广播台都覆盖特定的区域,不同广播台的覆盖区域可能重叠。
具体方法如下:
①列出每个可能的广播台集合,这被称为幂集(power set)。可能的子集有2**n个。
②在这些集合中,选出覆盖全美50个州的最小集合。
由于可能的子集有2**n个,因此运行时间为O(2**n)。
用贪婪算法可得到非常接近的解:
①选出这样一个广播台,它覆盖了最多的未覆盖的州。即使有重复的州也没有关系
②重复第一步,直到覆盖了所有的州
这是一种近似算法。判断近似算法优劣的标准如下:
①速度有多快
②得到的近似解与最优解的接近程度。在这个例子中贪婪算法的运行时间为O(n**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()
②计算答案
需要从中选择覆盖了最多的未覆盖州的广播台。将整个广播台存储在best_station 中。
states_needed = (["mt", "wa", "or", "id", "nv", "ut","ca", "az"]) #这个代码有问题没解决
stations = {}
stations["kone"] = (["id", "nv", "ut"])
stations["ktwo"] = (["wa", "id", "mt"])
stations["kthree"] = (["or", "nv", "ca"])
stations["kfour"] = (["nv", "ut"])
stations["kfive"] = (["ca", "az"])
final_stations = ()
while states_needed:
best_station = ()
states_covered = ()
for station, states_for_station in stations.items():
covered = states_needed and states_for_station
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'])
states_covered 是一个集合,包含该广播台覆盖的所有未覆盖的州。 for 循环迭代每个广播台,并确定它是否是最佳的广播台。下面来看看这个 for 循环的循环体。
covered 是一个集合,包含同时出现在 states_needed 和states_for_station 中的州;
贪婪算法和精确算法的运行时间对比:
练习
下面各种算法是否是贪婪算法。
8.3 快速排序。否
8.4 广度优先搜索。是
8.5 狄克斯特拉算法。是
4 NP完全问题
旅行商问题详解:
2个城市时,2条;3个城市时,6条;4个城市时,24条;同理:N个城市就是N!条,这被称为阶乘函数。
如何识别NP完全问题:
①元素较少时算法的运行速度非常快,但随着元素数量的增加,速度会变得非常慢。
②涉及“所有组合”的问题通常是NP完全问题。
③不能将问题分成小问题,必须考虑各种可能的情况。这可能是NP完全问题。
④如果问题涉及序列(如旅行商问题中的城市序列)且难以解决,它可能就是NP完全问题。
⑤如果问题涉及集合(如广播台集合)且难以解决,它可能就是NP完全问题。
⑥如果问题可转换为集合覆盖问题或旅行商问题,那它肯定是NP完全问题。
练习
8.6 有个邮递员负责给20个家庭送信,需要找出经过这20个家庭的最短路径。请问这是一个NP完全问题吗?类似旅行商问题,是一个NP完全问题
8.7 在一堆人中找出最大的朋友圈(即其中任何两个人都相识)是NP完全问题吗?类似集合覆盖问题,同样是一个NP完全问题
8.8 你要制作美国地图,需要用不同的颜色标出相邻的州。为此,你需要确定最少需要使用多少种颜色,才能确保任何两个相邻州的颜色都不同。请问这是NP完全问题吗?也是
5 小结
贪婪算法寻找局部最优解,企图以这种方式获得全局最优解。
对于NP完全问题,还没有找到快速解决方案。
面临NP完全问题时,最佳的做法是使用近似算法。
贪婪算法易于实现、运行速度快,是不错的近似算法。