给定一个数组,长度为偶数,将数组分成长度相等两部分,使两部分和最接近
先介绍01背包,然后解决长度可以不相等的情况,最后解决该问题
问题0: 01背包问题---k件商品,每件都有重量wi以及价格vi,给定一个袋子容量为W,求袋子中存放商品价值最大的取货方案
令f(k,w)为袋里可用容量为w时,取前k件商品的最大总价值
若已知f(k-1,w),求出f(k,w):第k件商品重量为wk,价格vk
若wk>w,则袋子容量不够,不能取第k件 f(k,w) = f(k-1, w)
若wk<w,则袋子容量足够,若取第k件,有总价值f(k-1,w-wk)+vk ,若不取则f(k-1, w) 有: f(k,w) = max{f(k-1,w-wk)+vk,f(k-1,w)}
递归方式解决:
def bag(k, w, v, W):
if k==-1:
return 0
if w[k]>W:
return bag(k-1, w, v, W)
else:
return max(bag(k-1,w,v,W), bag(k-1,w,v,W-w[k])+v[k])
临界条件k==-1时,不论背包可用容量如何,没有商品可以取,直接返回0;当k=0时第一件商品,若w[0]>W,则返回bag(-1,W)=0
若w[0]<w则返回max(bag(-1,W),bag(-1,W-w0)+v[0])
非递归方式:
动态规划方法,利用辅助数组dp[k+1][W+1],其中dp[0][:]表示没有商品时总价值 dp[:][0]表示背包容量为0总价值,对于每一种商品,对于每个容量,利用递推式来进行递推求解。
def bag2(k, w, v, W):
#k为商品总数
#dp = [[0]*(W+1)]*(k+1)
#dp = [([0]*(W+1)) for i in range(k+1)]
dp = [[0 for i in range(W+1)] for i in range(k+1)]
for i in range(W+1):
dp[0][i] = 0
for i in range(k+1):
dp[i][0] = 0
print (mat(dp))
for i in range(1,k+1):
for j in range(1,W+1):
if j<w[i-1]:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1])
print (i,j,w[i-1],dp[i][j],dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1])
print (mat(dp))
#print (dp)
return dp[k][W]
python中二维数组的声明方式:
dp = [[0]*(W+1)]*(k+1) 不要轻易使用list*n 后面均是第一个元素的浅拷贝,太坑,这种方式 是错误的。
dp = [([0]*(W+1)) for i in range(k+1)]
dp = [[0 for i in range(W+1)] for i in range(k+1)]
初始化数组,并将第一行以及第一列置为0:
从第二行(第一件商品)开始遍历,每次增大容量值,计算最大价值,当计算到dp[1][2]时,此时容量=2,而第一件商品重量w[0]=2,即可以放入背包中,此时dp[1][2]=3 同理对于第一件商品而言,之后继续增大容量有:
最终的数组:
问题1:将数组分成两部分(个数可以不相等),使其两部分和最接近
假定数组数字之和为sum, 可以转化为在一个数组中挑选一部分数使其最接近sum/2。
利用01背包来解决:
令dp[i][j]表示从前i个数中取任意个数,且这些数之和为j的取法是否存在
dp[:][0]第一列表示从前几个数中取数使得和为0,易知 dp[:][0]=True
dp[0][1:]第一行除去第一个 表示不取数,使得和从1到目标值的取法,易有 dp[0][1:] = False
def splitArray(num):
n = len(num)
sum = 0
for i in range(n):
sum = sum + num[i]
target = int(sum/2)
#dp[i][j]从前i个数中随意挑选一些数 其和是否可以等于j
dp = [[False for i in range(target+1)] for i in range(n+1)]
for i in range(n+1):
dp[i][0] = True
for i in range(1, target+1):
dp[0][i] = False
print (mat(dp))
for i in range(1, n+1):
for j in range(0, target+1):
if j>=num[i-1]:
dp[i][j] = dp[i-1][j-num[i-1]] or dp[i-1][j]
else:
dp[i][j] = dp[i-1][j]
print (mat(dp))
print (mat(dp))
for j in range(target, 0, -1):
if dp[n][j]:
print(sum - 2*j)
return sum - 2*j
return None
接下来对于每一行,依次测试目标值从1逐渐增大是否可以得到
最终结果是最后一行中,最接近目标值的数
输入数组 num=[2,3,4,5,9]
初始化:
计算第一个数可以得到的值: 第一个数为2 从前1个数中可以得到的和只有2,即第三列=true
最终结果:
解法2:
dp[k][s] 表示从前k个数中取任意个数,且这些数之和为s的取法是否存在
外阶段:在前k1个数中进行选择,k1=1,2...n。
内阶段:从这k1个数中任意选出k2个数,k2=1,2...k1。
状态:这k2个数的和为s,s=1,2...sum/2。
决策:决定这k2个数的和有两种决策,一个是这k2个数中包含第k1个数,另一个是不包含第k1个数。
range函数(a,b,index)从[a,b)以步长index递进 注意不包含b
def splitArray2(num):
n = len(num)
sum = 0
for i in range(n):
sum = sum + num[i]
target = int(sum/2)
#dp[i][j]从前i个数中随意挑选一些数 其和是否可以等于j
dp = [[False for i in range(target+1)] for i in range(n+1)]
for i in range(n+1):
dp[i][0] = True
for i in range(1, target+1):
dp[0][i] = False
print (mat(dp))
#从前i个数中取i个数,可以实现的和s有哪些
for i in range(1,n+1):
#print (i)
for j in range(i, 0, -1):
for s in range(1, target+1):
print (i,j,s)
if s>=num[i-1] and dp[j-1][s-num[i-1]]:
dp[j][s] = True
print (mat(dp))
#从前i个数中取任意个数,可以实现的和s有哪些
for i in range(2, n+1):
for s in range(1, target+1):
if dp[i-1][s]:
dp[i][s] = True
for s in range(target, 1, -1):
if dp[n][s]:
print(sum - 2*s)
return sum - 2*s
return None
基于动态规划数组,第一种解法,在初始化第一行以及第一列之后,然后逐行填充数组,最后返回结果;
解法二也是基于数组,初始化数组之后,首先遍历前i个数中计算取前i个可以的和有哪些,i表示前i个数,j表示遍历这i个数,观察目标值1--target计算哪些值可以计算得到
然后计算从前i个数中取任意个数,可以的和有哪些,对应于矩阵的列,若dp[i][j]==true则dp[i+1][j]也可以得到
初始化:
计算前k个数可以得到的和有哪些:
前k个数去任意个数:
问题2:给定一个数组,长度为偶数,将数组分成长度相等两部分,使两部分和最接近
选出的物体数必须为n/2
dp[k][s]表示从前k个数中取k个数,且k不超过n/2,且这些数之和为s的取法是否存在
def splitArray3(num):
n = len(num)
l = int(n/2)
sum = 0
for i in range(n):
sum = sum + num[i]
target = int(sum/2)
#dp[i][j]从前i个数中随意挑选一些数 其和是否可以等于j
dp = [[False for i in range(target+1)] for i in range(n+1)]
for i in range(n+1):
dp[i][0] = True
for i in range(1, target+1):
dp[0][i] = False
print (mat(dp))
#从前i个数中取i个数(i<n),可以实现的和s有哪些
for i in range(1,n+1):
#print (i)
for j in range(min(l,i), 0, -1):
for s in range(1, target+1):
print (i,j,s)
if s>=num[i-1] and dp[j-1][s-num[i-1]]:
dp[j][s] = True
print (mat(dp))
for s in range(target, 0, -1):
if dp[l][s]:
print(sum - 2*s)
return sum - 2*s
return None
注意最后寻找要从n/2行寻找,因为每部分的个数为n/2,即必须从数组中取出n/2个数来不断逼近sum/2
输入num=[2,3,0,5]
初始化
最终dp数组:
dp[k][s]代表从前k个数中取k个数,且k不超过n/2,且这些数之和为s的取法是否存在。