贪心算法
贪心算法(又称贪婪算法)是指,在堆问题求解时,总是做出当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出是在某种意义上的局部最优解。
贪心算法并不保证会得到最优解,但是在某些问题上贪心算法的解就是最优解。要会判断一个问题能否用贪心算法来计算。
一、找零问题
假设商店老板需要找零n元钱,钱币的面额有:100元、50元、20元、5元、1元,如何找零使得所需钱币的数量最小?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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、分数背包代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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、活动选择问题实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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)] |
五、总结
都是最优化问题。大大提升了解决问题的速度,但是它也有一些优化问题无法解决或得到的不是最优解。
贪心算法存在的问题:
- 不能保证求得的最后解是最佳的;
- 不能用来求最大或最小解问题;
- 只能求满足某些约束条件的可行解的范围。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术