动态规划--钢条切割收益最大化问题

动态规划–钢条切割收益最大化问题

这个是《算法导论》中动态规划一章的问题

问题:

对应给定长度n米的钢条,将其切割成k段(或不切k=1)出售,使收益最大,其中长度为i米的钢条,其出售价格为 p i p_i pi

分析:

假设钢条被切成k段,每段的长度分别为 i 1 i_1 i1, i 2 i_2 i2, i 3 i_3 i3,…, i k i_k ik时,钢条的收益最大,此时钢条长度为
n = i 1 + i 2 + i 3 + . . . + i k n=i_1 + i_2 + i_3 + ... + i_k n=i1+i2+i3+...+ik

最大收益为
r n = p i 1 + p i 2 + p i 3 + . . . + p i k r_n=p_{i_1}+p_{i_2}+p_{i_3}+...+p_{i_k} rn=pi1+pi2+pi3+...+pik

假设钢条被切成两段,要是收益最大,则这两段必须以最大收益再进行切割,即
r i + r n − i r_i+r_{n-i} ri+rni

那么长度为n的钢条的最大收益为
r n = m a x ( p n , r 1 + r n − 1 , r 2 + r n − 2 , r 3 + r n − 3 , . . . , r n − 1 + r 1 ) r_n=max(p_n,r_1+r_{n-1},r_2+r_{n-2},r_3+r_{n-3},...,r_{n-1}+r_1) rn=max(pn,r1+rn1,r2+rn2,r3+rn3,...,rn1+r1)
其中n>=1, p n p_n pn对应长度为n的钢条,即不切割直接出售。

此时依然是求解最大收益问题,但问题规模更小了。

上边的问题可以使用更简单的求解方法,即不管钢条如何切割, 左边一定会有一段,假设左边的第一段长度为i(i可以等于n),则右边的长度为n-i,要使左边第一段长度为i时的钢条的收益最大,则要求右边n-i长度的钢条的收益最大,即为 r n − i r_{n-i} rni,
此时钢条的收益为
p i + r n − i p_i+r_{n-i} pi+rni
则长度为n的钢条的最大收益为
r n = m a x ( p 1 + r n − 1 , p 2 + r n − 2 , p 3 + r n − 3 , . . . , p n − 1 + r 1 ) r_n=max(p_1+r_{n-1},p_2+r_{n-2},p_3+r_{n-3},...,p_{n-1}+r_1) rn=max(p1+rn1,p2+rn2,p3+rn3,...,pn1+r1)
其中1<=i<=n,
从上边的分析可以看出我们求解规模为n的原问题,可以先求解形式完全一样,规模为n-i的子问题。
故钢条问题满足最优子结构性质:
问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。

现在使用python对上述问题求解,

递归方法

# 递归法
def cutRod(p,n):
    if not n:
        return 0
    qlist = []
    for i in range(1,n+1):
        qlist.append(p[i]+cutRod(p,n-i))
    return max(qlist)

设T(n)表示求长度为n的钢条收益时cutRod函数调用的次数,分析上述代码,很容易得到
T ( n ) = 1 + T ( 1 ) + T ( 2 ) + . . . + T ( n − 1 ) T(n)=1+T(1)+T(2)+...+T(n-1) T(n)=1+T(1)+T(2)+...+T(n1)
使用数学归纳法,很容易证明
T ( n ) = 2 n T(n)=2^n T(n)=2n
故利用上述递归法,函数cutRod的运行时间为n的指数函数

带备忘录的自顶向下方法

# 带备忘录自顶向下法
def medoizedCutRod(p,n):
    r=[-100 for i in range(0,n+1)]
    return memoizedCutRodAux(p,n,r)
       
def memoizedCutRodAux(p,n,r):
    if r[n]>=0:
        return r[n]
    if n==0:
        q=0
    else:
        q = -100
        for i in range(1,n+1):
            q = max(q,p[i]+memoizedCutRodAux(p,n-i,r))
    r[n]=q
    return q

自底向上法

# 自底向上法
def bottomUpCutRod(p,n):
    r=[0 for i in range(0,n)]
    for j in range(1,n+1):#长度为j的钢条
        q = -100
        for i in range(1,j+1):#第一段为i的切割方案
            q = max(q,p[i]+r[j-i])
        r[j] = q
    return r[n]

自顶向下法和自底向上法有相同的运行渐进时间,他们都是对相同的子问题只求解一次,分析可以得到他们时间复杂度为 Θ ( n 2 ) Θ(n^2) Θ(n2),区别在于自底向上法没有函数的递归调用,具有更小的系数。

切割方案

上述方法只是求出了最大收益,但没有给出最大收益的切割方案,现在对自底向上法对进行修改,以保存得到最大收益的切割方案

def extendedBottomUpCutRod(p,n):
    r=[0 for i in range(0,n+1)]
    s=[0 for i in range(0,n+1)]#保存长度为j的钢条最优切割方案的第一段长度
    for j in range(1,n+1):#长度为j的钢条
        q = -100
        for i in range(1,j+1):#第一段为i的切割方案
            if q < p[i]+r[j-i]:
                q = p[i]+r[j-i]
                s[j] = i
        r[j] = q
    return (r,s)
    
def printCutRodSolution(p,n):
    (r,s) = extendedBottomUpCutRod(p,n)
    while n:
        print(s[n])#输出切割方法
        n = n - s[n]

测试

全部测试代码如下:

# 出售价格
p={1:2,2:5,3:8,4:9,5:10,6:17,7:17,8:20,9:24,10:30}

# 递归法
def cutRod(p,n):
    if not n:
        return 0
    qlist = []
    for i in range(1,n+1):
        qlist.append(p[i]+cutRod(p,n-i))
    return max(qlist)

# 带备忘录自顶向下法
def medoizedCutRod(p,n):
    r=[-100 for i in range(0,n+1)]
    return memoizedCutRodAux(p,n,r)
       
def memoizedCutRodAux(p,n,r):
    if r[n]>=0:
        return r[n]
    if n==0:
        q=0
    else:
        q = -100
        for i in range(1,n+1):
            q = max(q,p[i]+memoizedCutRodAux(p,n-i,r))
    r[n]=q
    return q

# 自底向上法
def bottomUpCutRod(p,n):
    r=[0 for i in range(0,n+1)]
    for j in range(1,n+1):#长度为j的钢条
        q = -100
        for i in range(1,j+1):#第一段为i的切割方案
            q = max(q,p[i]+r[j-i])
        r[j] = q
    #print(r)
    return r[n]

# 输出切割方案和最大收益
def extendedBottomUpCutRod(p,n):
    r=[0 for i in range(0,n+1)]
    s=[0 for i in range(0,n+1)]#保存长度为j的钢条最优切割方案的第一段长度
    for j in range(1,n+1):#长度为j的钢条
        q = -100
        for i in range(1,j+1):#第一段为i的切割方案
            if q < p[i]+r[j-i]:
                q = p[i]+r[j-i]
                s[j] = i
        r[j] = q
    return (r,s)
    
def printCutRodSolution(p,n):
    (r,s) = extendedBottomUpCutRod(p,n)
    print("最大收益:",r[n])
    print("切割方案:",end=" ")
    while n:
        print(s[n],end=" ")#输出切割方法
        n = n - s[n]

if __name__ == "__main__":
    #钢条长度
    n = 9
    r = cutRod(p,n)
    print("最大收益:",r)
    print("****************")
    
    r = medoizedCutRod(p,n)
    print("最大收益:",r)
    print("****************")
    
    r = bottomUpCutRod(p,n)
    print("最大收益:",r)
    print("****************")
    
    printCutRodSolution(p,n)

运行结果:
最大收益: 25


最大收益: 25


最大收益: 25


最大收益: 25
切割方案: 3 6

总结

动态规划问题应具备两个要素:最优子结构和子问题重叠
最优子结构:
问题的最优解包含了子问题的最优解 (或问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解)。
最问题重叠:
问题的递归算法会反复求解相同的问题 (注意:分治算法的解也由相关子问题的解构成,但其每次递归都产生新的子问题,不具备子问题的重叠性)。

你可能感兴趣的:(动态规划,python)