0-1背包问题
给定n种物品和一背包。物品重量wi,价值vi,背包容量c。如何选择物品使得装入背包中的价值最大?
状态表示:
m(i,j),1<=i<=n,0<=j<=c,表示背包可用容量j,待考虑装包的物品集{i,i+1,…n}时的最大装入物品价值。
转态转移方程:
边界:m(n,j)=0 第n个物品不放进去(因为容量不够),j<wi;
=Vn 第n个物品放进去,j>=wi;
一般:m(i,j)=m(i+1,j) 第n个物品不放进去(容量不够),j<wi;
=max{m(i+1,j),m(i+1,j-wi)+vi},容量够,j>=wi;
最终m[1][c]即原问题的解。
关键代码:
void knacksack(int m[][1000],int n,int c,int w[],int v[])
{
for(int j=0;j<=c;++j)//边界,即当前只处理第n个物品
if(j>=w[n]) m[n][j]=v[n];
else m[n][j]=0;
for(int i=n-1;i>=1;--i)//自第n-1行向上填充表
for(int j=0;j<=c;++j)
if(j<w[i]) m[i][j]=m[i+1][j];//容量不足,第i个物品装不下
else m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
//容量够,进行选择
}
总代码:
#include <iostream>
using namespace std;
void knacksack(int m[][1000],int n,int c,int w[],int v[])
{
for(int j=0;j<=c;++j)//边界,即当前只处理第n个物品
if(j>=w[n]) m[n][j]=v[n];
else m[n][j]=0;
for(int i=n-1;i>=1;--i)//自第n-1行向上填充表
for(int j=0;j<=c;++j)
if(j<w[i]) m[i][j]=m[i+1][j];//容量不足,第i个物品装不下
else m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
//容量够,进行选择
}
void traceback(int m[][1000],int n,int c,int w[],int v[],int x[])//输出物品装入背包的情况
{
for(int i=1;i<n;++i)
if(m[i][c]==m[i+1][c])
//因为我们求的其实是m[1][c]
//所以traceback从m[1][c]开始
x[i]=0;//用数组x记录是否装入
else{
x[i]=1;//第i个物品装入了
c=c-w[i];
}
if(m[n][c]>0) x[n]=1;//因为i=n时进入循环体数组会越界
else x[n]=0;
}
int main()
{
int n;cin>>n;//物品数量
int c;cin>>c;//容量
int m[n+1][1000];//表示不同状态下的价值
int w[n+1],v[n+1];
for(int i=1;i<=n;++i)//输入重量 价值
cin>>w[i]; //注意数组下标都从1开始
for(int j=1;j<=n;++j)
cin>>v[j];
int x[n+1];//记录背包放置情况
knacksack(m,n,c,w,v);
traceback(m,n,c,w,v,x);
cout<<"最大价值:"m[1][c]<<endl;
cout<<"背包放入情况:";
for(int i=1;i<=n;++i)
cout<<x[i]<<' ';
return 0;
}
0-1背包问题空间优化
可以发现0-1背包的状态转移方程 dp[i][j] = max{dp[i+1][j-w[i]]+v[i],dp[i+1][j]}的特点,当前状态仅依赖前一状态的剩余体积与当前物品体积v[i]的关系(如果从表格的角度(i,j)位置的值只依赖于下一行中正下方和下一行中左前方位置的值)。根据这个特点,我们可以将dp降到一维即dp[j] = max{dp[j],dp[j-w[i]]+v[i]}。
但需要注意:用二维数组时,循环体内j(剩余容量)的值是递增式填充;但一维的时候要从大到小填充(如果从小到大:由于是一维数组,会改变前面数组的值,影响了数组后面的数据更新)
关键代码
void knacksack(int m[],int n,int c,int w[],int v[])//空间优化
{
for(int j=0;j<=c;++j)//当于初始化 第n行数据
if(j>=w[n]) m[j]=v[n];
else m[j]=0;
for(int i=n-1;i>=1;--i)
for(int j=c;j>=w[i];--j) //容量从大到小来
//只有j>=w[i]更新 否则维持原值不变
m[j]=max(m[j],m[j-w[i]]+v[i]);
}
完全背包问题
问题描述:和0-1背包问题唯一区别在于每个物品数量是无限的
关键代码:
该代码中没有显示边界处理的代码(和上方的0-1背包边界处理一样)
for(int i=n-1; i>=1; --i)
for(int j=0; j<=c; j++)
{
dp[i][j]=dp[i-1][j];
for(int k=1; k*w[i]<=j; k++)
dp[i][j]=max(dp[i][j],dp[i][j-k*w[i]]+k*v[i]);
}
背包问题之贪心算法
此时放入背包可以只放入物品的一部分(就是不一定要完整放入)。我们的贪心策略是:求出每个物品单位重量的价值
,将单位重量价值高的物品先放入背包.
#include <iostream>
#include <algorithm>
using namespace std;
struct bag{
float w;//物品的重量
int v;//物品的价值
float vw;//物品单位重量的价值
float x;//使用率:1代表物品完整放入,小于1代表被分割后放入
};
bool cmp(bag &a,bag &b)
{
return a.vw>b.vw;
}
int main()
{
int n;cin>>n;//物品数量
bag b[n];//定义n个结构体
int c;cin>>c;//背包max容量
for(int i=0;i<n;++i)
cin>>b[i].w;
for(int i=0;i<n;++i)
cin>>b[i].v;
for(int i=0;i<n;++i)
{
b[i].vw=b[i].v/b[i].w;//单位质量价值
b[i].x=0;//使用率初始化0
}
sort(b,b+n,cmp);//按单位质量价值排序
int k;
for(k=0;k<n;++k)
{
if(b[k].w>c) break;
b[k].x=1;
c-=b[k].w;
}
if(k<=n-1) b[k].x=c/b[k].w;//最后一个物品可能只装了一部分
for(int i=0;i<n;++i)
{
cout<<"重:"<<b[i].w<<"、价值:"<<b[i].v<<"的物品被放入了背包"<<endl<<"放入比例:"<<b[i].x<<endl;
}
}