zoukankan      html  css  js  c++  java
  • 贪心算法

      贪心算法(又称贪婪算法)是指,在堆问题求解时,总是做出当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出是在某种意义上的局部最优解

      贪心算法并不保证会得到最优解,但是在某些问题上贪心算法的解就是最优解。要会判断一个问题能否用贪心算法来计算。

    一、找零问题

      假设商店老板需要找零n元钱,钱币的面额有:100元、50元、20元、5元、1元,如何找零使得所需钱币的数量最小?

    t = [100, 50, 20, 5, 1]
    
    def change(t, n):   # n指的总金额
        m = [0 for _ in range(len(t))]   # 创建一个和t一样长但全是0的列表
        # 这里假设t都是倒序排好的
        for i, money in enumerate(t):
            m[i] = n // money  # n整除money:376//100=3
            n = n % money  # n对money取余:376%100=76
        return m, n   # 如果到最后找不开,n就是找不开的钱
    
    print(change(t, 376))    # ([3, 1, 1, 1, 1], 0)   即:300+50+20+5+1
    
    # 如果t = [100, 50, 20, 5],则有1块钱找不开,
    print(change(t, 451))    # ([4, 1, 0, 0], 1)  

    二、背包问题

      一个小偷在某个商店发现有n个商品,第i个商品价值vi元,重wi千克。他希望拿走的价值尽量高,但他的背包最多只能容纳W千克的东西。他应该拿走哪些商品?

    1、0-1背包

      对于一个商品,小偷要么把它完整拿走,要么留下。不能只拿走一部分,或把一个商品拿走多次。(商品为金条)

    2、分数背包

      对于一个商品,小偷可以拿走其中任意一部分。(商品为金砂)

    3、问题示例

    • 商品1:v1=60   w1=10
    • 商品2:v2=100   w2=20
    • 商品3:v3=120   w3=30
    • 背包容量:W=50
    • 对于0-1背包和分数背包,贪心算法是否都能得到最优解?为什么?

      答:贪心算法对分数背包可以得到最优解,对0-1背包得不到最优解,很可能背包都无法装满。

      两种背包问题都具有最优子结构性质。对0-1背包问题,考虑重量不超过W而价值最高的装包方案。如果我们将商品j从此方案中删除,则剩余商品必须是重量不超过W-wj的价值最高的方案(小偷只能从不包括商品j的n-1个商品中选择拿走哪些)。

      虽然两个问题相似,但我们用贪心策略可以求解背包问题,而不能求解0-1背包问题,为了求解部分数背包问题,我们首先计算每个商品的每磅价值vi/wi。遵循贪心策略,小偷首先尽量多地拿走每磅价值最高的商品,如果该商品已全部拿走而背包未装满,他继续尽量多地拿走每磅价值第二高的商品,依次类推,直到达到重量上限W。因此,通过将商品按每磅价值排序,贪心算法的时间运行时间是O(nlgn)。

      为了说明贪心这一贪心策略对0-1背包问题无效,考虑下图所示的问题实例。此例包含3个商品和一个能容纳50磅重量的背包。商品1重10磅,价值60美元。商品2重20磅,价值100美元。商品3重30磅,价值120美元。因此,商品1的每磅价值为6美元,高于商品2的每磅价值5美元和商品3的每磅价值4美元。因此,上述贪心策略会首先拿走商品1。但是,最优解应该是商品2和商品3,而留下商品1。拿走商品1的两种方案都是次优的。

      但是,对于分数背包问题,上述贪心策略首先拿走商品1,是可以生成最优解的。拿走商品1的策略对0-1背包问题无效是因为小偷无法装满背包,空闲空间降低了方案的有效每磅价值。在0-1背包问题中,当我们考虑是否将一个商品装入背包时,必须比较包含此商品的子问题的解与不包含它的子问题的解,然后才能做出选择。这会导致大量的重叠子问题——动态规划的标识。

    4、分数背包代码实现

    def fractional_backpack(goods, w):
        """
        贪心算法——分数背包
        :param goods: 商品
        :param w: 背包容量
        :return:
        """
        m = [0 for _ in range(len(goods))]   # [0, 0, 0]
        total_v = 0    # 总价值
        for i, (price, weight) in enumerate(goods):
            if w >= weight:
                m[i] = 1
                total_v += price
                w -= weight
            else:  # 不足1
                m[i] = w / weight
                total_v += m[i]*price
                w = 0
                break
        return m, total_v
    
    print(fractional_backpack(goods, 50))   # ([1, 1, 0.6666666666666666], 240.0)   60+100+120*2/3=60+100+80=240
    

    三、拼接最大数字问题

      有n个非负整数,将其按照字符串拼接的方式拼接为一个整体。如何拼接可以使得得到的整数最大?

      例:32,94,128,1286,6,71可以拼接出的最大整数为94716321286128

    from functools import cmp_to_key   # 传递python2中sort的cmp函数
    
    li = [32, 94, 128, 1286, 6, 71]
    
    def xy_cmp(x, y):
        if x+y < y+x:  # 要让大的在前面,这里需要交换
            return 1
        elif x+y > y+x:
            return -1
        else:
            return 0
    
    def number_join(li):
        li = list(map(str, li))   # 转换为字符串:['32', '94', '128', '1286', '6', '71']
        # 方法1:cmp函数
        li.sort(key=cmp_to_key(xy_cmp))  # 传递进xy_cmp函数
        # 方法2:使用快排、冒泡排序等
        return "".join(li)
    
    print(number_join(li))    # 94716321286128
    

    四、活动选择问题

      假设有n个活动,这些互动要占用同一片场地,而场地在某时刻只能供一个活动使用。

      每个活动都有一个开始时间si结束时间fi(题目中时间以整数表示),表示活动在[si, fi)(左闭右开)区间占用场地。

      问:安排哪些活动能够使该场地举办的活动个数最多

      

    1、贪心结论和证明

      贪心结论:最先结束的活动一定是最优解的一部分

      证明:假设a是所有活动中最先结束的活动,b是最优解中最先结束的活动。

    • 如果a=b,结论成立。
    • 如果a≠b,则b的结束时间一定晚于a的结束时间,则此时用a替换掉最优解中的b,a一定不与最优解中的其他活动时间重叠,因此替换后的解也是最优解。

     2、活动选择问题实现

    activities = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 9), (5, 9), (6, 10), (8, 11), (8, 12), (2, 14), (12, 16)]
    
    # 保证活动是按照结束时间排好序的(每次选择最早结束的)
    activities.sort(key=lambda x: x[1])
    # print(activities)
    
    def activity_selection(a):
        """
        活动选择
        :param a: 活动
        :return:
        """
        res = [a[0]]  # 将最早结束的活动放入结果中
        for i in range(1, len(a)):   # 对列表进行遍历(除去第一个)
            if a[i][0] >= res[-1][-1]:  # 当前活动开始时间 大于等于 res最后一个活动的结束时间 因此不冲突
                res.append(a[i])
        return res
    
    print(activity_selection(activities))   # [(1, 4), (5, 7), (8, 11), (12, 16)]
    

    五、总结

      都是最优化问题。大大提升了解决问题的速度,但是它也有一些优化问题无法解决或得到的不是最优解。

    贪心算法存在的问题:

    • 不能保证求得的最后解是最佳的;
    • 不能用来求最大或最小解问题;
    • 只能求满足某些约束条件的可行解的范围。
  • 相关阅读:
    消息队列优缺点及各种MQ对比
    反射详解
    Tomcat线程模型及调优
    Tomcat结构及类加载机制
    Spring AOP
    Spring IOC
    Spring介绍
    SpringMVC介绍
    Mybatis介绍
    Ajax笔记(一)
  • 原文地址:https://www.cnblogs.com/xiugeng/p/9758080.html
Copyright © 2011-2022 走看看