动态规划之0_1背包

这篇博客探讨了如何使用动态规划解决0_1背包问题,以最大化背包中物品的总价值。作者介绍了问题背景,并提供了代码实现,包括一个自上而下的动态规划算法。此外,还分析了算法的时间和空间复杂性。
摘要由CSDN通过智能技术生成
问题描述:
   给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问应如何选择装入背包的物品,使得装
入背包中物品的总价值最大?


个人认为很重要的一点就是要理解    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







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值