1. 贪心的思想:
贪心算法的思想是建立在局部最优选择上的。在对问题求解时,总是做出在当前看来是最好的选择,即不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪心算法不保证会得到最优解,但是在某些问题上,贪心算法的解足够接近最优解或者完全等同于最优解。
2. 贪心的经典问题理解:
硬币找零问题
假设你是一名售货员,需要给顾客找零n元钱,你拥有的面额的硬币包括:1元、5元、10元。请编写一个程序,计算出最少需要多少个硬币才能凑足这个金额的找零。
def minCoins(coins, V):
"""
coins: 可用的硬币面额列表(降序排序)
V: 需要找零的总金额
"""
# 存储结果(硬币数)
result = []
# 遍历所有硬币
for i in range(len(coins)):
# 当前面额可以用的最大次数
while V >= coins[i]:
V -= coins[i]
result.append(coins[i])
return len(result)
coins = [10, 5, 1] # 假设硬币已经降序排序
V = 11
print(minCoins(coins, V))
思路讲解:
- 贪心算法总是选择当前能选择的最大面额的硬币。
- 通过不断减去选择的硬币面额,直到金额减至零。
- 结果result列表包含构成找零的所有硬币, len(result)则为硬币数量
背包问题
你是一名探险家,在一次探险中发现了三个不同类型的宝藏。每种宝藏有其重量和价值,现在你需要决定应该带回多少每种宝藏以最大化你的收益。由于你的背包容量有限,你可能需要将某些宝藏分割带走。具体宝藏信息如下:
- 宝藏A:重量10kg,价值60万元。
- 宝藏B:重量20kg,价值100万元。
- 宝藏C:重量30kg,价值120万元。
你的背包最大承重为50kg。使用贪心算法确定每种宝藏应带走的数量,以确保你能够获得最大的价值。
def fractionalKnapSack(W, wt, val, n):
"""
W: 背包的最大容量
wt: 物品重量列表
val: 物品价值列表
n: 物品数量
"""
# 计算单位重量价值并排序
item_value = [(val[i] / wt[i], wt[i], val[i]) for i in range(n)]
item_value.sort(reverse=True)
total_value = 0 # 总价值
for i in item_value:
cur_wt = i[1]
cur_val = i[2]
if W - cur_wt >= 0: # 如果可以装下整个物品
W -= cur_wt
total_value += cur_val
else: # 如果不能装下整个物品,装下部分
fraction = W / cur_wt
total_value += cur_val * fraction
W = 0
break
return total_value
val = [60, 100, 120]
wt = [10, 20, 30]
W = 50
n = len(val)
print(fractionalKnapSack(W, wt, val, n))
思路讲解:
- 计算每种物品的单位重量价值并按此价值进行降序排序。
- 依次考虑每种物品,如果当前背包容量可以完整装下物品,则全装;否则,只装入部分,以单位价值最大化。
- 持续这个过程直到背包填满或物品考虑完毕。
3. 贪心算法的反例
我们之前提到了,贪心是只选择局部最优解,就有可能无法保证全局最优解。
以硬币问题举个例子来看,假设有一个奇葩国家的硬币面额有1,5,8三种面额,你要找15块钱,售货员需要给你找回的硬币个数保持最少。
在贪心策略下,首先选择8,在选择5,在选择1,最后选择1。这种情况需要找回4个硬币。
但是很容易看出来,找3个5块的硬币数会更少。
这就是贪心算法的一个反例。
4. 总结:
贪心算法的优点在于实现简单,解题速度快,在具有贪心性质的问题中可以得到最优解。但是缺点也很明显,在每一步只选择局部最优解的前提下,无法最终解保证一定是全局最优解。