问题描述:
给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问应如何选择装入背包的物品,使得装
入背包中物品的总价值最大?
个人认为很重要的一点就是要理解 m[i][j]
对于m(i,j)就表示可选物品为i…n背包容量为j(总重量)时背包中所放物品的最大价值。
对于问题的比较好的分析与解释见:http://blog.csdn.net/dapengbusi/article/details/7463968
下面是我的代码实现部分,如果你看懂了以上博主的问题分析和解释,理解就更加深刻了。
下面来分析一下该动态规划算法的时间复杂性和空间复杂性:
显而易见上述算法计算Knapsack需要O(nc)计算时间,而Traceback需要O(n)计算时间。
但Knapsack算法有两个明显的缺点,1.要求算法所给的物品重量是wi(1<=i<=n)的整数,2.当背包容量很大时,算法需要计算的时间较多,例如,当c>2n次方是,算法Knapsack需要O(n2n)计算时间。
还有一种从上向下的动态规划算法:
此种算法详情请见博客:http://www.cnblogs.com/fly1988happy/archive/2011/12/13/2285377.html
个人认为很重要的一点就是要理解 m[i][j]
对于m(i,j)就表示可选物品为i…n背包容量为j(总重量)时背包中所放物品的最大价值。
对于问题的比较好的分析与解释见:http://blog.csdn.net/dapengbusi/article/details/7463968
下面是我的代码实现部分,如果你看懂了以上博主的问题分析和解释,理解就更加深刻了。
#include <iostream>
using namespace std;
#define MAX 20
const int N = 5;
void Knapsack(int v[],int w[],int c,int n,int m[][MAX]);
void Traceback(int m[][MAX],int w[],int c,int n,int x[]);
void printValue(int m[][MAX],int n,int c);
int main()
{
int c=10;
int v[]={0,6,3,5,4,3},w[]={0,2,2,6,5,4};//下标从1开始
int x[N+1];
int m[MAX][MAX];
cout<<"背包总容量:"<<c<<endl;
cout<<"待装物品重量分别为:"<<endl;
for(int i=1; i<=N; i++)
{
cout<<w[i]<<" ";
}
cout<<endl;
cout<<"待装物品价值分别为:"<<endl;
for( i=1; i<=N; i++)
{
cout<<v[i]<<" ";
}
cout<<endl;
Knapsack(v,w,c,N,m);
cout<<"背包能装的最大价值为:"<<m[1][c]<<endl;
Traceback(m,w,c,N,x);
cout<<"背包装下的物品编号为:"<<endl;
for( i=1; i<=N; i++)
{
if(x[i]==1)
{
cout<<i<<" ";
}
}
cout<<endl;
printValue(m,N,c);
return 0;
}
void Knapsack(int v[],int w[],int c,int n,int m[][MAX])
{
int jMax = w[n]-1;//此处将jMax=w[n]-1即表示背包容量小于当前物品w[n]的情况
int j,i;
for( j=0; j<=jMax;j++) //背包容量比当前w[n]的都小时,最底部的先初始化为0,比较下面可知,
{ //</span>之后同样的情况就是上一层继承下一层了。
m[n][j]=0;
}
for( j=w[n]; j<=c; j++) //最底部此时背包的最大总价值只能是当前这个物品的价值,而不管你背包
{ //</span>容量多大,因为只有 一个物品可以选择。
m[n][j] = v[n];
}
for( i=n-1; i>1; i--)
{
jMax = w[i]-1;
for( j=0; j<=jMax; j++)//同样是背包容量小于w[i],这里是该层继承下一层的值
{
m[i][j] = m[i+1][j];
}
for( j=w[i]; j<=c; j++) //此时有两种选择要么继承下一层的值,要么添加该层的值,
{ //所以要比较选取最大值得情况
m[i][j] = (m[i+1][j]>m[i+1][j-w[i]]+v[i])?m[i+1][j]:m[i+1][j-w[i]]+v[i];
}
}
//最后一次都不必循环了(因为循环的目的是为了利用中间值,在第一行所利用的中间值全都保存在了第二行,
//所以此时只许判断总容量是否大于w[1],再选择是否加入背包中。
m[1][c] = m[2][c];
if(c>=w[1])
{
m[1][c] = (m[1][c]>m[2][c-w[1]]+v[1])?m[1][c]:m[2][c-w[1]]+v[1];
}
}
//x[]数组存储对应物品0-1向量,0不装入背包,1表示装入背包
void Traceback(int m[][MAX],int w[],int c,int n,int x[])
{
for(int i=1; i<n; i++)
{
if(m[i][c] == m[i+1][c])
{
x[i]=0;
}
else
{
x[i]=1;
c-=w[i];
}
}
//由于存在上一层继承下一层的值(m[i][c] == m[i+1][c]),则当前层即当前编号为i的物品没有放入背包,
//继续下一层,那么如果都到了底部的话,说明最底部的那个及编号为n的物品一定放入的背包中(因为
//肯定再无向下继承了)
//***只要判断到了m[n][c]不为0,那么最后一件物品必然放进了背包
x[n]=(m[n][c])?1:0;
}
void printValue(int m[][MAX],int n,int c)
{
cout<<"打印m[i][j](第一行只计算了最后一个值):\n"<<endl;
for(int i=1;i<=n;i++)
{
cout<<"row "<<i<<": ";
for(int j=0;j<=c;j++)
{
if(i==1&&j<c)
cout<<" ";
else
cout<<m[i][j]<<" ";
}
cout<<endl;
}
}
下面来分析一下该动态规划算法的时间复杂性和空间复杂性:
显而易见上述算法计算Knapsack需要O(nc)计算时间,而Traceback需要O(n)计算时间。
但Knapsack算法有两个明显的缺点,1.要求算法所给的物品重量是wi(1<=i<=n)的整数,2.当背包容量很大时,算法需要计算的时间较多,例如,当c>2n次方是,算法Knapsack需要O(n2n)计算时间。
还有一种从上向下的动态规划算法:
#include <iostream>
using namespace std;
const int MIN=0x80000000;
const int N=3; //物品数量
const int V=8; //背包容量
int f[N+1][V+1];
int Package(int *W,int *C,int N,int V);
void TraceBack(int N,int V,int *C,int *W);
void main(int argc,char *argv[])
{
int W[4]={0,7,5,8}; //物品权重
int C[4]={0,3,2,4}; //物品大小
Package(W,C,N,V);
TraceBack(N,V,C,W);
return;
}
void TraceBack(int N,int V,int *C,int *W)
{
if(f[N][V]>0)
{
cout<<"最优结果为:"<<f[N][V]<<endl;
int i=N,j=V;
while(i)
{
if(f[i][j]==(f[i-1][j-C[i]]+W[i]))
{
cout<<i<<":"<<"w="<<W[i]<<",c="<<C[i]<<endl;
j-=C[i];
}
i--;
}
}
else
cout<<"无最优值"<<endl;
}
int Package(int *W,int *C,int N,int V)
{
int i,j;
memset(f,0,sizeof(f)); //初始化为0
//for(i=0;i<=N;i++)
//for(j=1;j<=V;j++) //此步骤是解决是否恰好满足背包容量,
//f[i][j]=MIN; //若“恰好”满足背包容量,即正好装满背包,则加上此步骤,若不需要“恰好”,则初始化为0
for(i=1;i<=N;i++)
for(j=C[i];j<=V;j++)
{
f[i][j]=(f[i-1][j]>f[i-1][j-C[i]]+W[i])?f[i-1][j]:(f[i-1][j-C[i]]+W[i]);
}
return f[N][V];
}
此种算法详情请见博客:http://www.cnblogs.com/fly1988happy/archive/2011/12/13/2285377.html