一、递归求解
1.1 物品不放回
class Solution(object):
def snap(self,var_w1,w2,price_):
price=0
n=len(var_w1)
if w2<=0:
return 0
for i in range (n):
if var_w1[i]<=w2:
price=max(price_[i]+self.snap(var_w1[i+1:],w2-var_w1[i],price_[i+1:]),price) #好像是没有加上去
# print("i={},price={}".format(i,price))
return price
import time
st=time.time()
w1_=[5,4,6,3];price_=[10,40,30,50]
# w1_=[1,2];price_=[2,1]
sol=Solution()
w2=10
print("递归的结果为:",sol.snap(w1_,w2,price_))
se=time.time()
print("递归的时间消耗:",se-st)
递归的结果为: 90
递归的时间消耗: 0.0
1.2 物品放回(完全背包)
class Solution(object):
def __init__(self,w1,price,w2):
self.w1=w1
self.price=price
self.n=len(w1)
def snap(self,w2):
price=0
if w2<=0:
return 0
for i in range (self.n):
if self.w1[i]<=w2:
price=max(self.price[i]+self.snap(w2-self.w1[i]),price) #好像是没有加上去
return price
import time
st=time.time()
w1_=[5,4,6,3];price_=[10,40,30,50];w2=10
sol=Solution(w1_,price_,w2)
print("记忆化搜索结果为:",sol.snap(w2))
se=time.time()
print("记忆化所花费的时间为:",se-st)
记忆化搜索结果为: 150
记忆化所花费的时间为: 0.0
如何在递归的方法中打印出拿出了哪几样东西?
递归的思想以及DP的思想可以给自己的算法提升一个档次,所以我不要急,这是一个过程
我认为这种方式是打印不出来的,之前的整数拆分最大乘、斐波那契、爬楼梯
二、记忆化搜索
2.1 物品不放回
class Solution(object):
def __init__(self,w1,w2):
self.memo=((min(w1)* w2)+1) *[-1]
#因为memo的长度最长为:w1中存在的最小的重量* 背包的负重,考虑到 snap()方法中的self.memo[w2]为下标索引,故加一个1
def snap(self,var_w1,w2,price_):
price=0
n=len(var_w1)
if w2<=0:
return 0
if self.memo[w2]!=-1:
return self.memo[w2]
for i in range (n):
if var_w1[i]<=w2:
price=max(price_[i]+self.snap(var_w1[i+1:],w2-var_w1[i],price_[i+1:]),price) #好像是没有加上去
print("i={},price={}".format(i,price))
self.memo[w2]=price
return price
import time
st=time.time()
w1_=[1,2];price_=[2,1];w2=3
sol=Solution(w1_,w2)
print("记忆化搜索结果为:",sol.snap(w1_,w2,price_))
se=time.time()
print("记忆化所花费的时间为:",se-st)
i=0,price=1
i=0,price=3
i=1,price=3
记忆化搜索结果为: 3
记忆化所花费的时间为: 0.0
递归总结
今天又重新整理下思路,发现在0-1背包问题中,递归的用处跟打家劫舍、爬楼梯这种问题的使用思路不一样,我一开始做动态规划的时候习惯当看到子树处理的数跟其他子树的某一块一样,就想到了用递归,虽然是这样的,但是其他形式的也可以,譬如在0-1背包问题中,用递归的思想去解题,递归是一种解决问题的手段,不一定要求处理的数据是一样的,但是有一点:就是处理的手法是一样的,如在归并排序中:虽然每个数都不一样,但是先用递归的手段让left 和right都只拿到了一个数,之后再比较,因为分成两部分可以连续的拆,这里的0-1背包也一样,只要我剩余的背包容量不为0,我就可以按照递归的方法对每拿一次进行递归。
但是我们看0-1背包为什么有重复的子结构: 确实是有的,如下图绿色的部分,虽然当前的背包剩余容量不一样,但是又有什么关系?如果一样的话那不就是完全一样的重复路径了吗?所以就是这样的。
rob中的代码也是传的nums[a :]
def rob1(nums):
res=0
for i in range (len(nums)):
res=max(res,nums[i]+rob1(nums[i+2:]))
return res
2.2 物品放回(完全背包)
class Solution(object):
def __init__(self,w1,price,w2):
self.w1=w1
self.price=price
self.n=len(w1)
self.memo=((min(w1)* w2)+1) *[-1]
#因为memo的长度最长为:w1中存在的最小的重量* 背包的负重,考虑到 snap()方法中的self.memo[w2]为下标索引,故加一个1
def snap(self,w2):
if self.memo[w2]!=-1:
return self.memo[w2]
price=0
if w2<=0:
return 0
for i in range (self.n):
if self.w1[i]<=w2:
price=max(self.price[i]+self.snap(w2-self.w1[i]),price) #好像是没有加上去
self.memo[w2]=price
return price
import time
st=time.time()
w1_=[5,4,6,3];price_=[10,40,30,50];w2=10
sol=Solution(w1_,price_,w2)
print("记忆化搜索结果为:",sol.snap(w2))
se=time.time()
print("记忆化所花费的时间为:",se-st)
记忆化搜索结果为: 150
记忆化所花费的时间为: 0.0010187625885009766
三、动态规划
class Solution(object):
def snap(self,w1,p,w):
'''Dp Cal'''
dp=[]#创建一个二维的表
for i in range (len(w1)+1):
dp.append([0]*(w+1))
for i in range (len(w1)):#纵向(1,3)
for j in range (w+1):#横向
if w1[i]<=j:
dp[i+1][j]=max(p[i]+dp[i][j-w1[i]],dp[i][j])
else:
dp[i+1][j]=dp[i][j]
j+=1
return dp
w1=[5,4,6,3]
p=[10,40,30,50]
w=10
sol=Solution()
sol.snap(w1,p,w)
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10],
[0, 0, 0, 0, 40, 40, 40, 40, 40, 50, 50],
[0, 0, 0, 0, 40, 40, 40, 40, 40, 50, 70],
[0, 0, 0, 50, 50, 50, 50, 90, 90, 90, 90]]
#Test :初始化一个二维的dp数组,存储着
w1=[3,2,1];w=5;dp=[]
for i in range (len(w1)+1):
dp.append([0]*w )
print(dp)
[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
记录拿取的物品
class Solution(object):
def snap(self,w1,p,w):
'''Dp Cal'''
dp=[]#创建一个二维的表
keep=[]
item=[]
keep_w=w
for i in range (len(w1)+1):
dp.append([0]*(w+1))
keep.append([0]*(w+1))
for i in range (len(w1)):#纵向(1,3)
for j in range (w+1):#横向
if w1[i]<=j and p[i]+dp[i][j-w1[i]]>dp[i][j]:
#这里填大于或者大于等于都可以,因为肯定大于,最根本的原因是因为每件商品的价值不一样,如果都一样的话,以后再证明
dp[i+1][j]=p[i]+dp[i][j-w1[i]]
keep[i+1][j]=1
else:
dp[i+1][j]=dp[i][j]
keep[i+1][j]=0
j+=1
for i in range (len(w1),0,-1):
if keep [i][keep_w]==1:
item.append(i)
keep_w-=w1[i-1]
else:
pass
return dp[len(w1)][w],keep,item
w1=[5,4,6,3]
p=[10,40,30,50]
w=10
sol=Solution()
dp_max,keep,item=sol.snap(w1,p,w)
print("The Max Price is: {}\nThe item is: {}".format(dp_max,item))
The Max Price is: 90
The item is: [4, 2]
这里总结下背包问题:
首先0-1背包问题是比较特殊的DP问题,要想用DP,需要证明两点:
1.证明不论物品的顺序如何,总有一条唯一的解。
2.DP中的最优解是当前的最优解,而不是全局的最优解,只有在最后的一行中分别代表着w=0…N的时候的全局最优解。
下面开始证明第一条:
1.不论物品的顺序如何,总有一条唯一的解
如上图所示,在递归树中只有一个唯一的线路,因为是拿取不放回,所以线路只有一条,当我们改变物品的顺序后,发现1+3和3+1是一样的,也就是说,正因为是拿取不放回,所以赋予了递归树的特性:最优路线只有一条。
如上图4所示,当我们加入一个id:4的物品,w=10的时候,如橙色的线路,也是只有一条解,也就是说存在两个物品重量一样价值一样的话,当w=7的时候,会有两条路线,分别是:1-3 / 1-4 ,我们这里现在不讨论最优解的个数与W总负重的关系了,我们现在讨论第二个问题,这个0-1背包问题为什么能够用到动态规划,我们好像从上面的图中没有看到重复解?
别急,我们将总负载量提高为W=20,这时候上面的递归树肯定会多几层,我们就分析下面的三层递归树:
我们从上图5可以看到当负载量W=20的时候,并且层数大于等于3的时候,我们能够得到重复子结构,这个时候我们想到的就是用动态规划了。
下面正式进入第二个问题的解释:
DP中的最优解是当前的最优解,而不是全局的最优解,只有在最后的一行中分别代表着w=0…N的时候的全局最优解。
上图:
当我们看到i=1行的时候,我们拿w=5来说,可以看到每迭代一轮,w=5的解并不是最优解,知道迭代完最后一个id的时候才能确定最优解的最终情况,值得一提的是最优解可能早就有了,但是最后一行的最优解是最具有表征力的。
还有就是牢记一点:画递归树的时候,要想W=无穷大的时候的情况再遍历,即W=N的时候,这样就不用考虑是否有尽头了。