grokking algorithms--第八章 贪婪算法中文翻译

贪婪算法
··············································································
在本章中:
你会学到如何处理不确定问题;
没有快速解决方案的问题称为NP完备;
当你看到这类问题时,可以快速地区分它们,从而不会浪费时间在为它们寻找快速算法上
你会用到近似算法来为NP完备问题找到近似解;
你会学到贪婪算法—它时一个非常简单实用的问题解决方案
··············································································
教室安排问题
假设你有一间教室,你希望尽可能地安排很多的课程在这间教室,你有一个课程时间表。

这里写图片描述

你不可以将它们都安排到这间教室,因为有些课程的上课时间是冲突的。
这里写图片描述

这里写图片描述
你希望将课程尽可能多的安排在这间教室,你需要怎么安排各个课程,从而使这间教室可以承接尽可能多的课程呢?
听起来是个难题,对吧?其实,这个算法很简单,简单到不可思议!这就是算法:
1.找到结束时间最早的课程,安排它为这间教室的第一个课程。
2.找到在第一堂课结束后最早开始的课程,然后,若是有多个课程的开始时间是一样的,选择最早结束的那个,安排它为第二个课程。
按照此种方法一直进行,你会得到答案。让我们来试一下。艺术课程的结束时间最早,10:00结束,因此它是安排在这间教室的第一个课程。

这里写图片描述

现在来找10:00后最早开始而且结束时间最早的课程。

这里写图片描述
英语肯定不在此列,因为它的上课时间和艺术的上课时间是冲突的到那时数学课是可以的。因此数学课是安排在这间教室的第二堂课。

这里写图片描述

最后,CS课和数学课是冲突的,因此CS课出局,但是音乐课的上课时间是合适的。
因此,这间教室的课程安排如下:

这里写图片描述

好多人都是说这个算法太简单了,因此是错误的。但这正是贪婪算法的魅力所在:它们非常简单!贪婪算法真的非常简单:在每一步,找到最优方案!在这个例子中,找结束时间最早的课程。用术语来表达:在每一步都找到局部最优解,在最后你就会得到全局最优解。相信我,贪婪算法找到了这种安排问题的最优解。
非常明显,贪婪算法并不是每次都起作用,但是它非常简单,我们来看另一个例子。

背包问题
假设你是一个贪心的小偷,你拿着背包去偷东西,商店里的所有东西你都可以偷,但是你只能装满这个背包,这个背包最多只能装35英镑。
这里写图片描述
你希望尽可能地偷值钱的东西,你可以利用什么算法来达到目的呢?
同样地,我们来试下贪婪算法。它最简单:
1.选择最贵的物品,将它们放入背包。
2.选择剩下的物品中最贵的,然后一直这么做。
但是这次,它不起作用了。你看,假设有3种物品你可以偷。
这里写图片描述

你的背包只能装35英镑的物品,立体音箱系统是最贵的,你偷这个,但是你的背包就不能偷其他的东西了。
这里写图片描述

你只偷了3000美元的东西。但是,若是你偷笔记本电脑和吉他,你就可以偷价值3500美元的东西。
这里写图片描述

很明显,贪婪算法这次没有给你局部最优解。但是也非常接近了。在下一章中,我会解释如何计算正确的方案。但是你是一个小偷,你不关心完美方案,相当好(差不多)的方案就已经很好了。
这厮我们从第二个例子中学到的:有的时候,完美是事情最大的敌人,好多时候,我们只需要一个差不多能解决问题的方案就可以了。这正是贪婪算法闪光的地方。因为它非常简单,而且它离完美答案也很近。

练习
8.1 你在一家家具公司上班,你需要将家具运往全国。你需要将这些箱子装上卡车,这些箱子大小不一,你需要尽可能地多装,你要怎样做呢?试试贪婪算法?你找到局部最优解了吗?
8.2 你计划去欧洲,你有7天的游玩时间,你有一个景观游玩计划(里面含有每个景点需要的时间),你怎样安排才能去看最多的景点呢?用贪婪算法。它给你局部最优解了吗?
我们来看最后一个例子,这个例子证明贪婪算法是非常有必要的。

集合覆盖问题
这里写图片描述

假设你有一个广播节目,想覆盖50个州。
你需要决定在哪些电台播放,从而可以覆盖所有的州,但是在这些州播放需要花钱,因此,你想尽可能少的出钱但是覆盖的州还要多。你有一个清单。
这里写图片描述
每个电台都会覆盖一个区域,这些区域中有重叠。你能找到覆盖这50个州的最小集合吗?听起来很简单,不是吗?其实很难,下面是方法:
1.找出所有可能的子集,这也被称为幂集,总共有2n个子集。
这里写图片描述
这里写图片描述
2.从这些中,找到能覆盖50个州的最少的电台集合。
问题是,计算每一个可能的电台集合时,需要花费很长的时间。需要O(2^n),因为有2n个电台,如果总共有5~10个电台,还可以计算,但是在这个例子中,有太多的电台组合。假设1分钟可以算10个子集,
没有一个算法可以快速的算出来,怎么办呢?
这里写图片描述

贪婪算法

贪婪算法可以帮助我们,贪婪算法的结果已经很接近了。
1.找到一个电台,它能覆盖为覆盖的州的最多,若是这个电台也覆盖了已经覆盖过的州也没有关系。
2.一直重复直到所有的州都被覆盖过了。
这就是贪婪算法,当我们发现计算精确的结果需要太多时间时,贪婪算法就起作用了。贪婪算法有2个评判标准
1.有多快
2.有多接近局部最优解。
贪婪算法是个很好的选择,不仅是它简单,而且通常执行也很快。在这个例子中,贪婪算法需要花费O(n^2)时间,n是电台的数量。
让我们来看该算法的代码实现
代码准备工作
在这个例子中,我们用集合来表示电台和州
首先,将你需要覆盖的州放在集合里。
集合和数组类似,但是集合中无重复项。假如你存储如下的数据:
states_needed = set([“mt”, “wa”, “or”, “id”, “nv”, “ut”, “ca”, “az”]) 先将数据放入一个数组中,然后将它转变成一个集合

>>> arr = [1, 2, 2, 3, 3, 3] 
>>> set(arr) 
set([1, 2, 3]) 

当它存入集合中时,重复项就去掉了。

你需要一个电台的数组来存放它们,我们用Hash表来存储。

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”]) 

key是电台名字,value值是每个电台覆盖的州名,在这个例子中,kone电台可以覆盖Idaho,Nevada和Utah州,把这些州放进集合中存储,可以让问题变简单。一会你就发现了。最后,你需要一个集合将所有的集合存储起来。
nal_stations = set()
计算结果
现在你需要计算结果了。
通过观察右边的图,你可以预测出你需要选择哪些州吗?这有不止一种选择方法,你要选择那些含有未覆盖州最多的的电台,我把它叫做best_station.

best_station = None

states_covered = set()

for station, states_for_station in stations.items(): 

states_covered存储还未被覆盖的州(这个电台有覆盖,但是已选择过得电台未覆盖的州),for循环让我们找到最适合的电台。for循环如下:

covered = states_needed & states_for_station 
if len(covered) > len(states_covered): 
    best_station = station 
  states_covered = covered 

这一行很有趣:

covered = states_needed & states_for_station

这是什么意思?
集合
假设你有一个集合,里面存放水果。
这里写图片描述
你还有一个集合,里面存放蔬菜。
这里写图片描述
当你有两个集合,你会发现有趣的事情。你可以做一下两种事情
这里写图片描述

这里写图片描述
是水果但不是蔬菜的项是AVOCADO和BANANA.
并集是将两者合并起来。交集是取两个集合中都有的元素。在这个例子中,是西红柿。差集是找出所有术语第一个但不属于第二个集合的项。
例如:

>>> 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”])
>>> vegetables - fruits ———————这会是什么结果呢?

回顾一下:
结合类似数组,但是集合中无重复项
可以对集合求交集、并集和差集。

回到代码中来
这里写图片描述
这是求差集

covered = states_needed & states_for_station 

covered是states_needed和states_for_station的交集。covered是本电台覆盖但其他电台未覆盖的项。现在我们来比较该电台和best_station哪个电台覆盖的州多。将该电台置为best_station。它覆盖了的项置为station_covered。循环结束时,将该项加入final_stations数组。我们也需要更斯station_needed数组,因为新加入一个电台后,station_needed中的有些项就不需要了。一直执行循环,直到station_needed为空。
现在,我们得到final_stations数组了。

>>> print final_stations
set([‘ktwo’,’kthree’,’kone’,’kfive’])

这是你想要的吗?我们除了选择1,2,3,5,也可以选择2,3,4,5.让我们来比较贪婪算法和精确算法需要花费的时间。
这里写图片描述

练习
这些算法中,哪些是贪婪算法?
8.3 快速排序
8.4 广度优先搜索算法
8.5 迪杰斯特拉算法
NP完备问题
要想解决NP完备问题,我们需要计算每个可能的集合。
这里写图片描述
你可能记起了第一个章提到的旅行推销员问题。在这个问题中,这个推销员需要去5个不同的城市。
这里写图片描述

他想找出最短的路径,为了找出最短的路径,我们需要计算每一种可能的路线。
这里写图片描述
5个城市需要计算多少条路径呢?

旅行推销员,一步一步来

让我们从简单的问题入手,假设你只需要去2个城市,有2条路可以选择。
这里写图片描述
是同一个路线吗?
你可能认为这是同一条路线,毕竟,从A->B 和B->A的距离是一样的。不完全是。一些城市(比如圣弗朗西斯科)有很多单行道。你去的路线和回来的路线不是同一条。你可能要多走1到2公里才能找到去高速公路的斜坡弯道。所以,A->B 和B->A不完全一样。
你可能会想,“在这旅行推销员问题中,他需要从特定的城市出发吗?”,例如,假设我是这个推销员,我住在圣弗朗西斯科,需要去其他四个城市。圣弗朗西斯科是我的起点。
但是,有时候起点不在你想要去的城市集合中。比如你是联邦快递,你运送一个包裹去旧金山湾区。这个包裹可能被晕倒旧金山湾区50个网点中的任意一个。这个包裹用卡车来运输。需要将它投递到50个网点的哪一个呢?这个例子中,起点是不知道的,需要你来找出局部最优解,找到旅行推销员的第一个拜访城市。

  假设从A->B 和B->A的旅行时间是一样的。若是没有定义出发城市,会简单点,我们用这个例子。

2个城市=2种可能的路线
3个城市
现在,假设有了第3个城市,有多少种可能的路线呢?
如果你从伯克利出发,你有2个其他城市需要访问。有6种可能的路线。
这里写图片描述

因此,3个城市=6条路线。
4个城市
假设你从Fremont出发,有6种可能的路线。
这里写图片描述
这个很像刚才只有3个城市时的6条路线,只多了我们刚才加的这个城市,Fremont!假设你有4个城市,我们选Fremont作为出发城市,有6种可能的路线。我们也可以选择其他城市作为出发城市,同样也会有6条路线
这里写图片描述

因此,4个不同的出发城市,每个对应不同的6种可能的路线,总共是24条可能的路线。你发现规律了吗?每加一个城市,你就多加了更多的可能路径。
这里写图片描述
若是6个城市,会有多少条可能路径呢?对了,720条,7个城市会有5040个可能的路径,8个城市会有40320个可能的路径。
这个被称为阶乘(第3章中提到过),5!=720 ,假设你有10个城市,那么10!=3628,800.你需要计算3000,000个可能的路径。你也看到了,想快速的计算出结果,这是完全不可能的。
这也是当城市数目很多时,旅行推销员的问题。不能快速计算出最准确答案的原因。
旅行推销员问题和集合覆盖问题有一个共同点:计算每一种可能的情况,然后去找出最小/最少的一种情况,这种问题被称为NP完备问题。

近似解
对旅行推销员问题来说,好的近似解时什么呢?可以快速的得到答案。若是读完就能得到答案,就更好了。下面是我是怎么做的。
随机选一个出发城市,然后从没去过的城市中选择离的最近的。假设从MARIN出发。总共需要71英里到PAIO ALTO。尽管这条路径不是最近的,但是它也是相当近的了。
这里写图片描述

NP完备问题的定义:一些非常难解的问题,旅行推销员问题和集合覆盖问题是其中的2个例子。一些非常聪明的人也认可,赵大能解决此类问题的非常快的算法几乎是不可能的。

怎么判定问题是否是NP完备问题呢?

这里写图片描述这里写图片描述

Jonah正在为他的梦想足球队选拨球员,他需要有这些技能的人:好的四分卫、好的跑位、抗压性和协调性很好的人等等。他有一批候选人,每一个人具备其中的一项或多项技能。

Jonah的团队需要具备上面的所有技能,人数是有要求的,Jonah意识到,这是个集合覆盖问题。
这里写图片描述

Jonah可以利用贪婪算法来创建团队。
1.找到一个选手,可以有礼貌其他人没有的才能中的大多数才能。
2.不断重复,直到团队中成员的技能合起来可以满足要求。
NP完备问存在于现实中的很多地方,若是可以准确的区分出它来就太棒了。我们就会停止寻找最精确解,转而去寻找近似解。但是难点在于区分出哪些问题是NP完备问题,因为NP完备问题和其他问题的差异很小,例如,上一章中,我们讨论了很多最短路径问题,我们知道了怎么求解A点到B点的最短路径。
这里写图片描述

但是,若是节点很多时,它就是旅行推销员问题,这就是NP完备问题了。答案是:没有一种简单的方法将NP完备问题筛选出来,下面是一些线索:
当数据量小时,运算速度很快,当数据量大时,运算速度很慢
好多节点都指向X的图
不能将问题分解为更小问题的问题
需要找到一条路线,包含很多点(旅行推销员问题)
包含了很多集合(如电台集合),很难解决的
可以将问题归类为旅行推销员或集合问题的问题。

练习

8.6 快递员需要给20个家庭送包裹,他需要找到去这2个家庭最近的路,请问这个是NP完备问题吗?
8.7 在一大群人中找出一个最大的小圈子(小圈子里的人相互都认识),请问这是个NP完备问题吗?
8.8 你在画一幅美国地图,相邻的州要用不同的颜色来标示。你能找出最少要用多少种颜色就能满足要求吗?这个是NP完备问题吗?

复习

贪婪算法可以快速找到局部最优解,从而找到全局的还不错的解决方案
NP完备问题么有快速解决方案
当遇到NP完备问题时,最好的选择方案是贪婪算法
贪婪算啊简单、高效,是最好的求近似解的算法。

猜你喜欢

转载自blog.csdn.net/nfzhlk/article/details/80114311
今日推荐