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

来源:互联网 发布:健身房健身计划软件 编辑:程序博客网 时间:2024/05/29 05:52

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

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

假设我们知道公司出售一段长度为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 qif __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  




原创粉丝点击