算法学习-动态规划

应用条件

本博客在学习北京大学陈斌老师《数据结构与算法》MOOC课程中总结反思形成。(续算法学习-递归算法

动态规划算法在陈老师体系中是延续着递归算法继续深入展开的。在学习前,我有一个困惑,就是动态规划算法和递归算法+函数值缓存技术(备忘录技术)有什么区别呢?

先看动态规划的使用条件:

  • 问题的最优解包含了更小规模子问题的最优解

这使得我产生了困惑,因为递归也是可以实现同样的事情的,我百度了常见的回答,应该说有相当一部分人认为他们两者本质上有共通之处,或者就是认为动态规划就是(递归+备忘录)。

这让我觉得不必过分纠结,陈老师课件中的回答是:

动态规划算法虽然这个问题是从递归算法开始解决,但最终得到一个更有条理的高效非递归算法

案例分享

本博客分析一个经典的应用场景“找零问题”,通过与递归算法展开对比加深理解。

问题描述

某货币体系中找零兑换的最少货币数问题

解法分享

# 找零兑换:动态规划算法代码
def dpMakeChange(coinValueList, change, minCoins):
    # 从1分开始到change逐个计算最少硬币数
    for cents in range(1, change + 1):
        # 1.初C始化一个最大值
        coinCount = cents
        # 2.减去每个硬币,向后查最少硬币数,同时记录总的最少数
        for j in [c for c in coinValueList if c <= cents]:
            if minCoins[cents - j] + 1 < coinCount:
                coinCount = minCoins[cents - j] + 1
        # 3.得到当前最少硬币数,记录到表格中
        minCoins[cents] = coinCount

    # 循环结束,得到最优解,返回最后一个结果
    return minCoins[change]

image-20210919175402539

代码参考自陈斌老师的PPT,我这里分析了递归算法和动态规划算法的时间开销,可以发现

  • 动态规划算法的时间开销更少,大约只有递归算法的 1 4 \frac{1}{4} 41
  • 从代码分析看,内存的开销相差不多

结果回溯

动态规划解法和递归解法的结果回溯都是一个相对来说需要单独考虑的问题。

下文分别给出老师和我提供的结果回溯代码,并作对比分析。

# 我的解法-结果回溯
def dpMakeChangeToresult(coinValueList, change, minCoins,changeResult):
    # 从1分开始到change逐个计算最少硬币数
    for cents in range(1, change + 1):
        # 1.初C始化一个最大值
        coinCount = cents
        Result = [1]*cents
        # 2.减去每个硬币,向后查最少硬币数,同时记录总的最少数
        for j in [c for c in coinValueList if c <= cents]:
            if minCoins[cents - j] + 1 < coinCount:
                coinCount = minCoins[cents - j] + 1
                # 涉及一些拷贝的问题
                Result = changeResult[cents - j].copy()
                Result.append(j)
        # 3.得到当前最少硬币数,记录到表格中
        minCoins[cents] = coinCount
        changeResult[cents] = Result

    # 循环结束,得到最优解,返回最后一个结果
    return minCoins[change],changeResult[cents]
    
print(dpMakeChangeToresult(clist,amnt,coinCount,[[]]*(amnt+1)))
# 老师解法:结果回溯
def dpMakeChange1(coinValueList, change, minCoins, coinUsed):
    # 从1分开始到change逐个计算最少硬币数
    for cents in range(1, change + 1):
        # 1.初C始化一个最大值
        newCoin = 1
        coinCount = cents
        # 2.减去每个硬币,向后查最少硬币数,同时记录总的最少数
        for j in [c for c in coinValueList if c <= cents]:
            if minCoins[cents - j] + 1 <= coinCount:
                coinCount = minCoins[cents - j] + 1
                newCoin = j
        # 3.得到当前最少硬币数,记录到表格中
        minCoins[cents] = coinCount
        coinUsed[cents] = newCoin

    # 循环结束,得到最优解,返回最后一个结果
    return minCoins[change]


def printCoins(coinUsed, change):
    coin = change
    while coin > 0:
        thisCoin = coinUsed[coin]
        print(thisCoin)
        coin = coin - thisCoin

可以发现我的解法

  • 理解逻辑和动态规划逻辑相似,方便,但是问题就是会占据大量内存,同时涉及一些拷贝问题
  • 老师的解法,实现了拆分和倒推,理解很有趣味,不会涉及拷贝问题

python基础

列表的拷贝问题,以前没有特别关注过,今天发现 如果直接使用

Result = changeResult[cents - j].append(j)

就会失去Result作为暂存变量的特性,结果直接影响到 changeResult[cents - j]列表的内容。换句话说,本文只想将之前的列表取过来加点东西放进新列表,结果之前的列表被改写了。

解释:将一个列表赋值给另一个列表的形式,则是将原列表指向的位置空间也指向了新的列表,所以对同一片内存空间进行修改会相互影响的。因此以后再编程中要加以关注!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

儒雅的钓翁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值