3递归(下)——数据结构与算法Python版学习笔记

分治策略

解决问题的典型策略:
将问题分为若干更小规模的部分
通过解决每一个小规模部分问题,并将结果汇总得到原问题的解

优化问题

找到某些问题的最优解
经典案例:兑换最少个数的硬币问题
解决:贪心策略

  • 因为每次都试图解决问题的尽量大的一部分。对应到兑换硬币问题,就是每次以最多数量的最大面值硬币来迅速减少找零面值
  • 在货币的硬币体系下表现尚好
  • 有21的硬币就失效了:63=25 * 2+10 * 1+1 * 3=21 * 3

找零兑换问题的递归解法

1.首先确定基本结束条件:
兑换硬币最简单的就是需要兑换的找零,其面值正好等于某种硬币
2.其次是减小问题的规模:
我们要对每种硬币尝试1次,例如美元体系

找零减去1分(penny)后,求兑换硬币最少数量(递归调用自身)
5分(nikel)后
10分(dime)后
25分(quarter)后
上述4项中选择最小的一个
在这里插入图片描述

极其低效的递归算法
在这里插入图片描述

import 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
start = time.time()
print(recMC([1,5,10,25],63))
end = time.time()
print(end - start)

在这里插入图片描述
改进的关键:消除重复
我们可以用一个表将计算过的中间结果保存起来,在计算之前查表看看是否已经计算过

这个算法的中间结果就是部分找零的最优解
在递归调用之前,先查表如果有部分找零的最优解不用进行递归调用,如果没有才进行

在这里插入图片描述

import time
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
start = time.time()
print(recDC([1,5,10,25],63,[0]*64))
end = time.time()
print(end - start)

值得学习的:用64个0的列表来记录63找零的
“备忘录技术”/动态缓存/记忆化

找零兑换的动态规划解法

找零兑换的动态规划算法从最简单的“1分钱找零”的最优解开始,逐步递加上去,直到我们需要的诏令签署
在找零递加的过程中,设法保持每一分钱的递加都是最优解,一直加到求解找零钱数,自然得到最优解

问题的最优解包含了更小规模子问题的最优解,这是一个最优化问题能够用动态规划策略解决的必要条件。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import time
def dpMakeChange(coinValueList,change,minCoins):
        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
                #3.得到当前最少硬币数,记录到表中
                minCoins[cents] = coinCount
        return minCoins[change]
start = time.time()
print(dpMakeChange([1,5,10,21,25],63,[0]*64))
end = time.time()
print(end - start)

扩展

生成最优解列表同时跟踪记录所选的那个硬币币制

在得到最后的解后,减去选择的硬币币值,回溯到表格之前的部分找零,就能逐步得到每一步所选择的硬币币制

在这里插入图片描述

def dpMakeChange(coinValueList,change,minCoins,coinUsed):
        for cents in range(1,change + 1):
                coinCount = cents#1.初始化一个最大值
                newCoin = 1
                #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 #对应最小数量,所减的硬币
                minCoins[cents] = coinCount#3.得到当前最少硬币数,记录到表中
                coinsUsed[cents] = newCoin #记录本步骤加的1个硬币
        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)

博物馆大盗问题

在这里插入图片描述
在这里插入图片描述

m(i,W)记为
表示前i个宝物中,重量之和不超过W的组合,所得到的最大价值
在这里插入图片描述

# 宝物的重量和价值
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
# key是(宝物组合,最大重量),value是最大价值
m = {}
def thief(tr,w):
        if tr == set() or w == 0:
                m[tuple(tr),w] = 0 #tuple是key的要求
                return 0
        elif (tuple(tr),w) in 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))

在这里插入图片描述
仍没弄懂请移步B站详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值