目录
背包问题
一、0-1背包问题(物品不能分割dp)
1.0-1背包(倒着写)
物品编号 | 1 | 2 | 3 | 4 |
质量W | 2 | 1 | 3 | 2 |
价值v | 12 | 10 | 20 | 15 |
背包容量c=5,物品个数n=4,物品价值v,p[i][j]:[0,i]的物品任取放入容量为j的背包。
不放物品i,背包容量为j,它能装的最大价值为:p[i-1][j];
放物品i,背包容量为j,它能装的最大价值为:背包容量-物品i的容量 所能放的最大价值+物品i的价值。p[i-1][j-w[i]]+v[i]
p[i][j]=max(p[i-1][j-w[i]]+v[i],p[i-1][j])
当前元素的值是由上方(p[i-1][j])和左上角元素(p[i-1][j-w[i]]+v[i])的值推出来的。
图示计算过程,在装物品4时,去除物品4的质量背包所剩容量为3。
背包容量为1时,最大价值为10,1号物品;
背包容量为2时,最大价值为12,2号物品;
背包容量为3时,最大价值为22,1,2号物品;
所以背包能装的最大价值为:(背包容量5-物品i的容量2) 所能放的最大价值+物品i的价值。22+15=37。
背包容量j | |||||||
p[i][j] | 0 | 1 | 2 | 3 | 4 | 5 | |
0 | 0 | 0 | 0 | 0 | 0 | 0 | |
物品编号i | 1 | 0 | 0 | 12↖ | 12← | 12← | 12← |
2 | 0 | 10↖ | 12↑ | 22↖ | 22← | 22← | |
3 | 0 | 10↑ | 12↑ | 22↑ | 30↖ | 32↖ | |
4 | 0 | 10↑ | 15↖ | 25↖ | 30↑ | 37↖ |
#include<bits/stdc++.h>
#define NUM 10
#define CAP 6
using namespace std;
int V[NUM];
int W[NUM];
int P[NUM][CAP];
int main()
{
int c;//背包容量
int n;//物品个数
scanf("%d",&c);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&W[i],&V[i]);
for(int i=1;i<=n;i++)
{
for(int j=c;j>=1;j--)
{
if(W[i]<=j) P[i][j]=max(P[i-1][j],P[i-1][j-W[i]]+V[i]);
else P[i][j]=P[i-1][j];
printf(" %d ",P[i][j]);
}
printf("\n");
}
printf("%d",P[n][c]);
return 0;
}
输入:
5 4
2 12
1 10
3 20
2 15
输出:
12 12 12 12 0
22 22 22 12 10
32 30 22 12 10
37 30 25 15 10
37
2.0-1背包
#include<bits/stdc++.h>
#define NUM 10
#define CAP 6
using namespace std;
int V[NUM];
int W[NUM];
int P[NUM][CAP];
int main()
{
int c;//背包容量
int n;//物品个数
scanf("%d",&c);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&W[i],&V[i]);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=c;j++)
{
if(W[i]<=j) P[i][j]=max(P[i-1][j],P[i-1][j-W[i]]+V[i]);
else P[i][j]=P[i-1][j];
printf(" %d ",P[i][j]);
}
printf("\n");
}
printf("%d",P[n][c]);
return 0;
}
输入:
5 4
2 12
1 10
3 20
2 15
输出:
0 12 12 12 12
10 12 22 22 22
10 12 22 30 32
10 15 25 30 37
37
3.0-1背包一维
p[i][j]=max(p[i-1][j-w[i]]+v[i],p[i-1][j]),当前层的数值是由上一层推导出来的,可以把i-1的数据拷贝到第i层,直接在第i层计算,新的值覆盖当前层的数据,每次计算都在更新这一层的数据(滚动数组),实现二维数组的降维操作。
p[j]:容量为j的背包所能装的最大价值
二维递推公式:p[i][j]=max(p[i-1][j-w[i]]+v[i],p[i-1][j])
一维递推公式:
不放物品i p[j](p[i-1][j]已经拷贝到当前层)
放物品i p[i-w[i]]+v[i]
p[j]=max(p[j],p[i-w[i]]+v[i])
一维数组只能倒叙遍历,元素只被添加一次。
物品编号 | 1 | 2 | 3 | 4 |
质量W | 2 | 1 | 3 | 2 |
价值v | 12 | 10 | 20 | 15 |
背包容量:
5 | 4 | 3 | 2 | 1 | 0 |
物品:
12 | 12 | 12 | 12 | 0 | 0 |
22 | 22 | 22 | 12 | 10 | 0 |
32 | 30 | 22 | 12 | 10 | 0 |
37 | 30 | 25 | 15 | 10 | 0 |
遍历到物品4时,p[5-2]+v=p[3]+15
求背包容量为3时背包能装的最大价值,p[3]的最大值是,装了物品1和物品2时背包的价值。
#include<bits/stdc++.h>
#define NUM 10
using namespace std;
int V[NUM];
int W[NUM];
int P[NUM];
int main()
{
int c;//背包容量
int n;//物品个数
scanf("%d",&c);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&W[i],&V[i]);
for(int i=1;i<=n;i++)
{
for(int j=c;j>=1;j--)
{
if(W[i]<=j) P[j]=max(P[j],P[j-W[i]]+V[i]);
printf(" %d ",P[j]);
}
printf("\n");
}
printf("%d",P[c]);
return 0;
}
输入:
5 4
2 12
1 10
3 20
2 15
输出:
12 12 12 12 0
22 22 22 12 10
32 30 22 12 10
37 30 25 15 10
37
二、部分背包问题(物品可以分割 贪心算法)
物品可以是可以分割的金币,钻石,分割之后物品的单位价值不变。往里面装尽可能值钱的物品,所以优先选择单位价值高的物品。背包容量50,先装性价比最高的1号物品,再装质量20的2号物品,最后剩20个容量全装3号物品,这种方式背包装的物品价值最高。
n | 1 | 2 | 3 |
质量 | 10 | 20 | 30 |
价值 | 60 | 100 | 120 |
性价比 | 6 | 5 | 4 |
#include<bits/stdc++.h>
using namespace std;
struct bag{
int w;//质量
int v;//价值
double c;//性价比
}a[1001];
bool cmp(bag a,bag b)
{
return a.c >= b.c;//从大到小
}
double knapsack(int n,bag a[],double c)
{
double cleft = c;//背包的剩余容量
int i=0;//用来增加金币堆数,背包放完一堆金币,层数加一
double b = 0;//用来统计总价值
while(i<n&&a[i].w<cleft)
{
cleft -= a[i].w;//装进物品,容量需要减去这个物品的质量
b += a[i].v;//背包总价值增加
i++; //已经装了一堆金币,层数加一
}
if(i<n) b+=1.0*a[i].v*cleft/a[i].w;
//直到装不下一堆完整的金币,背包价值=现在的背包价值+剩下金币的性价比*剩余容量
return b;
}
int main()
{
int c;//背包容量
int n;//n堆金币
int i;
//while(scnaf("%d%d",&c,&n)&&c)
scanf("%d%d",&c,&n);
for(int i=0;i<=n-1;i++)
{
scanf("%d%d",&a[i].w,&a[i].v);//质量、价值
a[i].c = 1.0*a[i].v/a[i].w;//性价比
}
sort(a,a+n,cmp);//对金币的单位价值从大到小排序
printf("%15.3lf\n\n",knapsack(n,a,c));
return 0;
}
三、完全背包一维滚动数组
每个物品可以使用无数次,可以一直装同一件物品。
for(int i=1;i<=n;i++)
{ for(int j=1;j<=c;j++)//改成正序遍历物品就可以使用无数次
{
if(W[i]<=j) P[j]=max(P[j],P[j-W[i]]+V[i]);
printf(" %d ",P[j]);
}
}
物品编号 | 1 | 2 | 3 | 4 |
质量W | 2 | 1 | 3 | 2 |
价值v | 12 | 10 | 20 | 15 |
背包容量:
0 | 1 | 2 | 3 | 4 | 5 |
物品:
0 | 0 | 12 | 12 | 24 | 24 |
0 | 10 | 20 | 30 | 40 | 50 |
0 | 10 | 20 | 30 | 40 | 50 |
0 | 10 | 20 | 30 | 40 | 50 |
当遍历到物品2时,p[1]=10,p[2]=p[2-1]+10=20,
p[5]=p[4]+10=p[3]+10+10=p[2]+10+10+10=50。以此实现了多次装同一种物品。
#include<bits/stdc++.h>
#define NUM 10
using namespace std;
int V[NUM];
int W[NUM];
int P[NUM];
int main()
{
int c;//背包容量
int n;//物品个数
scanf("%d",&c);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&W[i],&V[i]);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=c;j++)
{
if(W[i]<=j) P[j]=max(P[j],P[j-W[i]]+V[i]);
printf(" %d ",P[j]);
}
printf("\n");
}
printf("%d",P[c]);
return 0;
}
输入:
5 4
2 12
1 10
3 20
2 15
输出:
0 12 12 24 24
10 20 30 40 50
10 20 30 40 50
10 20 30 40 50
50
本博客资料、代码来源于清华大学出版社算法设计与分析,本博客仅用于个人学习,可能存在纰漏,敬请批评指正。