算法导论程序35--动态规划(钢条切割)

求解一个如何切割钢条的问题:

公司购买长钢条,将其切割为短钢条出售。切割工序本身没有成本支出,公司管理层希望知道最佳的切割方案。

假设我们知道公司出售一段长度为i英寸的钢条的价格为pi(i=1,2,...,)


钢条切割问题:

给定一段长度为n英寸的钢条和一个价格表pi(i=1,2,...,n),求切割钢条方案,是的销售收益rn最大。

注意:如果长度为n英寸的钢条的价格的pn足够大,最优解可能就是完全不需要切割。

长度为n英寸的钢条共有2的n-1次方种不同的切割方案,因为在距离钢条i(i=1,2,...,n-1)英寸处,我们总是可以选择切割或不切割。

如果一个最优解将钢条切割为k段(对某个1=<k<=n),那么最优切割方案:

n=i1+i2+i3+...+ik

将钢条切割为长度分别为i1,i2,...,ik的小段,得到最大收益:

rn=pi1+pi2+...+pik

更一般地,对于rn(n>=1),我们可以用更短的钢条的最优切割收益来描述它:


第一个参数pn表示不切割,直接出售长度为n英寸的钢条的方案。其他n-1个参数对应另外n-1种方案:

对每个i=1,2,...,n-1,首先将钢条切割为长度为i和n-i的两段,接着求解这两段的最优切割收益

由于无法预知哪种方案会获得最优收益,我们必须考察所有可能的i,选取其中收益最大者。


为了求解规模为n的原问题,我们先求解形式完全一样,但规模更小的子问题。即当完成首次切割后,我们将两段钢条看成两个独立的钢条切割问题实例。我们通过组合两个相关子问题的最优解。并在所有可能的两段切割方案中选取组合收益最大者,构成原问题的最优解。

最优子结构性质:

问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。


自顶向下递归实现:

def cut_rod(p,n):
    if n==0:
        return 0
    q=float("-inf")
    for i in range(0,n):
        q=max(q,p[i]+cut_rod(p,n-i-1))
    return q

if __name__=='__main__':
    p=[1,5,8,9,10,17,17,20,24,30]
    for i in range(1,11):
        print("长度为",i,"的钢条的切割最大收益为:",end='')
        print(cut_rod(p,i))
运行:

>>> 
==== RESTART: D:\Program Files\Python\test\algorithms\算法导论\35-cut-rod.py ====
长度为 1 的钢条的切割最大收益为:1
长度为 2 的钢条的切割最大收益为:5
长度为 3 的钢条的切割最大收益为:8
长度为 4 的钢条的切割最大收益为:10
长度为 5 的钢条的切割最大收益为:13
长度为 6 的钢条的切割最大收益为:17
长度为 7 的钢条的切割最大收益为:18
长度为 8 的钢条的切割最大收益为:22
长度为 9 的钢条的切割最大收益为:25
长度为 10 的钢条的切割最大收益为:30


但是,上面的程序真的效率好低呀!




使用动态规划方法求解最优钢条切割问题:

朴素递归算法之所以效率很低,是因为它反复求解相同的子问题。因此,动态规划方法仔细安排求解顺序,对每个子问题之求解一次,并将结果保存下来。如果随后再次需要此子问题的解,只需查找保存的结果,而不必重新计算。


因此,动态规划方法是付出额外的内存空间来节省计算时间,是典型的时空权衡的例子。


两种实现方式:

(1)带备忘的自顶向下方法:

def memorized_cut_rod(p,n):
    r=[]
    for i in range(n):
        r.append(float("-inf"))
    return memorized_cut_rod_aux(p,n,r)

def memorized_cut_rod_aux(p,n,r):
    if r[n-1]>=0:
        return r[n-1]
    if n==0:
        q=0
    else:
        q=float("-inf")
        for i in range(0,n):
            q=max(q,p[i]+memorized_cut_rod_aux(p,n-i-1,r))
    r[n-1]=q
    return q

运行:

print("动态规划方法:")
    for i in range(1,11):
        print("长度为",i,"的钢条的切割最大收益为:",end='')
        print(memorized_cut_rod(p,i))
动态规划方法:
长度为 1 的钢条的切割最大收益为:1
长度为 2 的钢条的切割最大收益为:5
长度为 3 的钢条的切割最大收益为:8
长度为 4 的钢条的切割最大收益为:10
长度为 5 的钢条的切割最大收益为:13
长度为 6 的钢条的切割最大收益为:17
长度为 7 的钢条的切割最大收益为:18
长度为 8 的钢条的切割最大收益为:22
长度为 9 的钢条的切割最大收益为:25
长度为 10 的钢条的切割最大收益为:30


(2)自底向上:

def bottom_up_cut_rod(p,n):
    r=[]
    for i in range(n+1):
        r.append(float("-inf"))
    r[0]=0
    for j in range(n):
        q=float("-inf")
        for i in range(j+1):
            q=max(q,p[i]+r[j-i])
        r[j+1]=q
    return r[n]
运行:

    print("动态规划方法2:")
    for i in range(1,11):
        print("长度为",i,"的钢条的切割最大收益为:",end='')
        print(bottom_up_cut_rod(p,i))

动态规划方法2:
长度为 1 的钢条的切割最大收益为:1
长度为 2 的钢条的切割最大收益为:5
长度为 3 的钢条的切割最大收益为:8
长度为 4 的钢条的切割最大收益为:10
长度为 5 的钢条的切割最大收益为:13
长度为 6 的钢条的切割最大收益为:17
长度为 7 的钢条的切割最大收益为:18
长度为 8 的钢条的切割最大收益为:22
长度为 9 的钢条的切割最大收益为:25
长度为 10 的钢条的切割最大收益为:30

重构方法:

长度为j的钢条不仅计算最大收益rj,还保存最优解对应的第一段钢条的切割长度sj。

def extended_bottom_up_cut_rod(p,n):
    r=[]
    s=[]
    for i in range(n+1):
        r.append(float("-inf"))
        s.append(float("-inf"))
    r[0]=0
    s[0]=0
    #j+1表示钢条的长度,i+1表示第一段的长度
    for j in range(n):
        q=float("-inf")
        for i in range(j+1):
            if q<p[i]+r[j-i]:
                q=p[i]+r[j-i]
                s[j+1]=i+1
        r[j+1]=q
    return r,s

运行结果:

    print("动态规划重构方法:")
    for i in range(1,11):
        print("长度为",i,"的钢条的切割最大收益为:",extended_bottom_up_cut_rod(p,i)[0][i],'  ',end='')
        print("第一段钢条的切割长度为:",extended_bottom_up_cut_rod(p,i)[1][i])

动态规划重构方法:
长度为 1 的钢条的切割最大收益为: 1   第一段钢条的切割长度为: 1
长度为 2 的钢条的切割最大收益为: 5   第一段钢条的切割长度为: 2
长度为 3 的钢条的切割最大收益为: 8   第一段钢条的切割长度为: 3
长度为 4 的钢条的切割最大收益为: 10   第一段钢条的切割长度为: 2
长度为 5 的钢条的切割最大收益为: 13   第一段钢条的切割长度为: 2
长度为 6 的钢条的切割最大收益为: 17   第一段钢条的切割长度为: 6
长度为 7 的钢条的切割最大收益为: 18   第一段钢条的切割长度为: 1
长度为 8 的钢条的切割最大收益为: 22   第一段钢条的切割长度为: 2
长度为 9 的钢条的切割最大收益为: 25   第一段钢条的切割长度为: 3
长度为 10 的钢条的切割最大收益为: 30   第一段钢条的切割长度为: 10

下面的过程,接受两个参数:价格表p和钢条长度n,然后调用extended_bottom_up_cut_rod(p,n)来静思园切割下来的每段钢条的长度s[1...n]。最后输出长度为n的钢条的完整的最优切割方案:

def print_cut_rod_solution(p,n):
    (r,s)=extended_bottom_up_cut_rod(p,n)
    while n>0:
        print(s[n],' ',end='')
        n=n-s[n]
    print()
运行结果:

    for i in range(1,11):
        print("长度为",i,"的钢条的切割最大收益的切割方案为:",end='')
        print_cut_rod_solution(p,i)

长度为 1 的钢条的切割最大收益的切割方案为:1  
长度为 2 的钢条的切割最大收益的切割方案为:2  
长度为 3 的钢条的切割最大收益的切割方案为:3  
长度为 4 的钢条的切割最大收益的切割方案为:2  2  
长度为 5 的钢条的切割最大收益的切割方案为:2  3  
长度为 6 的钢条的切割最大收益的切割方案为:6  
长度为 7 的钢条的切割最大收益的切割方案为:1  6  
长度为 8 的钢条的切割最大收益的切割方案为:2  6  
长度为 9 的钢条的切割最大收益的切割方案为:3  6  
长度为 10 的钢条的切割最大收益的切割方案为:10  




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值