动态规划 背包问题1 01背包 完全背包

背包问题作为基本的动态规划问题很有必要掌握,最近看了《背包九讲》,自己总结了下:

01背包

  问题: 总体积为V的背包,存在n个物品,第 i i i个物品的体积为 v i v_i vi,价值为 w i w_i wi,求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值。
 动态规划总体来说就是对一个元素选和不选的问题,而在背包问题中,对于每种背包问题,都存在一个状态转移方程
   d p [ i ] [ j ] = dp[i][j]= dp[i][j]=max ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v i ] + w i ) (dp[i-1][j],dp[i-1][j-v_i]+w_i) (dp[i1][j],dp[i1][jvi]+wi
 这里 d p [ i ] [ j ] dp[i][j] dp[i][j]就代表的是前i个物品放入j空间下的最大价值,因为 i i i总是从 i − 1 i-1 i1转移过来的,所以当遍历到 第i个物品时,我们可以选第 i i i个物品,则得到 d p [ i − 1 ] [ j − v i ] + w i dp[i-1][j-v_i]+w_i dp[i1][jvi]+wi,不选则是 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j] ,求这两个等式的最大值就是我们要求的 d p [ i ] [ j ] dp[i][j] dp[i][j]
  所以, d p [ i ] [ j ] dp[i][j] dp[i][j]要么等于 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j],要么等于 d p [ i − 1 ] [ j − v i ] + w i dp[i-1][j-v_i]+w_i dp[i1][jvi]+wi,对于动态规划的初学者,最好把表格画上,这里我们只给出v[1]那行的转移路线,因为v[1]=4,所以当 j<4时,直接 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j],当j>4时, d p [ i ] [ j ] = dp[i][j]= dp[i][j]=max ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v i ] + w i ) = d p [ i − 1 ] [ j − v i ] + w i (dp[i-1][j],dp[i-1][j-v_i]+w_i)=dp[i-1][j-v_i]+w_i (dp[i1][j],dp[i1][jvi]+wi=dp[i1][jvi]+wi,v[2]所在行同理。
在这里插入图片描述
我们先假设 V = 7 , n = 3 , v = [ 2 , 4 , 3 ] , w = [ 5 , 6 , 7 ] V=7,n=3,v=[2,4,3],w=[5,6,7] V=7,n=3,v=[2,4,3],w=[5,6,7]得到

const int n=1010;
int dp[n][n];//这里我们是故意开大,也可以定义int dp[v.size()][V]
int zero_one_pack(vector<int>&v, vector<int>&w, int V)
{
 dp[0][0] = 0;//一般动态规划都需要初始化,这里我们dp是全局的,默认元素为0
 for (int i = 1; i <=v.size(); i++)
 {
  for (int j = 0; j <= V; j++)
  {
   dp[i][j] = max(dp[i-1][j], dp[i - 1][j - v[i]] + w[i]);
  }
 }
 return dp[v.size()][V];
}

时间复杂度 O ( n V ) O(nV) O(nV),空间复杂度 O ( n V ) O(nV) O(nV)
 我们可以对上式进行的空间复杂度进行优化,优化为 O ( V ) O(V) O(V),这里利用了滚动数组的原理,从图我们可以得到,第i行只与第i-1行有关,则可以将其优化为 O ( 2 V ) O(2V) O(2V),继续观察得到,当 v [ i ] > 0 v[i]>0 v[i]>0时, d p [ i ] [ j ] dp[i][j] dp[i][j]只与 d p [ i − 1 ] [ j − v [ i ] ] dp[i-1][j-v[i]] dp[i1][jv[i]] d p [ i − 1 ] [ j ] ] dp[i-1][j]] dp[i1][j]]有关,即只与 d p [ i − 1 ] dp[i-1] dp[i1] j j j个元素有关,则我们怎么能只利用一维数组实现呢?为什么不能正向遍历只能反向遍历?
在这里插入图片描述
我们发现,第j列的元素只与第j-v[i]列的元素有关,当正向遍历到 d p [ j ] dp[j] dp[j]时,只要 j − v [ i ] > = 0 j-v[i]>=0 jv[i]>=0, d p [ j − v [ i ] ] dp[j-v[i]] dp[jv[i]]一定发生了改变。所以考虑反向遍历,我们发现正好 d p [ j ] dp[j] dp[j]可以利用没有改变的 d p [ j − v [ i ] ] dp[j-v[i]] dp[jv[i]],优化代码如下

const int n=1010;
int dp[n];
int Op_zero_one_pack(vector<int>&v, vector<int>&w, int V)
{
 dp[0] = 0;
 for (int i = 0; i < v.size(); i++)
 {
  for (int j = V; j >= v[i]; j--)
  {
   dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
  }
 }
 return dp[V];
}

空间复杂度 O ( V ) O(V) O(V)
 这里还有一个问题,当题目要求必须把背包装满时,我们就加一个小改动,初始化时将dp数组所有元素初始化为 − 1 -1 1, d p [ 0 ] = 0 dp[0]=0 dp[0]=0。再加上一个判断 i f ( d p [ j − v [ i ] ] ! = − 1 ) if(dp[j-v[i]]!=-1) if(dp[jv[i]]=1),即 j − v [ i ] j-v[i] jv[i]的空间放满是才会传递到 d p [ j ] dp[j] dp[j]。代码如下:

const int n=1010;
int dp[n];
int Full_zero_one_pack(vector<int>&v, vector<int>&w, int V)
{
memset(dp,-1,sizeof(dp));//初始化为-1
 dp[0] = 0;
 for (int i = 0; i < v.size(); i++)
 {
  for (int j = V; j >= V[i]; j--)
  {
  if(dp[j-v[i]]!=-1)
   dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
  }
 }
 return dp[V];
}

空间复杂度 O ( V ) O(V) O(V)

2 完全背包

 完全背包:题目要求基本上和01背包一样,唯一的区别在于完全背包中规定每种物品的数目为无限。同时,有优化和不优化两种版本。
当不优化时,可以将其视为01背包,仅仅是比01背包多了一层循环。
  01背包是求 d p [ j ] = m a x ( d p [ j ] , d p [ j − v [ i ] ] + w [ i ] ) dp[j] = max(dp[j], dp[j - v[i]] + w[i]) dp[j]=max(dp[j],dp[jv[i]]+w[i]),当是完全背包,求 d p [ j ] = m a x ( d p [ j ] , d p [ j − v [ i ] ] + w [ i ] , d p [ j − 2 v [ i ] ] + 2 w [ i ] ) , d p [ j − 3 v [ i ] ] + 3 w [ i ] . . . . . d p [ j − k v [ i ] ] + k w [ i ] dp[j] = max(dp[j], dp[j - v[i]] + w[i],dp[j - 2v[i]] + 2w[i]),dp[j - 3v[i]] + 3w[i] ..... dp[j - kv[i]] + kw[i] dp[j]=max(dp[j],dp[jv[i]]+w[i]dp[j2v[i]]+2w[i])dp[j3v[i]]+3w[i].....dp[jkv[i]]+kw[i], k < = V / c i k<=V/c_i k<=V/ci,这里给出二维版本,一维版本类似:

const int n=1010;
int dp[n][n];//这里我们是故意开大,也可以定义int dp[v.size()][V]
int Complete_pack(vector<int>&v, vector<int>&w, int V)
{
 dp[0][0] = 0;//一般动态规划都需要初始化,这里我们dp是全局的,默认元素为0
 for (int i = 1; i <= v.size(); i++)
 {
  for (int j = 1; j <= V; j++)
  {
    for(int k=1;k<=j/v[i];k++)
   if(j>=v[i-1])
   dp[i][j] = max(dp[i - 1][j], dp[i - 1][j -k* v[i-1]] + k*w[i-1]);
  }
 }
 return dp[v.size()][V];
}

时间复杂度 O ( n V k ) O(nVk) O(nVk),空间复杂度为 O ( n V ) O(nV) O(nV).。
 优化版本和01背包的优化版本类似,就是01背包极力要避免的事情( d p [ j ] dp[j] dp[j]使用了已经改变的 d p [ j − v [ i ] ] dp[j-v[i]] dp[jv[i]]),正是完全背包所想要的。大家可以画个图就知道了。从小到大遍历

const int n=1010;
int dp[n];//这里我们是故意开大,也可以定义int dp[v.size()][V]
int OP_Complete_pack(vector<int>&v, vector<int>&w, int V)
{
 dp[0] = 0;//一般动态规划都需要初始化,这里我们dp是全局的,默认元素为0
 for (int i = 0; i < v.size(); i++)
 {
  for (int j = v[i]; j <= V; j++)
  {
   dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
  }
 }
 return dp[V];
}

时间复杂度 O ( n V ) O(nV) O(nV),空间复杂度为 O ( V ) O(V) O(V).。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值