简单背包问题+0/1背包问题+DP

简单背包问题
  首先不好意思,前段时间在做项目,现在项目结束了,有时间了,来为大家更新下算法!
看题:
小红和小明在魔法石矿里挖到了很多的魔法石,他们有一个背包,可以放入的重量为S,现有N件魔法石,重量分别为W1,W2,W3,...Wn,各重量均为正整数,从N件魔法石中挑选若干件,使得放入背包的重量之和正好为S。若成功,则输出放入背包的物品,否则输出“Failed!”。
【输入格式】
 第一行两个整数即S和N,其中S<1000,N<32.第二行为N个整数,即N件物品的重量。
【输出格式】
 若成功,则输出放入背包的物品,以空格间隔,否则输出“Failed!”.
【输入样例】
10  5
12 3 4 5 6
【输出样例】
4 6

枚举算法
  该问题可以转化为某个物品取或不取的问题,以1代表取,以0代表不取,穷举出所有的可能性,输出总质量==S的组合即可,下面是输出所有符合条件的组合:
//简单背包问题——枚举算法
#include<iostream>
using namespace std;
int N,S;
int W[40];           		//初始化每个物品的重量 
int flag[40]={0};			//标记数组


void Print()				//打印结果 
{
	for(int i=0;i!=N;i++)
	if(1 == flag[i])
		cout<<W[i]<<" ";
	cout<<endl; 
} 

int main()
{
	int sum,all_count=1;
	cin>>S>>N;
	for(int i=0;i<N;i++)
		cin>>W[i];
	for(int i=0;i!=N;i++)	//计算所有可能性次数,即2的n次方 
		all_count *= 2;
		
	
	for(int num=0;num<=all_count;++num)
	{
		for(int i=0;i!=N;i++)	//列举所有flag数组可能 
			if(flag[i] == 0)
			{
				flag[i]=1;
				continue;
			}
			else
			{
				flag[i]=0;
				break;
			}
			sum = 0;			//本次重量初始化为0 
			
	for(int i=0;i!=N;i++)		//按标记计算所有选中物品重量和 
		if(flag[i] == 1)
			sum += W[i];
	if(sum == S)				//打印方案 
		Print();
	} 
	return 0;
	
}
下面我们来引入另一种算法
递归算法
  解决此题最可能的方法是一个一个将物品放入背包内实验,设布尔函数knapsack(s,n)表示剩下n个物品中装满剩下重量为s的背包,如果有解,返回1,否则返回0.实验过程应该如下所述:
(1)取最后一个物品Wn,调用knapsack(s,n);
(2)如Wn=s,结束程序,输出结果(n,Wn);
(3)如Wn<s,且n>1,则求knapsack(s-Wn,n-1);
(4)如Wn>s,且n>1,删除Wn,从剩下n-1中继续找,即knapsack(s,n-1)。
还可以得知递归结束的条件应为:
(1)Wn=s(正好放入的物品重量等于背包能装的重量)
(2)Wn!=s(无解)
(3)n<=0(再没有物品可试)
  但实际上问题并不是这么简单,因为所选取并放入的物品Wn很可能导致无法获得正确结果。例如s=10,
物品重量分别为1,6,2,7,5,如果第一次选择Wn=5放入背包后,则后面再怎么选择也不可能成功,正确的做法是排除
Wn=5,从Wn=7开始才可能有正确答案,即7+2+1=10。
  因此Wn是否有效还要看后续的knapsack(s-Wn,n-1)是否有解,如果无解,说明先前取得Wn不合适,就要放弃Wn,在剩余物品中
重新开始挑选,即knapsack(s,n-1)。
参考代码如下:
//简单背包问题——递归算法
#include<iostream>
using namespace std;
#define MAXN 40

int W[MAXN];		//各物品重量

int knapsack(int s,int n)		//s为剩余重量,n为剩余可选物品数 
{
	if(s == 0)					//如果正好装满 
		return 1;	
	if(s<0 || (s>0 && n<1))		//如s<0 或 n<1 则不能完成 
		return 0;
	if(knapsack(s-W[n],n-1))	//从后往前装,装上W[n]后,若剩余物品仍有解 
	{
		cout<<W[n]<<" ";		//则装进第n个包,并输出 
		return 1;
	}
	return knapsack(s,n-1);		//如装了第n个包后,导致无解,则删除该包,尝试第n-1个 
} 



int main()
{
	int S,N;
	cin>>S>>N;
	for(int i=1;i<=N;++i)
		cin>>W[i];
	if(knapsack(S,N))
		cout<<"\n";
	else
		cout<<"Failed!\n";
	
}
大家是不是对背包问题有一定了解了,下面我们引入另一种背包0/1背包问题
0/1背包问题
小红和小明有一个最多能装m千克的背包,有n块魔法石,它们的重量分别是W1,W2,...Wn,它们的价值分别是为C1,C2,...Cm。若每种魔法石只有一件,问能装入的最大总价值。
【输入示例】
  第一行为两个整数m和n,以下n行中,每行两个整数Wi,Ci,分别代表第i件物品的重量和价值
【输出格式】
  输出一个整数,即最大价值
【输入样例】
8 3
2 3
5 4
5 5
【输出样例】
8
动态规划算法
在这道题里,物品或者被装入背包,或者不被装入背包,只有两种选择。因此被称为0/1背包问题。我们可以使用穷举组合、贪心算法,但是,用穷举组合可能会超时,贪心法不稳定,往往的不出最优解。由此需要考虑动态规划算法(DP)。

使用动态规划算法解决如下:
设f(i,x)表示前i件物品,背包容量为x时的最优价值;
则f(i,x)=max{f(i-1,x-W[i])+C[i],f(i-1,x)}
f(n.m)即为最优解,边界条件为f(0,x)=,f(i,0)=0;
如果对上述公式不理解,我们使用题目中的样例用表格法来分析:
根据边界条件f(0,x)=0,f(i,0)=0;表格如下所示:

 试放入第一个物品,即W=2,C=3试一下在各个重量段所能取得的最大价值如表所示:


  如下表所示,试放入第二个物品,即w=5,c=4,试一下在各个重量段所能取得的最大价值:

 由于第二个物品重量为5,所以在1~4重量段时,值仍为3,当重量段>=5时,可以尝试放入改物品,即比较改重量段减去5时的价值
加该物品的价值,与不放第二个物品时的总价值哪个大,取其最大值。以后读入的物品一次类推。
	例如:f(2,5)=max(f(1,(5-5))+4,f(1,5))
		    =max(f(1,0)+4,f(1,5))
		    =4
		F(2,7)=max(f(1,(7-5))+4,f(1,7))
		      =max(f(1,2)+4,f(1,7))
		      =7
	以此法试放入第三个物品,即w=5,c=5时,表格如图所示:



最终答案为8
完整代码
// 0/1背包问题
#include<iostream>
#include<cstdlib>
using namespace std;


int i,j,m,n;
int w[200],c[200],f[200][200];

int main()
{
	cin>>m>>n;
	for(i=1;i<=n;i++)
		cin>>w[i]>>c[i];
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
			if(j >= w[i])
				f[i][j]=max(f[i-1][j-w[i]]+c[i],f[i-1][j]);
			else
				f[i][j]=f[i-1][j];
	cout<<f[n][m]<<endl;
	return 0;
} 
到这里就结束了,希望此篇文章,能够对大家有所帮助,也希望的算法精益求精,不断突破瓶颈;下次为大家讲解N皇后问题!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值