【有书共读】《算法图解》第八章读书笔记——贪婪算法

一,贪心算法的基本原理

贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。当然,希望贪心算法得到的最终结果也是整体最优的。虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。如单源最短路经问题,最小生成树问题等。在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。 

贪心算法求解的问题一般具有两个重要性质:贪心选择性质和最优子结构性质。

(1)所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优解的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。

(2)当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。

算法很简单:每步都采取最优的做法。你每步都选择局部最优解,最终得到的就是全局最优解。
<练习题>

8.1 你在一家家具公司工作,需要将家具发往全国各地,为此你需要将箱子装上卡车。每个箱子的尺寸各不相同,你需要尽可能利用每辆卡车的空间,为此你将如何选择要装上卡车的箱子呢?请设计一种贪婪算法。使用这种算法能得到最优解吗?

选择可以装入卡车中最大的箱子,不断重复,直到不能再装,这种算法得不到最优解。

8.2 你要去欧洲旅行,总行程为7天。对于每个旅游胜地,你都给它分配一个价值——表示你有多想去那里看看,并估算出需要多长时间。你如何将这次旅行的价值最大化?请设计一种贪婪算法。使用这种算法能得到最优解吗?

不断地挑选可以在剩下的时间内完成的价值最大的活动,知道剩下的时间不能够完成任何活动为止。同样这种算法得不到最优解


二,近似算法

在有些情况下,完美是优秀的敌人。有时候,你只需找到一个能够大致解决问题的算法,此时贪婪算法正好可派上用场,因为它们实现起来很容易,得到的结果又与正确结果相当接近。

  • 判断近似算法优劣的标准如下:
    1.速度有多快
    2.得到的近似解和最优解的接近程度

三,集合

集合类似于列表,只是不能包含重复的元素。
集合运算:并集、交集和差集。
1.并集意味着将集合合并。
2.交集意味着找出两个集合中都有的元素。
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 中的州;

贪婪算法和精确算法的运行时间对比:


四,NP完全问题

NP完全问题的简单定义是, 以难解著称的问题, 如旅行商问题和集合覆盖问题。 很多非常聪明的人都认为, 根本不可能编写出可快速解决这些问题的算法。
判断NP完全问题:

  • 元素较少时算法的运行速度非常快, 但随着元素数量的增加, 速度会变得非常慢。
  • 涉及“所有组合”的问题通常是NP完全问题。
  • 不能将问题分成小问题, 必须考虑各种可能的情况。 这可能是NP完全问题。
  • 如果问题涉及序列(如旅行商问题中的城市序列) 且难以解决, 它可能就是NP完全问题。
  • 如果问题涉及集合(如广播台集合) 且难以解决, 它可能就是NP完全问题。
  • 如果问题可转换为集合覆盖问题或旅行商问题, 那它肯定是NP完全问题。
<练习题>
8.6 有个邮递员负责给20个家庭送信,需要找出经过这20个家庭的最短路径。请问这是一
NP完全问题吗?

8.7 在一堆人中找出最大的朋友圈(即其中任何两个人都相识)是NP完全问题吗?

8.8 你要制作美国地图,需要用不同的颜色标出相邻的州。为此,你需要确定最少需要使
用多少种颜色,才能确保任何两个相邻州的颜色都不同。请问这是
NP完全问题吗? 

五,小结

  • 贪婪算法寻找局部最优解, 企图以这种方式获得全局最优解。
  • 对于NP完全问题, 还没有找到快速解决方案。
  • 面临NP完全问题时, 最佳的做法是使用近似算法。
  • 贪婪算法易于实现、 运行速度快, 是不错的近似算法。

全部评论
代码有问题别往上贴好吗
点赞 回复 分享
发布于 2018-10-05 15:46

相关推荐

LZStarV:冲就好了,就算真的是字节也冲,面评脏了大不了等三四个月就淡了,而且等到那个时候实力进步了选择还多,何必拘泥于字节
点赞 评论 收藏
分享
评论
3
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务