python算法学习
1.贪心算法
主要思想:不断选取当前条件下最好的选择来构造子步骤,不从整体上考虑最优,只从当前局部最优
利用贪心算法需要解决两个问题:
- 是否具有贪心选择性质:根据同一规则f,将原问题变成一个相似的但规模更小的子问题,后面的每一步都是当前最佳的选择。
- 是否具有局部最优解:通过一个贪心标准可以得到问题的最优解。
解决贪心问题的一般思路:
- 建立对问题精确描述的数学模型,包括定义最优解的模型。
- 将问题分成一系列子问题,同时定义子问题的最优解结构。
- 应用贪心算法原则可以确定每个子问题的局部最优解,并根据最优解模型,用子问题 的局部最优解堆叠出全局最优解。
1.1硬币找零问题
问题描述:
有6种不同面值的硬币,各硬币的面值分别为5分、1角、2角、5角、1 元和2元。现要用这些面值的硬币来购物和找钱。购物时规定了可以使用的各种面值的硬币个 数。对于给定的各种面值的硬币个数和付款金额,如何计算使用硬币个数最少的交易方案。(硬币数等于付钱硬币数加找零硬币数)
其核心思想为消费者硬币数量有限,商店的硬币无限。因此问题可以用以下公式来描述:
- MIN(消费者支付硬币数量+商店找零硬币数量)
- 支付值-找零值=商品值
这里所用的贪心算法为:
MAX(消费者拥有的硬币面值-商店拥有的硬币面值)优先使用。 即消费者优先使用面值最大的,商店优先使用面值最小的
为什么有效?
接下来验证这个算法的贪心选择性和最优子结构性质,证明贪心算法可以获得最优解:
贪心选择性:用贪心策略的选择替代原来的选择,如果能获得更优解或同优解,那么贪心 策略就可以获得最优解。
c[]={10,5,2,1} 在硬币选择问题中,如果需要表示一张六分的面值,可以选用三个两分的,也可以用一张更大的面值5加一个一分的来代替,这种优先使用大面值的方法可以使用更少的硬币,所以说满足贪心选择性质
疑问?
那如果说表示6分的,按照贪心选择的策略应该选择更大的10分再减去两个两分,这样不是比选择一个五分加上一个一分用到的硬币更多了吗
解答:
您提出的情况是贪心算法中的一个特例,其中贪心策略并不总是直接选择最大的硬币,而是选择能够使得找零中硬币总数最少的方式。在这种情况下,选择一个5分硬币和一个1分硬币来凑成6分,确实比选择一个10分硬币再找零两个2分硬币更优,因为前者只需要两枚硬币。
这个例子说明,贪心算法的有效性取决于问题的具体情况和贪心选择的定义。在找零问题中,贪心选择的策略是每一步都选择当前最大的可用硬币,但这并不意味着总是选择最大的硬币。实际上,贪心选择的策略是在每一步都做出一个局部最优的选择,以期望这个选择能导致全局最优解。
对于找零问题,贪心算法的正确策略是选择当前最大的硬币,但如果这个选择导致后续找零时需要更多的硬币,那么这个选择就不再是贪心的。因此,在实际应用贪心算法时,我们需要考虑当前选择对后续步骤的影响。
在您给出的例子中,选择10分硬币然后找零两个2分硬币,实际上可能会导致后续找零时需要更多的硬币。因此,贪心算法在这里会选择更小的硬币组合,即一个5分硬币和一个1分硬币,因为这样可以减少总的硬币数量。
总之,贪心算法在找零问题中的应用是选择每一步的最优解,但这个最优解可能是基于当前情况的最优,而不是单纯选择最大的硬币。在实际编程实现时,我们需要根据问题的具体规则来设计贪心策略,以确保最终得到全局最优解。
最优子结构证明
- 定义问题:
S[i]
表示凑出金额 i 所需的最小硬币数。c[j]
是一个特定面值的硬币。
- 减少一个硬币:
- 假设我们已经知道了凑出金额
i
所需的最优硬币数S[i]
,并且这个解中包括了至少一个面值为c[j]
的硬币。 - 现在我们尝试去掉一个
c[j]
硬币,这将改变问题为用最少的硬币凑出金额i - c[j]
。 - 我们设
S[j]
是凑出金额j
的最优解。如果从S[j]
减去一个c[j]
硬币,问题变为求解n - c[j]
的最优解,硬币数变为k-1
。
- 假设我们已经知道了凑出金额
- 证明最优子结构:
- 如果我们假设存在另一个解
T[i]
,使得T[i]
是金额n - c[j]
的最优解,而且所用的硬币数m
小于k-1
。 - 那么,如果我们在
T[i]
的基础上加上一个c[j]
硬币,我们将得到一个用于金额n
的解,总硬币数是m + 1
。 - 根据假设,这个解的硬币数
m + 1
小于k
(因为m < k-1
),这与S[i]
是金额n
的最少硬币数的假设矛盾,因为我们已经找到了一个更小的硬币数。
- 如果我们假设存在另一个解
- 结论:
- 这个矛盾说明了我们的初步假设(存在一个比
S[i]
更好的解)是错误的。这意味着减去一个硬币后的问题(金额n - c[j]
)的最优解确实是S[i]
的子问题的最优解。 - 因此,问题满足最优子结构性质,即整个问题的最优解可以通过其子问题的最优解来构造。
- 这个矛盾说明了我们的初步假设(存在一个比
最终代码
d = [0.05,0.1,0.2,0.5,1,2] # 存储每种硬币面值
d_num = [] # 存储每种硬币的数量
s = 0
# 拥有的零钱总和
temp = raw_input('请输入每种零钱的数量:')
d_num0 = temp.split(" ")
for i in range(0, len(d_num0)): d_num.append(int(d_num0[i]))
s += d[i] * d_num[i] # 计算出收银员拥有多少钱
sum = float(raw_input("请输入需要找的零钱:"))
if sum > s:
# 当输入的总金额比收银员的总金额多时,无法进行找零
print("数据有错")
return 0
s = s - sum
# 要想用的硬币数量最少,那么需要利用面值大的硬币,
# 因此从数组的面值大的元素开始遍历
i = 5
while i >= 0:
if sum >= d[i]: #优先选择最大的面值
n = int(sum / d[i])
if n >= d_num[i]:
n = d_num[i] # 更新n
sum -= n * d[i] # 贪心的关键步骤,令sum动态改变
print("用了%d个%f元硬币"%(n, d[i]))
i -= 1