1. 0-1背包
1.1 相关链接
python解决0-1背包问题(超直观)
01背包问题
彻底理解0-1背包问题
1.2 问题描述
- 有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。
- 其中每种物品都只有一件。
1.3 解决实现
二维dp
时间复杂度:O(物品个数背包大小)
空间复杂度:O(物品个数背包大小)
令dp[i][j]来表示前i件物品装入容量为j的背包所能得到的最大总价值。
对于dp[i][j]来说,i指的是前i件物品,j指的是还剩下多少背包空间。于是对于dp[i][j]来说,有公式:
import numpy as np
weight=[2,2,6,5,4]
value=[3,6,5,4,6]
weight_most=10
def bag_0_1(weight,value,weight_most):#return max value
num = len(weight)
weight.insert(0,0)#前0件要用
value.insert(0,0)#前0件要用
bag=np.zeros((num+1,weight_most+1),dtype=np.int32)#下标从零开始
for i in range(1,num+1):
for j in range(1,weight_most+1):
if weight[i]<=j:
bag[i][j]=max(bag[i-1][j-weight[i]]+value[i],bag[i-1][j])
else:
bag[i][j]=bag[i-1][j]
# print(bag)
return bag[-1,-1]
result=bag_0_1(weight,value,weight_most)
print(result)
空间复杂度的极致优化
时间复杂度:O(物品个数*背包大小)
空间复杂度:O(背包大小)
java代码:
public class KnapSack01 {
public static int knapSack(int[] w, int[] v, int C) {
int size = w.length;
if (size == 0) {
return 0;
}
int[] dp = new int[C + 1];
//初始化第一行
//仅考虑容量为C的背包放第0个物品的情况
for (int i = 0; i <= C; i++) {
dp[i] = w[0] <= i ? v[0] : 0;
}
//考虑容量为C的背包放第i~size-1个物品的情况,假设前i个已经计算过
for (int i = 1; i < size; i++) {
for (int j = C; j >= w[i]; j--) {
//容量为j的时候价值为dp[j](不放w[i]和放w[i]的最大值)
dp[j] = Math.max(dp[j], v[i] + dp[j - w[i]]);
}
}
return dp[C];
}
public static void main(String[] args) {
int[] w = {2, 1, 3, 2};
int[] v = {12, 10, 20, 15};
System.out.println(knapSack(w, v, 5));
}
}
1.4 变种问题
三维0-1背包
力扣474. 一和零
方法1:
class Solution:
def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
L=len(strs)
dp=[[[0]*(n+1) for _ in range(m+1)] for _ in range(L+1)]
for i in range(1,L+1):
count1,count2=strs[i-1].count("0"),strs[i-1].count("1")
for j in range(m+1):
for k in range(n+1):
dp[i][j][k]=dp[i-1][j][k]
if count1<=j and count2<=k:
dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-count1][k-count2]+1)
return dp[-1][-1][-1]
https://leetcode-cn.com/problems/ones-and-zeroes/solution/yi-he-ling-by-leetcode-solution-u2z2/
方法2:优化为二维数组
class Solution:
def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
L=len(strs)
dp=[[0]*(n+1) for _ in range(m+1)]
count1,count2=strs[0].count("0"),strs[0].count("1")
for j in range(m+1):
for k in range(n+1):
if count1<=j and count2<=k:
dp[j][k]=1
for i in range(1,L):
count1,count2=strs[i].count("0"),strs[i].count("1")
for j in range(m,count1-1,-1):
for k in range(n,count2-1,-1):
dp[j][k]=max(dp[j][k],dp[j-count1][k-count2]+1)
return dp[m][n]
2. 完全背包
2.1 相关链接
动态规划:完全背包、多重背包
【Algorithm】完全背包 巧用公式变形法转化为01背包问题
leetcode 完全背包汇总贴 (更新ing)
2.2 问题描述
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
2.3 解决实现
1)初始的方式-三重循环:
根据第i种物品放多少件进行决策,所以状态转移方程为
其中F[i-1][j-KC[i]]+KW[i]表示前i-1种物品中选取若干件物品放入剩余空间为j-K*C[i]的背包中所能得到的最大价值加上k件第i种物品;
设物品种数为N,背包容量为V,第i种物品体积为C[i],第i种物品价值为W[i]。
与01背包相同,完全背包也需要求出NV个状态F[i][j]。但是完全背包求F[i][j]时需要对k分别取0,…,j/C[i]求最大F[i][j]值。
这样代码应该是三层循环(物品数量,物品种类,背包大小这三个循环),伪代码如下:
F[0][] ← {0}
F[][0] ← {0}
for i←1 to N
do for j←1 to V
do for k←0 to j/C[i]
if(j >= k*C[i])
then F[i][k] ← max(F[i][k],F[i-1][j-k*C[i]]+k*W[i])
return F[N][V]
很明显,这样一般情况下会超时,需要转化成时间复杂度比较低的在进行求解
经过思考可以想到完全背包可以转化为01背包:
2)转为01背包-二重循环:
因为同种物品可以多次选取,那么第i种物品最多可以选取V/C[i]件价值不变的物品,然后就转化为01背包问题。如果把第i种物品拆成体积为C[i]×2k价值W[i]×2k的物品,其中满足C[i]×2k≤V。即设F[i][j]表示出在前i种物品中选取若干件物品放入容量为j的背包所得的最大价值。那么对于第i种物品的出现,我们对第i种物品放不放入背包进行决策。如果不放那么F[i][j]=F[i-1][j];如果确定放,背包中应该出现至少一件第i种物品,所以F[i][j]种至少应该出现一件第i种物品,即F[i][j]=F[i][j-C[i]]+W[i]。为什么会是F[i][j-C[i]]+W[i]?因为F[i][j-C[i]]里面可能有第i种物品,也可能没有第i种物品。我们要确保F[i][j]至少有一件第i件物品,所以要预留C[i]的空间来存放一件第i种物品。
状态方程为:
0-1背包和完全背包的不同:
从二维数组上区别0-1背包和完全背包也就是状态转移方程就差别在放第i中物品时,完全背包在选择放这个物品时,最优解是F[i][j-c[i]]+w[i]即画表格中同行的那一个,而0-1背包比较的是F[i-1][j-c[i]]+w[i],上一行的那一个。
从一维数组上区别0-1背包和完全背包差别就在循环顺序上,0-1背包必须逆序,因为这样保证了不会重复选择已经选择的物品,而完全背包是顺序,顺序会覆盖以前的状态,所以存在选择多次的情况,也符合完全背包的题意。状态转移方程都为F[i] = max(F[i],dp[F-c[i]]+v[i])。
3)用一位数组进行空间优化:
#include<cstdio>
#include<algorithm>
using namespace std;
int w[300],c[300],f[300010];
int V,n;
int main()
{
scanf("%d%d",&V,&n);
for(int i=1; i<=n; i++)
{
scanf("%d%d",&w[i],&c[i]);
}
for(int i=1; i<=n; i++)
for(int j=w[i]; j<=V; j++)//注意此处,与0-1背包不同,这里为顺序,0-1背包为逆序
f[j]=max(f[j],f[j-w[i]]+c[i]);
printf("max=%d\n",f[V]);
return 0;
}