贪婪算法

main
3wish 2023-12-22 19:23:39 +08:00
parent 6d113fa237
commit 32fc3b1a41
3 changed files with 158 additions and 0 deletions

View File

@ -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

34
greedy/greedy.py 100644
View File

@ -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

View File

@ -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仍然是短板
- 向内移动ii的高度增大后容量才可能变大
- 由此便可推出本题的贪心策略初始化两指针分列容器两端每轮向内收缩短板对应的指针直至两指针相遇
"""
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 的因子那么它就应该被继续切分最终的切分方案只应出现 123这三个因子
123 这三个因子中显然 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))