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

    五、总结

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

    贪心算法存在的问题:

    • 不能保证求得的最后解是最佳的;
    • 不能用来求最大或最小解问题;
    • 只能求满足某些约束条件的可行解的范围。
  • 相关阅读:
    Codeforces Gym 100571A A. Cursed Query 离线
    codeforces Gym 100500 J. Bye Bye Russia
    codeforces Gym 100500H H. ICPC Quest 水题
    codeforces Gym 100500H A. Potion of Immortality 简单DP
    Codeforces Gym 100500F Problem F. Door Lock 二分
    codeforces Gym 100500C D.Hall of Fame 排序
    spring data jpa 创建方法名进行简单查询
    Spring集成JPA提示Not an managed type
    hibernate配置文件中的catalog属性
    SonarLint插件的安装与使用
  • 原文地址:https://www.cnblogs.com/xiugeng/p/9758080.html
Copyright © 2011-2022 走看看