当前位置:   article > 正文

Python数据结构20:动态规划:找零兑换问题的动态规划解法并显示使用的硬币组合_python动态规划 零钱兑换

python动态规划 零钱兑换

在我们使用递归算法时,可能会出现规模庞大的重复计算,用一个中间表记录每个计算过的最优解法,就可以避免大量的重复计算。中间结果记录可以很好解决找零兑换问题。实际上,这种方法还不能称为动态规划,而是叫做“memoization(记忆化/函数值缓存)”的技术提高了递归解法的性能。

1. 找零兑换的动态规划解法

动态规划算法采用了一种更有条理的方式来得到问题的解。
找零兑换的动态规划算法从最简单的“1分钱找零”的最优解开始,逐步递加上去,直到我们需要的找零钱数。
在找零递加的过程中,设法保持每一分钱的递加都是最优解,一直加到求解找零钱数,自然得到最优解。
递加的过程能保持最优解的关键是,其依赖于更少钱数最优解的简单计算,而更少钱数的最优解已经得到了。
问题的最优解包含了更小规模子问题的最优解,这是一个最优化问题能够用动态规划策略解决的必要条件。
originalamount找零兑换问题具体来说就是:

numCoins 就是当前需要找零的硬币个数。
1 5 10 25就是硬币的币值。
originalamount就是要找零的钱数。

例如用动态规划解决11分钱的兑换问题。

  1. 从1分钱兑换开始,逐步建立一个兑换表。
看这个表的顺序就是从上往下。

兑换5分钱的时候,最优解就是一个5分的硬币。

  1. 计算11分钱的兑换法,我们做如下几步:
  • 首先减去1分硬币,剩下10分钱查表最优解是1,共需要2个硬币
  • 然后减去5分硬币,剩下6分钱查表最优解是2,共需要3个硬币
  • 最后减去10分硬币,剩下1分钱查表最优解是1,共需要2个硬币

25分就不减了,因为明显 11 < 24,不可能用25分来找零。

  1. 通过上述最小值得到最优解:2个硬币

解决思路如下:

need_change = 65
init_min_coins = [0] * (need_change + 1)  # 建一个空表
real_value_list = [1, 5, 10, 25]


def dynamic_programming(change, min_coins, value_list):
    for current_change in range(1, change + 1):
        min_coins[current_change] = current_change  # 最坏的情况是只用一分钱兑换
        for value in value_list:  # 遍历每个币值
            if current_change >= value:
                if 1 + min_coins[current_change - value] < min_coins[current_change]:
                    # 若使用当前币值时,需要的最小硬币个数最小,就更新最小硬币数表中的个数
                    min_coins[current_change] = 1 + min_coins[current_change - value]
    return min_coins[change]  # 直接查表,最后一个就是我们想要的结果


print(dynamic_programming(need_change, init_min_coins, real_value_list))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

我们注意到动态规划算法的dynamic_programming并不是递归函数。虽然这个问题是从递归算法开始解决,但最终我们得到一个更有条理的高效非递归算法。

动态规划中最主要的思想是:

  • 最简单情况开始到达所需找零的循环
  • 其每一步都依靠以前的最优解来得到本步骤的最优解,直到得到答案

2. 找零兑换:动态规划算法扩展

前面的算法已经得到了最少硬币的数量,但没有返回硬币如何组合。
扩展算法的思路很简单,只需要在生成最
优解列表同时跟踪记录所选择的那个硬币币值即可。
在得到最后的解后,减去选择的硬币币值,回溯到表格之前的部分找零,就能逐步得到每一步所选择的硬币币值。

need_change = 63
init_min_coins = [0] * (need_change + 1)  # 建一个空表
init_coins_used = [0] * (need_change + 1)  # 建一个空表用于保存每一步新增的硬币币值
real_value_list = [1, 5, 10, 21, 25]


def dynamic_programming(change, min_coins, value_list, coins_used):
    for current_change in range(1, change + 1):
        min_coins[current_change] = current_change  # 最坏的情况是只用一分钱兑换
        for value in value_list:  # 遍历每个币值
            if current_change >= value:
                if 1 + min_coins[current_change - value] <= min_coins[current_change]:
                    # 若使用当前币值时,需要的最小硬币个数最小,就更新最小硬币数表中的个数
                    min_coins[current_change] = 1 + min_coins[current_change - value]
                    coins_used[current_change] = value  # 当前使用的币值存入到表中
    return min_coins[change], coins_used  # 直接查表,最后一个就是我们想要的结果


def combination_values(change, coins_used):
    print(coins_used[change])
    while change > 1:
        # 每次显示并减去当前新增的硬币,就是回溯了使用的所有币值,即显示了每次新增的硬币的币值
        if coins_used[change - coins_used[change]] > 0:
            print(coins_used[change - coins_used[change]])
        change = change - coins_used[change]


min_coins, coins_used = dynamic_programming(need_change, init_min_coins, real_value_list, init_coins_used)
print(min_coins)
print(coins_used)
combination_values(need_change, coins_used)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

3
[0, 1, 1, 1, 1, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 21, 21, 21, 21, 25, 25, 25, 25, 25, 25, 21, 21, 21, 21, 25, 25, 25, 25, 25, 25, 21, 21, 21, 21, 25, 25, 25, 25, 25, 25, 25, 21, 21, 21, 25, 25, 25, 25, 25, 25, 25, 21, 21]
21
21
21

参考

本文的知识来源于B站视频 【慕课+课堂实录】数据结构与算法Python版-北京大学-陈斌-字幕校对-【完结!】,是对陈斌老师课程的复习总结
代码是我自己写的,功能比较简单

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/243838
推荐阅读
相关标签
  

闽ICP备14008679号