贪婪算法
parent
6d113fa237
commit
32fc3b1a41
|
@ -0,0 +1,48 @@
|
|||
"""
|
||||
给定 n个物品,第 i 个物品的重量为 w[i-1]、价值为 v[i-1],和一个容量为 cap 的背包。
|
||||
每个物品只能选择一次,但可以选择物品的一部分,价值根据选择的重量比例计算。
|
||||
问在限定背包容量下背包中物品的最大价值。
|
||||
|
||||
分数背包问题和 0-1 背包问题整体上非常相似,状态包含当前物品 i 和容量 c,目标是求限定背包容量下的最大价值。
|
||||
不同点在于,本题允许只选择物品的一部分。如图 15-4 所示,我们可以对物品任意地进行切分,并按照重量比例来计算相应价值。
|
||||
|
||||
对于物品 i,它在单位重量下的价值为 v[i-1]/w[i-1],简称单位价值。
|
||||
假设放入一部分物品 i,重量为 w,则背包增加的价值为 w*v[i-1]/w[i-1]。
|
||||
"""
|
||||
|
||||
"""
|
||||
贪心策略确定:
|
||||
|
||||
最大化背包内物品总价值,本质上是最大化单位重量下的物品价值。由此便可推理出图 15-5 所示的贪心策略。
|
||||
|
||||
将物品按照单位价值从高到低进行排序。
|
||||
遍历所有物品,每轮贪心地选择单位价值最高的物品。
|
||||
若剩余背包容量不足,则使用当前物品的一部分填满背包。
|
||||
"""
|
||||
|
||||
|
||||
class Item:
|
||||
def __init__(self, w: int, v: int):
|
||||
self.w = w # 物品重量
|
||||
self.v = v # 物品价值
|
||||
self.avg = self.v / self.w
|
||||
|
||||
|
||||
def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int:
|
||||
items = [Item(w, v) for w, v in zip(wgt, val)]
|
||||
items.sort(key=lambda item: item.avg, reverse=True)
|
||||
|
||||
res = 0
|
||||
for item in items:
|
||||
if item.w <= cap:
|
||||
# 若剩余空间充足,则将当前物品整个拿走
|
||||
res += item.v
|
||||
cap -= item.w
|
||||
else:
|
||||
# 若剩余空间不足,则拿走当前物品的部分
|
||||
res += (item.avg) * cap
|
||||
break
|
||||
|
||||
return res
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
"""
|
||||
「贪心算法 greedy algorithm」是一种常见的解决优化问题的算法,其基本思想是在问题的每个决策阶段,
|
||||
都选择当前看起来最优的选择,即贪心地做出局部最优的决策,以期获得全局最优解。
|
||||
|
||||
- 动态规划会根据之前阶段的所有决策来考虑当前决策,并使用过去子问题的解来构建当前子问题的解。
|
||||
- 贪心算法不会考虑过去的决策,而是一路向前地进行贪心选择,不断缩小问题范围,直至问题被解决。
|
||||
"""
|
||||
|
||||
"""
|
||||
使用贪心算法解决“零钱兑换问题”,给定目标金额,我们贪心地选择不大于且最接近它的硬币,不断循环该步骤,直至凑出目标金额为止。
|
||||
"""
|
||||
|
||||
"""
|
||||
贪心算法不仅操作直接、实现简单,而且通常效率也很高。
|
||||
|
||||
然而,对于某些硬币面值组合,贪心算法并不能找到最优解。也就是说,对于零钱兑换问题,贪心算法无法保证找到全局最优解,
|
||||
并且有可能找到非常差的解。它更适合用动态规划解决。
|
||||
|
||||
相较于动态规划,贪心算法的使用条件更加苛刻,其主要关注问题的两个性质。
|
||||
- 贪心选择性质:只有当局部最优选择始终可以导致全局最优解时,贪心算法才能保证得到最优解。
|
||||
- 最优子结构:原问题的最优解包含子问题的最优解。
|
||||
"""
|
||||
|
||||
def coin_change_greedy(coins: list[int], amt: int):
|
||||
i = len(coins) - 1
|
||||
count = 0
|
||||
|
||||
while amt > 0:
|
||||
while i > 0 and coins[i] > amt:
|
||||
i -= 1
|
||||
amt -= coins[i]
|
||||
count += 1
|
||||
|
||||
return count if amt == 0 else -1
|
|
@ -0,0 +1,76 @@
|
|||
"""
|
||||
输入一个数组 ht,其中的每个元素代表一个垂直隔板的高度。数组中的任意两个隔板,以及它们之间的空间可以组成一个容器。
|
||||
容器的容量等于高度和宽度的乘积(面积),其中高度由较短的隔板决定,宽度是两个隔板的数组索引之差。
|
||||
请在数组中选择两个隔板,使得组成的容器的容量最大,返回最大容量。
|
||||
|
||||
容器由任意两个隔板围成,因此本题的状态为两个隔板的索引,记为 [i,j]。
|
||||
|
||||
根据题意,容量等于高度乘以宽度,其中高度由短板决定,宽度是两隔板的数组索引之差。设容量为 cap[i,j],则可得计算公式:
|
||||
cap[i,j] = min(ht[i],ht[j]) * (j - i)
|
||||
|
||||
贪心策略确定:
|
||||
|
||||
- 现选取一个状态 [i, j],其满足索引 i<j 且高度 ht[i]<ht[j]。
|
||||
- 若此时将长板 j 向短板 i 靠近,则容量一定变小。因为i仍然是短板。
|
||||
- 向内移动i,i的高度增大后,容量才可能变大。
|
||||
- 由此便可推出本题的贪心策略:初始化两指针分列容器两端,每轮向内收缩短板对应的指针,直至两指针相遇。
|
||||
"""
|
||||
import math
|
||||
|
||||
|
||||
def max_capacity(ht: list[int]) -> int:
|
||||
i, j = 0, len(ht) - 1
|
||||
res = 0
|
||||
while i < j:
|
||||
cap = min(ht[i], ht[j]) * (j - i)
|
||||
res = max(res, cap)
|
||||
if ht[i] < ht[j]:
|
||||
i += 1
|
||||
else:
|
||||
j -= 1
|
||||
return res
|
||||
|
||||
|
||||
"""
|
||||
最大切分乘积问题:
|
||||
|
||||
给定一个正整数 n,将其切分为至少两个正整数的和,求切分后所有整数的乘积最大是多少?
|
||||
|
||||
假设我们将 n 切分为 m 个整数因子,其中第 i 个因子记为 ni
|
||||
|
||||
贪心策略确定:
|
||||
|
||||
根据经验,两个整数的乘积往往比它们的加和更大。假设从 n 中分出一个因子 2,
|
||||
则它们的乘积为 2(n-2)。我们将该乘积与 n 作比较:
|
||||
2*(n-2)>=n --> n>=4
|
||||
|
||||
也就是当 n>=4 时,切分出一个 2 后乘积会变大,这说明大于等于 4 的整数都应该被切分。
|
||||
|
||||
贪心策略一:如果切分方案中包含 >=4 的因子,那么它就应该被继续切分。最终的切分方案只应出现 1、2、3这三个因子。
|
||||
在 1、2、3 这三个因子中,显然 1 是最差的,因为 1*(n-1)<n恒成立。
|
||||
|
||||
贪心策略二:在切分方案中,最多只应存在两个 2。因为三个 2,总是可以替换为两个 3,从而获得更大的乘积。
|
||||
|
||||
代码实现:
|
||||
我们无须通过循环来切分整数,而可以利用向下整除运算得到 3 的个数 a,用取模运算得到余数 b,
|
||||
此时有:
|
||||
n = 3a + b
|
||||
|
||||
请注意,对于 n<=3 的边界情况,必须拆分出一个 1,乘积为 1(n-1)。
|
||||
"""
|
||||
|
||||
|
||||
def max_product_cutting(n: int) -> int:
|
||||
if n <= 3:
|
||||
return 1 * (n - 1)
|
||||
|
||||
a, b = n // 3, n % 3
|
||||
if b == 1:
|
||||
# 当余数为 1 时,将一对 1 * 3 转为 2 * 2
|
||||
return int(math.pow(3, a - 1)) * 2 * 2
|
||||
if b == 2:
|
||||
# 当余数为 2 时不用处理
|
||||
return int(math.pow(3, a)) * 2
|
||||
|
||||
# 余数为 0 时不用处理
|
||||
return int(math.pow(3, a))
|
Loading…
Reference in New Issue