有N件物品和一个容量为V的背包。第i件物品的费用是v[i],价值是w[i]。求解将哪些物品装入背包可以使价值总和最大。数组
要么拿,要么不拿优化
关键就在于找到它的最优子问题,物品为N个,体积为V,咱们须要取二维状态的dpspa
将前i个物品放入体积为j的背包中能够得到的最大价值
->dp[i][j]
code只有单件物品就只须要考虑放或者不放,若是放入,体积就须要减去v[i],价值就加上w[i]cdn
好比咱们想要知道前5个物品放入体积为9的背包中最大价值是多少,物品id为0-4blog
也就是求
dp[4][9],
须要知道dp[3][j]
,0
it< code="">,而后再对物品4进行选取,根据01背包状态转移方程计算dp[4][9]最大价值; <> 要知道
dp[3][j]
,咱们就要知道dp[2][j]
,再对物品3进行选取,根据01背包状态转移方程计算dp[3][j]
最大价值;io要知道
dp[2][j]
,咱们就要知道dp[1][j]
,再对物品3进行选取,根据01背包状态转移方程计算dp[2][j]
最大价值;class要知道
dp[1][j]
,咱们就要知道dp[0][j]
,再对物品3进行选取,根据01背包状态转移方程计算dp[0][j]
最大价值;循环把
dp[i][0]
初始化为0(0<=i< p="">
public int napzack01_first(int []w,int []v,int V,int N){
int [][]dp = new int[N][V+1];
for(int i=0;i<N;i++)
for(int j=1;j<=V;j++)
if(j>=v[i])
if(i==0) dp[0][j] = w[i];
else dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
else
dp[i][j] = dp[i-1][j];
return dp[N-1][V];
}复制代码
第一次优化:二维数组优化为一维数组
咱们能够从表格发现,当前行的数据只与上一行的数据有关,因此咱们只要每次循环后确保数组保存了上一次计算的结果
问题以及解决:
咱们从当前循环来看,计算体积为
5
的dp值最多只须要0-4
的上一次计算的结果,若是顺序从小到大更新的话,咱们计算5
时,此时以前的0-4
的值都被更新了,不是上一次计算的结果(而是当前计算的结果),然后面的须要上一次前面的数据;因此咱们不能从头开始,而应该从最后开始更新,从后往前,这样能够保证优先更新体积大的值而让体积小的值保留上一次就算的结果。
//1维01背包
public int napzack01_second(int []w,int []v,int V,int N){
int []dp = new int[V+1];
for(int i=0;i<N;i++)
for(int j=V;j>=0;j--)
if(j>=v[i])
if(i==0) dp[j] = w[i];
else dp[j] = Math.max(dp[j],dp[j-v[i]]+w[i]);
return dp[V];
}复制代码
所有放满01背包:
咱们以前讨论了一种是不要求所有放满的01背包,咱们再看若是要求所有放满会有什么不一样
若是咱们把物品表换成这样:
若是咱们想要所有放满,就拿不到物品4,即便它价值50,可是咱们想要的是要把背包装满,这就涉及到初始化时的问题
咱们须要为每一次循环放入物品到j的背包中设置一个可否放满的标志,每次判断这个标志来检查当前物品放入可否使得背包装满
咱们只须要在初始化时把一维数组的1-V
初始化为-1
,表示当前0个物品放入背包,不能装满这些体积的背包,把数组dp[0]
正常设置为0,表示体积为0的背包能够被0个物品装满;而后咱们在计算数组值时先判断dp[j-v[i]]
是否为-1,为-1说明当前物品放入没法装满背包,则不进行修改数组的值;只有不为-1才能继续放入,说明当前物品放入能够放满体积为j
的背包
//所有放满01背包
public int napzack01_fourth(int []w,int []v,int V,int N){
int []dp = new int[V+1];
for(int i=0;i<dp.length;i++){
dp[i] = -1;
}
dp[0] = 0;
for(int i=0;i<N;i++)
for(int j=V;j>=0;j--)
if(j>=v[i]&&dp[j-v[i]]!=-1)
dp[j] = Math.max(dp[j],dp[j-v[i]]+w[i]);
return dp[V];
}复制代码
有N件物品(每一个物品都有无限个)和一个容量为V的背包。第i件物品的费用是v[i],价值是w[i]。求解将哪些物品(每一个物品均可以装多个)装入背包可以使价值总和最大。
咱们每种物品最多有选取V/v[i]
个,咱们能够再多一次循环,计算选取0-V/v[i]
个的最大价值
时间复杂度:每种物品有V/v[i]个,共须要求解N*V中状态,时间为O(NV*Σ(V/v[i]))
空间复杂度:O(N)
状态转移方程:
dp[j] = max{dp[j-1],dp[j-k*v[i]]+k*w[i]}
code
//彻底背包
//状态转移方程 dp[j] = dp[j-k*v[i]]+k*w[i]
public int napzack_complete(int []w,int []v,int N,int V){
int dp[] = new int [V+1];
for(int i=0;i<N;i++)
for(int j=0;j<=V;j++)
for(int k=0;k*v[i]<=j;k++)
dp[j] = Math.max(dp[j-1],dp[j-k*v[i]]+k*w[i]);
return dp[V];
}复制代码
咱们记得在优化01背包时,咱们为了获取到上一次计算的值,咱们选择从后往前计算,可是彻底背包正好相反,这才是它此昂要的
,彻底背包由于须要累计多个同一物品的值,前一次计算多是1个、2个等等,下一次j变化了之后,计算的多是3个或者更多,因此咱们须要保存实时计算出来的多个同一物品的最大价值,咱们选取从前日后的顺序,这样每次前面计算的咱们均可以在j增大之后累加得到更多个同一物品的最大价值(根据状态转移方程可知,咱们计算一个位置的最大价值只须要当前位置的上一次计算的值和当前次循环内更前面的值)
例如:
在咱们计算第2个物品dp[5]的时候,物品2的体积为2,价值为5,咱们须要上一次计算也就是第一个物品的dp[5]的值,还须要dp[5-2]=dp[3]的值,dp[3]咱们在本次循环内计算dp[5]以前就已经算过了,dp[3]可能选了一个物品2,也可能没有选,咱们计算dp[5]就根据这个dp[3]的大小在进行选取,就能够进行屡次选取。
根本上就是把一类物品转化为多个一种物品
咱们不须要体积从0开始计算。而只须要在每一个物品的循环内从当前物品的最小个数1开始,也就是
v[i]
(不须要0个是由于初始化的时候已经把体积为0的dp值设置为0了)
参数依然使用
code
//把彻底背包优化为01背包
public int napzack_comlete01(int []w,int []v,int N,int V){
int dp[] = new int[V+1];
for(int i=0;i<N;i++)
for(int j=v[i];j<=V;j++)
dp[j] = Math.max(dp[j],dp[j-v[i]]+w[i]);
return dp[V];
}复制代码