2018.7.20 学习日志-----贪心算法

贪心算法
简介:
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。(就是你每步都选择局部最优解)
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
简单来说
贪婪算法就是每步都采取最优的做法,也就是说每步都选择局部最优解,最终得到的就是全局最优解
贪婪算法并非适用于任何情况,但它却易于实现
有时候可以使用贪婪算法作为近似算法

贪心算法每过程:
1.建立数学模型来描述问题;
2.把求解的问题分成若干个子问题;
3.对每一子问题求解,得到子问题的局部最优解;
4.把子问题的解局部最优解合成原来解问题的一个解。

例子
《算法图解》的例子
1.教室调度问题
假设有如下课程表,希望将尽可能多的课安排在一间教室上课

课程 开始时间 结束时间
美术 9 AM 10 AM
英语 9:30 AM 10:30AM
数学 10 AM 11 AM
计算机 10:30 AM 11:30AM
音乐 11 AM 12 AM

安排的原则:
(1) 选出结束最早的课,它就是要在这间教室上的第一堂课。
(2) 接下来,必须选择第一堂课结束后才开始的课。同样,你选择结束最早的课,这将是要 在这间教室上的第二堂课。
重复这样做就能找出答案!

结果:这里写图片描述

2.背包问题
假设你是个贪婪的小偷,背着可装35磅(1磅≈0.45千克)重东西的 背包,在商场伺机盗窃各种可装入背包的商品。
你力图往背包中装入价值最高的商品,你会使 用哪种算法呢?
同样,你采取贪婪策略,这非常简单。
(1) 盗窃可装入背包的最贵商品。
(2) 再盗窃还可装入背包的最贵商品,以此类推。
只是这次这种贪婪策略不好使了!例如,你可盗窃的商品有下面三种。 这里写图片描述

依照原则
先装下了一个音响,然后就装不下其他的物品的了,背包里的东西价值3000美元
但是如果不是偷音响而是偷笔记本电脑和吉他,那么背包里的东西价值3500美元
显然在这里,贪心算法不能得到最优解,但是非常接近

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

练习题:

1.你在一家家具公司工作,需要将家具发往全国各地,为此你需要将箱子装上卡车。车子的载重为1T,箱子的重量不尽相同,你需要尽可能将更多的箱子装入卡车上(不考虑卡车可以装载的空间),为此你将如何选择要装上卡车的箱子呢?请设计一种贪婪算法。使用这种算法能得到最优解吗?

解:一种贪心策略是:选择剩余箱子里最轻的箱子装入卡车,重复这个过程,直到达到卡车的载重为止。使用这种算法不能得到最优解。

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

解: 不断地挑选可在余下的时间内完成的价值最大的活动,直到余下的时间不够完成任何 活动为止。使用这种算法不能得到最优解。

3.集合覆盖问题
问题描述:
假设你办了个广播节目,要让全美50个州的听众都收听得到。为此,你需要决定在哪些广播台播出。在每个广播台播出都需要支付费用,因此你力图在尽可能少的广播台播出。

使用下面的贪婪算法可得到非常接近的解。
(1) 选出这样一个广播台,即它覆盖了最多的未覆盖州。即便这个广播台覆盖了一些已覆盖 的州,也没有关系。
(2) 重复第一步,直到覆盖了所有的州

这是一种近似算法(approximation algorithm)。在获得精确解需要的时间太长时,可使用近 似算法。
只是得到了近似的解,并不是最优解

代码实现:

# 贪心算法
'''问题描述:假设你办了个广播节目,
要让全美50个州的听众都收听得到。为此,你需要决定在哪些广播台播出。
在每个广播台播出都需要支付费用,因此你力图在尽可能少的广播台播出。'''

# 出于简化考虑这里假设要覆盖的州没有那么多,广播台也没有那么多
def handle(stations):
    global states_need  # 声明了全局变量为什么?
    print_stations = []  # 记录下需要的广播台
    while states_need:  # 集合为空,即州已经全部覆盖完之后结束循环
        cover = set()  # 建立一个空集合,以便下面比较
        for station, states in stations.items():  # station表示广播台,states表示广播台所覆盖的州
            brother = states & states_need  # 得到一个交集
            if (len(brother) > len(cover)):  # 覆盖的范围广就记录下信息
                cover = brother
                beststation = station
        # 结束这for循环之后,表明已经得到了一个局部的解
        print_stations.append(beststation)
        states_need = states_need - cover  # 更新,还没有覆盖的州
    return print_stations


# 创建一个集合表示要覆盖的州,为什么要用到集合?
states_need = {'mt', 'wa', 'or', 'id', 'nv', 'ut', 'ca', 'az'}
# 用散列表来创建广播台清单
stations = {}
stations['kone'] = set(['id', 'nv', 'ut'])
stations['ktwo'] = set(['wa', 'id', 'mt', 'ca'])
stations['kthree'] = set(['or', 'nv', 'ca'])
stations['kfour'] = set(['nv', 'ut'])
stations['kfive'] = set(['ca', 'az'])
print(stations)
print(handle(stations))


#输出
{'kone': {'id', 'nv', 'ut'}, 'ktwo': {'mt', 'id', 'ca', 'wa'}, 'kthree': {'or', 'ca', 'nv'}, 'kfour': {'nv', 'ut'}, 'kfive': {'ca', 'az'}}
['ktwo', 'kone', 'kthree', 'kfive']

时间复杂度O(n^2)

代码中提出的问题:
1.在代码的函数中为什么用global声明了全局变量?
在python的函数中和全局同名的变量,如果你有修改变量的值就会变成局部变量,如果确定要引用全局变量,并且要对它修改,必须加上global关键字
可以参考这篇文章:https://blog.csdn.net/magictong/article/details/4464024

2.为什么使用集合来进行操作?
使用了集合,可以更加方便的找出相同的元素

最后《算法图解》里介绍了NP完全问题,面临NP完全问题最佳的做法是使用近似算法
而贪心算法易于实现,运行速度快,是不错的近似算法
贪婪算法寻找局部最优解,企图以这种方式获得全局最优解,不能保证最后的解是不是最优解

猜你喜欢

转载自blog.csdn.net/Yk_0311/article/details/81128175