From 32fc3b1a414e7c61665c8c21130e508dac85eda1 Mon Sep 17 00:00:00 2001 From: 3wish Date: Fri, 22 Dec 2023 19:23:39 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B4=AA=E5=A9=AA=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- greedy/fractional_knapsack.py | 48 ++++++++++++++++++++++ greedy/greedy.py | 34 ++++++++++++++++ greedy/max_question.py | 76 +++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 greedy/fractional_knapsack.py create mode 100644 greedy/greedy.py create mode 100644 greedy/max_question.py diff --git a/greedy/fractional_knapsack.py b/greedy/fractional_knapsack.py new file mode 100644 index 0000000..a11ddcb --- /dev/null +++ b/greedy/fractional_knapsack.py @@ -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 + + diff --git a/greedy/greedy.py b/greedy/greedy.py new file mode 100644 index 0000000..c0af603 --- /dev/null +++ b/greedy/greedy.py @@ -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 diff --git a/greedy/max_question.py b/greedy/max_question.py new file mode 100644 index 0000000..e1f9c53 --- /dev/null +++ b/greedy/max_question.py @@ -0,0 +1,76 @@ +""" +输入一个数组 ht,其中的每个元素代表一个垂直隔板的高度。数组中的任意两个隔板,以及它们之间的空间可以组成一个容器。 +容器的容量等于高度和宽度的乘积(面积),其中高度由较短的隔板决定,宽度是两个隔板的数组索引之差。 +请在数组中选择两个隔板,使得组成的容器的容量最大,返回最大容量。 + +容器由任意两个隔板围成,因此本题的状态为两个隔板的索引,记为 [i,j]。 + +根据题意,容量等于高度乘以宽度,其中高度由短板决定,宽度是两隔板的数组索引之差。设容量为 cap[i,j],则可得计算公式: + cap[i,j] = min(ht[i],ht[j]) * (j - i) + +贪心策略确定: + +- 现选取一个状态 [i, j],其满足索引 i 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) 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))