基于python的数据结构和算法(北京大学)第六章(贪心策略和动态规划)
-
分治策略:
解决问题的典型策略:分而治之
将问题分为若干更小规模的部分
通过解决每一个小规模部分问题,并将结果汇总得到原问题的解
分治策略和递归算法的联系:
-
从找零问题中看贪心策略:
贪心策略(Greedy Method):每次都试图解决问题尽量大的一部分。
贪心策略解法:从最大面值的硬币开始,用尽量多的数量,有余额的,再到下一最大面值的硬币,还用尽量多的数量,一直到最小面值硬币为止。
递归解法:
兑换硬币最简单直接的情况就是需要兑换的面值正好等于某种硬币,就只用找零1枚硬币,也就是递归的基本结束条件。
其次需要减小问题的规模,我们要对每种硬币尝试1次,例如美元体系:找零减去1分(penny)后,求兑换硬币最少数量(递归调用自身);找零减去5分(nikel)后,求兑换硬币最少数量;找零减去10分(dime)后,求兑换硬币最少数量;找零减去25分(quarter)后,求兑换硬币最少数量;上述4项中选择最小的一个。
# 效率极差递归解法: 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))
上述代码效率低下的重要原因就是重复计算太多,算法改进的关键就在于消除重复计算。可以用一个表将计算过的中间结果保存起来,在计算之前查表看看是否已经计算过。这个算法的中间结果就是部分找零的最优解,在递归调用过程中已经得到的最优解被记录下来。在递归调用之前,先查找表中是否已有部分找零的最优解,如果有,直接返回最优解而不进行递归调用,如果没有才进行递归调用。
这种技术叫做memoization(记忆化/函数值缓存)技术。
# 递归解法改进代码: def recDC(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 + recDC(coinValueList,change-i,knownResults) if numCoins < minCoins: minCoins = numCoins # 找到最优解,记录到表中 knownResults[change] = minCoins return minCoins print(recDC([1,5,10,25],63,[0]*64))
- 动态规划法:
从最简单的“1分钱找零”的最优解开始,逐步递加上去,直到我们需要的找零钱数。
在找零递加的过程中,设法保持每一分钱的递加都是最优解,一直加到求解找零钱数,自然得到最优解。
采用动态规划来解决11分钱的兑换问题:
从1分钱兑换开始,逐步建立一个兑换表。
计算11分钱的兑换法,我们做如下几步:
1.首先减去1分硬币,剩下10分钱查表最优解是1(2个)
2.然后减去5分硬币,剩下6分钱查表最优解是2(3个)
3.最后减去10分硬币,剩下1分钱查表最优解是1(2个)
通过上述最小值得到最优解:2个硬币
def dpMakeChange(coinValueList,change,minCoins): # 从1分开始到change逐个计算最少硬币数 for cents in range(1,change+1): # 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 # 得到当前最少硬币数,记录在表中 minCoins[cents] = coinCount # 返回最后一个结果 return minCoins[change] print(dpMakeChange([1,5,10,21,25],63,[0]*64))
我们注意到动态规划算法的dpMakeChange并不是递归函数,虽然这个问题是从递归算法开始解决,但最终我们得到一个更有条理的高效非递归算法。
动态规划中最主要的思想是: 从最简单的情况开始到达所需找零的循环,其每一步都依靠以前的最优解来得到本步骤的最优解,直到得到答案。
代码改进:返回硬币如何组合
# 代码改进:返回硬币如何组合 def dpMakeChange(coinValueList,change,minCoins,coinsUsed): for cents in range(1,change+1): coinCount = cents newCoin = 1 # 初始化一下新加硬币 for j in [c for c in coinValueList if c <= cents]: if minCoins[cents - j] + 1 < coinCount: coinCount = minCoins[cents - j] + 1 newCoin = j # 对应最小数量,所减的硬币 minCoins[cents] = coinCount coinsUsed[cents] = newCoin return minCoins[change] def printCoins(coinsUsed,change): coin = change while coin > 0: thisCoin = coinsUsed[coin] print(thisCoin) coin = coin - thisCoin amnt = 63 clist = [1,5,10,21,25] coinsUsed = [0]*(amnt+1) coinCount = [0]*(amnt+1) print("Making change for",amnt,"requires") print(dpMakeChange(clist,amnt,coinCount,coinsUsed),"coins") print("They are:") printCoins(coinsUsed,amnt) print("The used list is as follows:") print(coinsUsed)
- 博物馆大盗问题:
大盗潜入博物馆,面前有5件宝物,分别有重量和价值,大盗的背包仅能负重20公斤,请问如何选择宝物,总价值最高?
我们把m(i, W)记为:前i(1 <= i <= 5)个宝物中,组合不超过W(1 <= W <= 20)重量,得到的最大价值m(i, W)应该是m(i-1, W)和m(i-1, W-Wi)+vi两者最大值。我们从m(1, 1)开始计算到m(5, 20)。
动态规划:
# 宝物的重量和价值 tr = [None,{'w':2,'v':3},{'w':3,'v':4},{'w':4,'v':8}, {'w':5,'v':8},{'w':9,'v':10}] max_w = 20 # 大盗最大承重 # 初始化二维表格m[(i,w)],表示前i个宝物中,最大重量w的组合,所得到的最大价值 # 当i什么都不取,或w上限为0,价值均为0 m = {(i,w):0 for i in range(len(tr)) for w in range(max_w+1)} # 逐个填写二维表格 for i in range(1,len(tr)): for w in range(1,max_w+1): if tr[i]['w'] > w: # 装不下第i个宝物 m[(i,w)] = m[(i-1,w)] # 不装第i个宝物 else: # 不装第i个宝物,装第i个宝物,两种情况下最大价值 m[(i,w)] = max( m[(i-1,w)], m[(i-1,w-tr[i]['w'])] + tr[i]['v']) print(m[(len(tr)-1,max_w)])
递归:
# 宝物的重量和价值 tr = {(2,3),(3,4),(4,8),(5,8),(9,10)} max_w = 20 # 大盗最大承重 m = {} # 初始化记忆化表格m,key是(宝物组合,最大重量),value是最大价值 def thief(tr,w): if tr == set() or w == 0: m[(tuple(tr),w)] = 0 return 0 elif (tuple(tr),w) in m: # 若该组合已经存在与记忆化表格m,就直接返回 return m[(tuple(tr),w)] else: vmax = 0 for t in tr: if t[0] <= w: # 逐个从集合中去掉某个宝物,递归调用 # 选出所有价值中的最大值 v = thief(tr-{t},w-t[0]+t[1]) vmax = max(vmax,v) m[(tuple(tr),w)] = vmax return vmax print(thief(tr,max_w))
- 基于python的数据结构和算法(北京大学)第七章(排序和查找)
- 基于python的数据结构和算法(北京大学)散列表
- 信息度的分类提取策略-------基于python(网络爬虫)
- LeetCode--Best Time to Buy and Sell Stock (贪心策略 or 动态规划)
- LeetCode--Best Time to Buy and Sell Stock (贪心策略 or 动态规划)
- 集合覆盖问题-基于贪心思想的近似算法-python实现
- python买卖股票的最佳时机(基于贪心/蛮力算法)
- 贪心算法和动态规划的思路及其Python实现
- MOOC —— Python语言基础与应用 by 北京大学 第六章 计算和控制流(二)
- 金融市场量化交易配对策略(二):跑赢沪深300十个点(基于聚宽python2.7平台)
- SSCFLP Benchmark Data 基于贪心策略的局部搜索算法结果:
- 算法很美之贪心策略与动态规划
- 金融市场量化交易配对策略(一):寻找配对股票(基于聚宽python2.7平台)
- SSCFLP Benchmark Data 基于贪心策略的模拟退火算法结果:
- 小鱼要学数据结构与算法(基于python)—Day8优化问题与策略
- 基于贪心策略的活动选择问题
- 一种基于贪心选择策略的哈夫曼算法
- LeetCode--Best Time to Buy and Sell Stock (贪心策略 or 动态规划)
- 递归应用——贪心策略解决找零问题的python实现
- 【心得分享】基于华为云的Python3-ECS-OBS爬虫实验总结