问题:某公司购买了一根长钢条(或木条),将其切割为短钢条出售。不同长度的短钢条市场售价不同,切割过程没有成本支出,公司管理层想知道最佳的切割方案。
思路:对于任意长度为L的钢条首次切割后成为两部分:第一部分长度为X,第二部分长度为L-X。由于是第一次切割,所以第一部分的价格就是长度L钢条的市场售价,设为p[X];第二部分可能需要再进行分割,设其最大收益为bestSplit(L-X)。那么L长度的钢条的总收益就为这两部分的和:p[X] + bestSplit(L-X)。如果我们穷尽长度L钢条的所有首次切割位置,就可以记录下最大的收益和最大收益时首次切割的位置,最大收益设为bestSplit(L)。我们发现计算任意长度钢条的最大收益的求解形式是一样的,即是:首次切割后“第一块的市场售价”与“剩余部分最大收益”的加和。其中,“第一块的市场售价”可以直接得到,“剩余部分最大收益”依然可以在其上再切一刀变为“第一块的市场售价”与”剩余部分最大收益“的加和。
代码:选用递归方式,详见注释:
import time
# 不同长度的钢条的市场售价,index表示钢条的长度
p=[1,5,8,9,10,17,17,20,24,30]
# 存储最优的第一刀位置,index表示钢条的长度
bestSplit=[]
# the length of the steel to split
l=0
# 存储某个长度钢条的最大收益
cheatSheet = []
def bestPrice(l):
global p, cheatSheet, bestSplit
if l == 0: return 0
if l == 1: return p[0]
# 如果该长度的钢条最大收益之前已经被算出,我们直接使用
if cheatSheet[l-1] != -1: return cheatSheet[l-1]
# 临时存储该长度钢条的最大收益,这里初始化为0
price_max = 0
# 遍历每个“第一刀”的位置
for i in range(l):
# 第一部分的长度不能大于可以直接售卖的最大长度,即,len(p)
if i+1 > len(p):
break
"""
第一刀将钢条切为两部分:part one长度为i+1,是一个整钢条,价格可以直接从p[i]得出;
part two可以再被切割多次,长度为l-i-1,价格需要继续计算,这里用递归调用
"""
price = p[i] + bestPrice(l-i-1)
# 通过擂台比较的方式找到最大的价格,将最大的价格和最优的第一刀位置保存下来
if price > price_max:
price_max = price
bestPoint = i+1
# 保存长度l下最优第一刀的位置
bestSplit[l-1] = bestPoint
# 保存长度l下最优的收益
cheatSheet[l-1] = price_max
return price_max
# 该数组用于保存每一刀切的位置
splits=[]
"""
根据已经计算好的bestSplit数组输出第一块到最后一块的长度
"""
def split(l):
global splits
if l < 1: return
if l == 1:
splits.append(1)
return
partOne = bestSplit[l-1]
partTwo = l - partOne
splits.append(partOne)
split(partTwo)
def main():
import sys
# 全局变量,定义的注释在代码最前面
global l, bestSplit, cheatSheet
# 与算法无关,实例该程序的使用方法
if len(sys.argv) < 2:
print("Error!")
print("Right usage:\npython3 steelBar.py length")
return
l = int(sys.argv[1])
"""
初始化这两个数组的内容为特殊值-1;数组大小为初始钢条的长度。
"""
for i in range(l):
cheatSheet.append(-1)
bestSplit.append(-1)
t1 = time.time()
# bestPrice为递归函数,注释在前
print("The best price is ", bestPrice(l))
t2 = time.time()
print("Time spent: ",t2-t1)
split(l)
print("Split the steel into ", splits)
main()
若代码保存为:steelBar.py
调用方法为:
python3 steelBar.py length