分析找到递推式
存子问题
某公司出售钢条,出售价格与钢条长度之间对关系如下表:
问题:现在有一段长度为n的钢条和上面的价格表,求切割钢条方案,使得总收益最大。
长度为4的钢条的所有切割方案如下:(c方案最优)
思考:长度为n的钢条的不同切割方案有几种?
答:长度为n就有2^(n-1)次切割方法
给出题目的最优表
现在就是需要从小到大的最优解算出来即可,即当要计算长度为4的最优解,可以观察前面的(1+3)的最优解是多少,(2+2)的最优解是多少,以及本身不切割的最优解是多少即可。
【递推式】
设长度为n的钢条切割后最优收益值为rn,可以得出递推式:
1.第一个参数Pn表示n自身,不切割
2.其他n-1个参数分别表示另外n-1种不同切割方案,对方案i取1,2……,n-1
将钢条切割为长度为i和n-i两端
方案i的收益为切割两段的最优收益之和
3.考察所有的i,选择其中收益最大的方案
【最优子结构】
1.可以将求解规模为n的原问题,划分为规模更小的子问题:完成一次切割后,可以将产生的两段钢条看成两个独立的钢条切割问题。
2.组合两个子问题的最优解,并在所有可能的两段切割中选取组合收益最大的,构成原问题的最优解。
3.钢条切割满足最优子结构:问题的最优解由相关子问题的最优解组合而成,这些子问题可以独立求解。
eg:假设长度为9的钢条长度
如果3,6是最优子结构,则长度为9的最优解是长度为3的子结构和长度为6的子结构相加。
【优化递推式】
钢条切割问题还存在更简单的递归求解方法
1.从钢条的左边切割下长度为i的一段,只对右边剩下的一段继续进行切割,左边的不再切割
2.递推式化简为:*只是在原来递推式的基础上将右边换成p[i].
3.不做切割的方案就可以描述为:左边一段长度为n,收益为p[n],剩下一段长度为0,收益为r[0]=0.
eg:假设长度为9的钢条长度(假设切成2 3 2 2最优)
在原来的递推式里,可以看成5和4切割(5切成2和3,4切成2和2)所以5+4最优
在优化后的式子里,可以切成2和7 (2不切,表示左边的一段,只切右边的7,切成2 2 3)也是最优
#钢条长度的价格,下标表示多少长
p=[0,1,5,8,9,10,17,17,20,24,30]
#方法一:双递归
def cut_rod_recurision(p,n):
if n==0:
return 0
else:
res=p[n] #表示不切割的价值
#遍历从1到n-1;(p[1]+p[n-1])...(p[n-1]+p[1])
for i in range(1,n):
res=max(res,cut_rod_recurision(p,i)+cut_rod_recurision(p,n-i))
return res
#方法二:优化,单递归
def cut_rod_recurision_1(p,n):
if n==0:
return 0
else:
res=p[n]
for i in range(1,n):
#切成两部分,左边不动,右边切()
'''
设9的最优解是 2 3 2 2
上面那种方法可以对 4,5分别切割
这种方法则可以看成2 7,对7切割成2 2 3
'''
res=max(res,p[i]+cut_rod_recurision_1(p,n-i))
return res
#方法三:动态规划(存放)
def cut_rod_recurision_dp(p,n):
li=[0]#创建一个用于存放最优解的列表
#i表示1~n的最优解
for i in range(1,n+1):
res=p[i]
#用于计算每位i的最优解
for j in range(1,i+1):
#优化递推式
res=max(res,p[j]+li[i-j])
#将最大的i存入li中
li.append(res)
return li[n]
print(cut_rod_recurision(p,9)) #25
print(cut_rod_recurision_1(p,9)) #25
print(cut_rod_recurision_dp(p,9)) #25
题目修改为不仅要输出最优解,还要输出最优切割方案
【解题思路】
在方法三的基础上增加一个s列表,用于存放1~n的最大价格的左边不切割长度
遍历循环,将输入的n减去s列表里的最优左长度,当n为0,程序结束。
def cut_rod_extend(p,n):
r=[0] #用于存放最优结果 最大价格
s=[0] #用于存放价格最大值对应方案的左边不切割部分的长度
for i in range(1,n+1):
res_r=0 #最优解
res_s=0 #左半段不切割部分的长度
for j in range(1,i+1):
if p[j]+r[i-j]>res_r:
res_r=p[j]+r[i-j]
res_s=j
r.append(res_r)
s.append(res_s)
return r[n],s
def cur_rod_solution(p,n):
r,s=cut_rod_extend(p,n)
res=[] #用于存放最优方案
while n>0: #为0返回
res.append(s[n]) #从s列表中找最优左长度
n-=s[n] #输入的数减去最优左长度
return r,res
print(cur_rod_solution(p,9)) #(25, [3, 6])
5、动态规划问题关键特征
1.什么问题可以使用动态规划方法?
和最优值/最优化的问题相关(什么什么最大,什么最小)
最优子结构
原问题的最优解中涉及到多少个子问题
在确定最优解使用哪些子问题时,需要考虑多少种选择
重叠子问题