应用条件
本博客在学习北京大学陈斌老师《数据结构与算法》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]
代码参考自陈斌老师的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]列表的内容。换句话说,本文只想将之前的列表取过来加点东西放进新列表,结果之前的列表被改写了。
解释:将一个列表赋值给另一个列表的形式,则是将原列表指向的位置空间也指向了新的列表,所以对同一片内存空间进行修改会相互影响的。因此以后再编程中要加以关注!!