Python数据结构与算法34:递归:找零兑换问题的递归解法

:本文如涉及到代码,均经过Python 3.7实际运行检验,保证其严谨性。

本文阅读时间约为7分钟

上一节提到,贪心策略会有失效的情况,如何在不依赖某种硬币体系的情况下找到找零硬币最少个数的最优解?

这就要求我们找一种“能行”的找到最优解的方法。所谓“能行”,是指不依赖于类似于美元那种固定的四种面值的硬币体系。

递归就是这么一种“能行”的方法。

递归解决找零问题的最优解

套用递归的三个条件:

兑换¢63的例子。

1)确定基本结束条件:需要兑换的找零,其面值正好等于某个货币单位,比如找零是¢25,就给1个¢25的硬币。

2)其次是减小问题的规模,我们要对每种硬币尝试1次,例如美元硬币体系:

  • 找零减去¢1后,求兑换硬币最少数量(递归调用自身,第3个条件);
  • 找零减去¢5后,求兑换硬币最少数量;
  • 找零减去¢10后,求兑换硬币最少数量;
  • 找零减去¢25后,求兑换硬币最少数量;

最后选择上面结果中最小的一个。

numCoins = min(1+numCoins(originalamout-1), 1+numCoins(originalamount-5), 1+numCoins(original-10), 1+nuCoins(originalamount-25))。
具体代码实现如下:

# 递归解决找零问题v1。
import time
start =  time.time()
def recMC(coinValueList, change):
    minCoins = change
    if change in coinValueList:
        return 1  # 最小规模,直接返回。
    else:
        for i in [c for c in coinValueList if c <= change]:
            numCoins = 1 + recMC(coinValueList, change - i)  # 调用自身和减少规模。
            if numCoins < minCoins:
                minCoins = numCoins
    return minCoins

print(recMC([1, 5, 10, 25], 63))
end = time.time()
print(f"运行本递归程序一共用了{end - start}秒。")

<<<
6
运行本递归程序一共用了37.6766254901886秒。
<<<
递归解决之道的问题

递归方法虽然“能行”,也就是能解决问题,但是其最大的问题在于:极其低效!!!

上述案例中,兑换¢63,需要进行67,716,925次递归调用。本计算机花了37秒时间才得到答案。

以26分兑换硬币为例,看看递归调用过程(377次递归的一小部分)。结果发现,重复计算太多,例如找零15分的出现了三次,而最终解决还要52次递归调用。而实际上一次就够了。所以递归算法的致命缺点就是重复的计算太多了。

递归解法的改进

对递归解法进行改进,关键之处就在于消除重复计算:

用一个表将计算过的中间结果保存起来,在计算之前查看表,看是否已经计算过。所谓中间结果,就是部分找零的最优解,在递归调用过程中已经得到的最优解被记录下来。在递归调研之前先查表是否已有部分找零的最优解,如果有,直接返回最优解而不是进行递归调用;如果没有,才进行递归调用。

# 递归解决找零问题v2。
import time
start =  time.time()
def recMC(coinValueList, change, knownResults):
    minCoins = change
    if change in coinValueList:
        knownResults[change] = 1  # 记录最优解。
        return 1  # 最小规模,直接返回。
    elif knownResults[change] > 0:
        return knownResults[change]  # 查表成功,直接用最优解。
    else:
        for i in [c for c in coinValueList if c <= change]:
            numCoins = 1 + recMC(coinValueList, change - i, knownResults)  # 调用自身和减少规模。
            if numCoins < minCoins:
                minCoins = numCoins
                # 找到最优解,记录到表中。
                knownResults[change] = minCoins
    return minCoins

print(recMC([1, 5, 10, 25], 63, [0] * 64))
end = time.time()
print(f"运行本递归程序一共用了{end - start}秒。")

<<<
6
运行本递归程序一共用了0.0009992122650146484秒。
<<<

可以看到,优化过后的程序运行时间从约38秒降低至1毫秒。
改进之后的解法,极大地减少了递归调用次数。对63分兑换硬币问题,仅仅需要221次递归调用,是改进之前的三十万分之一,程序效率得到极大提升。

To be continued.

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容