简单背包问题
首先不好意思,前段时间在做项目,现在项目结束了,有时间了,来为大家更新下算法!
看题:
小红和小明在魔法石矿里挖到了很多的魔法石,他们有一个背包,可以放入的重量为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
【输入格式】
第一行两个整数即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)。 参考代码如下:
大家是不是对背包问题有一定了解了,下面我们引入另一种背包0/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背包问题
小红和小明有一个最多能装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 完整代码到这里就结束了,希望此篇文章,能够对大家有所帮助,也希望的算法精益求精,不断突破瓶颈;下次为大家讲解N皇后问题!// 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; }