动态规划之钢条切割

钢条切割问题:给定一段长度为n英寸的钢条和一个价格表p_{i}(i=1,2,...,n),求切割钢条方案,使得销售收益r_{n}最大。注意,如果长度为n英寸的钢条的价格p_{n}足够大,最优解可能就是完全不需要切割。

一个价格表的样例:

长度i12345678910
价格p_{i}1589101717202430

对于r_{n}(n\geqslant 1),可以用更短的钢条的最优切割收益来描述它:

r_{n}=max(p_{n},r_{1}+r_{n-1},r_{2}+r_{n-2},...,r_{n-1}+r_{1})

最优子结构:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。在钢条切割问题中,当完成首次切割后,我们将两段钢条看成两个独立的钢条切割问题实例,通过组合两个相关子问题的最优解,并在所有可能的两段切割方案中选取组合收益最大者,构成原问题的最优解。

一种相似的但更为简单的递归求解方法:将钢条从左边切割下长度为i的一段,只对右边剩下的长度为n-i的一段继续进行切割(递归求解),对左边的一段则不再进行切割。

r_{n}=max(p_{i}+r_{n-i})

1.自顶向下递归实现:

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

其运行时间T(n)=1+\sum_{j=0}^{n-1}T(j)

可以推出T(n)=2^{n}

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

(1)带备忘的自顶向下法:此方法仍按自然的递归形式编写过程,但过程会保存每个子问题的解(通常保存在一个数组或散列表中)。当需要一个子问题的解时,过程首先检查是否已经保存过此解。如果是,则直接返回保存的值,从而节省了计算时间;否则,按通常方式计算这个子问题。

def MemoizedCutRod(p, n):
    r = [0] * (n+1)
    return MemoizedCutRodAux(p, n, r)


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

其运行时间为 \Theta (n^{2})

(2)自底向上法:这种方法一般需要恰当定义子问题“规模”的概念,使得任何子问题的求解都只依赖于“更小的”子问题的求解。因而我们可以将子问题按规模排序,按由小至大的顺序求解。当求解某个子问题时,它所依赖的那些更小的子问题都已求解完毕,结果已经保存。每个子问题只需求解一次,当我们求解它(也是第一次遇到它)时,它的所有前提子问题都已求解完成。

def BottomUpCutRod(p, n):
    r = [0] * (n+1)
    for j in range(1, n+1):
        q = 0
        for i in range(1, j+1):
            q = max(q, p[i] + r[j-i])
        r[j] = q
    return r[n]

其运行时间为\Theta (n^{2})

3.重构解

扩展动态规划算法,不仅仅求最大值 q,把取得最大值的位置i 也记录下来(记录在 s[j] 中),保存对应的切割方案,输出最优解。

def ExtendedBottomUpCutRod(p, n):
    r = [0] * (n+1)
    s = [0] * (n+1)
    for j in range(1, n+1):
        q = 0
        for i in range(1, j+1):
            if q < p[i] + r[j-i]:
                q = p[i] + r[j-i]
                s[j] = i
        r[j] = q
    sol = []
    while n > 0:
        sol.append(s[n])
        n -= s[n]
    return r, sol

主程序为:

from CutRod import CutRod
from MemoizedCutRod import MemoizedCutRod
from BottomUpCutRod import BottomUpCutRod
from ExtendedBottomUpCutRod import ExtendedBottomUpCutRod
import time

p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30]

before = time.time()
for i in range(1, 11):
    r = CutRod(p, i)
    # print("切割方案", i, "的最优收益值:", r)
after = time.time()
print("自顶向下递归实现运行时间:", round((after-before)*1000, 3), "ms")

before = time.time()
for i in range(1, 11):
    r = MemoizedCutRod(p, i)
    # print("切割方案", i, "的最优收益值:", r)
after = time.time()
print("带备忘的自顶向下运行时间:", round((after-before)*1000, 3), "ms")

before = time.time()
for i in range(1, 11):
    r = BottomUpCutRod(p, i)
    # print("切割方案", i, "的最优收益值:", r)
after = time.time()
print("自底向上法运行时间:", round((after-before)*1000, 3), "ms")


for i in range(1, 11):
    r, sol = ExtendedBottomUpCutRod(p, i)
    print("切割方案", i, "的最优收益值:", r[i], "切割长度:", sol)

输出结果为:

自顶向下递归实现运行时间: 2.04 ms
带备忘的自顶向下运行时间: 0.223 ms
自底向上法运行时间: 0.171 ms
切割方案 1 的最优收益值: 1 切割长度: [1]
切割方案 2 的最优收益值: 5 切割长度: [2]
切割方案 3 的最优收益值: 8 切割长度: [3]
切割方案 4 的最优收益值: 10 切割长度: [2, 2]
切割方案 5 的最优收益值: 13 切割长度: [2, 3]
切割方案 6 的最优收益值: 17 切割长度: [6]
切割方案 7 的最优收益值: 18 切割长度: [1, 6]
切割方案 8 的最优收益值: 22 切割长度: [2, 6]
切割方案 9 的最优收益值: 25 切割长度: [3, 6]
切割方案 10 的最优收益值: 30 切割长度: [10]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值