1.NP完全问题(Non-deterministic Polynomial Complete)
NP完全问题(多项式复杂程度的非确定性问题)的简单定义是,以难解著称,没有快速算法的问题,如旅行商问题和集合覆盖问题。
要判断问题是不是NP完全问题很难,易于解决的问题和NP完全问题的差别通常很小。为避免浪费时间去寻找解决它们的快速算法,以下是一些识别NP完全问题的方法:
❑ 元素较少时算法的运行速度非常快,但随着元素数量的增加,速度会变得非常慢。
❑ 涉及“所有组合”的问题通常是NP完全问题。
❑ 一个问题不能分成小问题,必须考虑各种可能的情况。这可能是NP完全问题。
❑ 如果问题涉及序列且难以解决(如旅行商问题中的城市序列),它可能就是NP完全问题。
❑ 如果问题涉及集合且难以解决(如广播台集合),它可能就是NP完全问题。
❑ 如果问题可转换为集合覆盖问题或旅行商问题,那它肯定是NP完全问题。
对于NP完全问题,还没有找到快速解决方案,很多非常聪明的人都认为,根本不可能编写出可快速解决这些问题的算法。面临NP完全问题时,最佳的做法是使用近似算法。
2.贪婪算法——最简单的近似算法
近似算法(approximation algorithm)
在获得精确解需要的时间太长时,可使用近似算法。使用它们可快速找到NP完全问题的近似解。
判断近似算法优劣的标准如下:
❑ 速度有多快;
❑ 得到的近似解与最优解的接近程度。
贪婪算法
有时候,你只需找到一个能够大致解决问题的算法,此时贪婪算法正好可派上用场,因为它们实现起来很容易,得到的结果又与正确结果相当接近。
贪婪算法寻找 局部最优解,企图以这种方式获得全局最优解。
贪婪算法易于实现、运行速度快,是不错的近似算法。
2.1教室调度问题(序列问题)
假设有如下课程表,你希望将尽可能多的课程安排在某间教室上。
你希望在这间教室上尽可能多的课。如何选出尽可能多且时间不冲突的课程呢?这个问题好像很难,但算法可能简单得让你大吃一惊。
具体做法如下:
(1) 选出结束最早的课,它就是要在这间教室上的第一堂课。
(2) 接下来,必须选择第一堂课结束后才开始的课。同样,你选择结束最早的课,这将是要在这间教室上的第二堂课。重复这样做就能找出答案!
贪婪算法很简单:每步都采取最优的做法。在这个示例中,你每次都选择结束最早的课。用专业术语说,就是你每步都选择局部最优解,最终得到的就是全局最优解。显然,贪婪算法并非在任何情况下都行之有效,但它易于实现!
2.2 广播覆盖问题(集合覆盖问题)
假设你办了个广播节目,要让全美50个州的听众都收听得到。为此,你需要决定在哪些广播台播出。在每个广播台播出都需要支付费用,因此你力图在尽可能少的广播台播出。
如何找出覆盖全美50个州的最小广播台集合呢?听起来很容易,但其实非常难。具体方法如下。
(1) 列出每个可能的广播台集合,这被称为幂集(power set)。可能的子集有2n个。
(2) 在这些集合中,选出覆盖全美50个州的最小集合。问题是计算每个可能的广播台子集需要很长时间。由于可能的子集有2n个,因此运行时间为O(2n)。如果广播台不多,只有5~10个,这是可行的。但如果广播台很多,结果将如何呢?随着广播台的增多,需要的时间将激增。没有任何算法可以足够快地解决这个问题!
怎么办呢?
获得精确解需要的时间太长时,近似算法贪婪算法可化解危机!
使用下面的贪婪算法可得到非常接近的解。
(1) 选出这样一个广播台,即它覆盖了最多的未覆盖州。即便这个广播台覆盖了一些已覆盖的州,也没有关系。
(2) 重复第一步,直到覆盖了所有的州。这是一种近似算法(approximation algorithm)。
贪婪算法是不错的选择,它们不仅简单,而且通常运行速度很快。在这个例子中,贪婪算法的运行时间为O(n2),其中n为广播台数量。
算法实现:
# 需要覆盖广播台的州
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中
best_station = None
#储存该广播台覆盖的所有未覆盖的州
states_covered = set()
for station, states_for_station in stations.items():
#同时出现在states_needed和states_for_station中的州:当前广播台覆盖的一系列还未覆盖的州
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)
print(final_stations)
练习
8.1 你在一家家具公司工作,需要将家具发往全国各地,为此你需要将箱子装上卡车。每个箱子的尺寸各不相同,你需要尽可能利用每辆卡车的空间,为此你将如何选择要装上卡车的箱子呢?请设计一种贪婪算法。使用这种算法能得到最优解吗?
8.2 你要去欧洲旅行,总行程为7天。对于每个旅游胜地,你都给它分配一个价值——表示你有多想去那里看看,并估算出需要多长时间。你如何将这次旅行的价值最大化?请设计一种贪婪算法。使用这种算法能得到最优解吗?
8.3~8.5下面各种算法是否是贪婪算法。
8.3 快速排序。
8.4 广度优先搜索。
8.5 狄克斯特拉算法。
8.6 有个邮递员负责给20个家庭送信,需要找出经过这20个家庭的最短路径。请问这是一个NP完全问题吗?
8.7 在一堆人中找出最大的朋友圈(即其中任何两个人都相识)是NP完全问题吗?
8.8 你要制作美国地图,需要用不同的颜色标出相邻的州。为此,你需要确定最少需要使用多少种颜色,才能确保任何两个相邻州的颜色都不同。请问这是NP完全问题吗?
答案
8.1 一种贪婪策略是,选择可装入卡车剩余空间内的最大箱子,并重复这个过程,直到不能再装入箱子为止。使用这种算法不能得到最优解。
8.2 不断地挑选可在余下的时间内完成的价值最大的活动,直到余下的时间不够完成任何活动为止。使用这种算法不能得到最优解。
8.3 不是。
8.4 是。
8.5 是。
8.6 是。
8.7 是。
8.8 是。