基于python的数据结构和算法(北京大学)第六章(贪心策略和动态规划)

  • 分治策略:
    解决问题的典型策略:分而治之
    将问题分为若干更小规模的部分
    通过解决每一个小规模部分问题,并将结果汇总得到原问题的解
    基于python的数据结构和算法(北京大学)第六章(贪心策略和动态规划)_第1张图片
    分治策略和递归算法的联系:
    基于python的数据结构和算法(北京大学)第六章(贪心策略和动态规划)_第2张图片

  • 从找零问题中看贪心策略:
    贪心策略(Greedy Method):每次都试图解决问题尽量大的一部分。
    贪心策略解法:从最大面值的硬币开始,用尽量多的数量,有余额的,再到下一最大面值的硬币,还用尽量多的数量,一直到最小面值硬币为止。

递归解法:
兑换硬币最简单直接的情况就是需要兑换的面值正好等于某种硬币,就只用找零1枚硬币,也就是递归的基本结束条件。
其次需要减小问题的规模,我们要对每种硬币尝试1次,例如美元体系:找零减去1分(penny)后,求兑换硬币最少数量(递归调用自身);找零减去5分(nikel)后,求兑换硬币最少数量;找零减去10分(dime)后,求兑换硬币最少数量;找零减去25分(quarter)后,求兑换硬币最少数量;上述4项中选择最小的一个。
基于python的数据结构和算法(北京大学)第六章(贪心策略和动态规划)_第3张图片

# 效率极差递归解法:
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分钱兑换开始,逐步建立一个兑换表。
    基于python的数据结构和算法(北京大学)第六章(贪心策略和动态规划)_第4张图片
    计算11分钱的兑换法,我们做如下几步:
    1.首先减去1分硬币,剩下10分钱查表最优解是1(2个)
    2.然后减去5分硬币,剩下6分钱查表最优解是2(3个)
    3.最后减去10分硬币,剩下1分钱查表最优解是1(2个)

通过上述最小值得到最优解:2个硬币
基于python的数据结构和算法(北京大学)第六章(贪心策略和动态规划)_第5张图片

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公斤,请问如何选择宝物,总价值最高?
    基于python的数据结构和算法(北京大学)第六章(贪心策略和动态规划)_第6张图片
    我们把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)。
    基于python的数据结构和算法(北京大学)第六章(贪心策略和动态规划)_第7张图片
    基于python的数据结构和算法(北京大学)第六章(贪心策略和动态规划)_第8张图片
    动态规划:
# 宝物的重量和价值
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))

你可能感兴趣的:(数据结构,动态规划,算法)