一、0/1背包问题算法基本思想
0/1背包问题是经典的动态规划问题。
问题基本描述为:给你体积为V的背包,在物品G1,G2,G3...(这些物品所对应的体积分别为W1,W2,W3...价值分别为P1,P2,P3...)中选择一些放到背包里,使得在不超过背包体积的情况下,背包内物品的价值总和为最大。
一句话:决定一件物品要不要放进背包的条件是这件物品放进去所带来的总体收益是大于还是小于不放进去的总体收益,也就是对应的动态规划方程为:f(i,v) = max{ f(i-1,v) , f(i-1,v-w[i]) + p[i] },其中f(i,v)表示在体积为V的背包里放i件物品。这个方程的含义就是 在v体积的背包内,放i-1件物品(不放i物品)所带来的收益为f(i-1,v)。 在v体积的背包内,如果要放i物品,那么剩下的体积只有v-w[i],在这么多体积内放i-1件物品所带来的总体收益再加上i物品带来的 收益 就是f(i-1, v-w[i])。那么在v体积内可以放i件物品的时候最大收益就是这以上两种情况的较大者。
二、0/1背包问题的数据结构
w[i] 一维数组: 表示i物品所占体积
p[i] 一维数组: 表示i物品的收益
三、0/1背包问题过程图解
0/1背包问题确实难画图解,网上的现成的也少,我就不好意思省略了。
四、算法源代码
1.0/1背包递归算法
#include<cstdio> #define MAXN 20 //物品1,2...5的价值 int p[] = {0,6,3,5,4,6}; //物品1,2...5的体积 int w[] = {0,2,2,6,5,4}; //递归算法: 表示当背包总共容量为V的情况下,放i件物品所能获得的最大价值 int f(int i,int V); //回溯求最优解向量 void traceback(int x[],int n,int ft[][MAXN],int V); //计算最大值 int max(int a,int b); //程序执行入口 int main() { printf("Using a recursive algorithm:\n"); int maxValue = f(5,10); printf("when the count of goods is %d and the total volume of the bag is %d,\nthe maxValue is %d\n\n",5,10,maxValue); return 0; } int max(int a,int b){ return a>b?a:b; } //递归实现 p[] = {0,6,3,5,4,6}; w[] = {0,2,2,6,5,4}; 注意p,w中0只是拿来填充index = 0的时候没什么意义 int f(int i,int V) { //当背包里面物品为0个的时候,价值当然为0 if(i==0) return 0; //当所放物品的体积超过背包所提供最大体积 //当然不能考虑把这个物品放入背包的情况 if (V < w[i]) return f(i-1,V); //当情况不是上面那样的时候 //就要考虑把当前 i 物品放入背包 else return max(f(i-1,V),f(i-1,V-w[i])+p[i]); } //回溯寻找最优解向量 void traceback(int x[],int n,int ft[][MAXN],int V) { //基本思路就是在最优解成立情况下,如果i物品被放进背包(即x[i] = 1) //那么ft[i][V]就是对应的最优子结构,这样给剩下物品提供的总体积减少w[i] //如果i物品不放进背包那么x[i] = 0,当然给剩下物品的总体积不变 for(int i=1;i<=n;i++){ //这一步比较就是来说明i物品有没有放进去 //如果放进去f[i][V]和f[i-1][V]肯定是不会相同的 if(ft[i][V]==ft[i-1][V]) x[i] = 0; else{ x[i] = 1; V-=w[i]; } } }
2.0/1背包递推算法(加回溯求解向量)
#include<cstdio> #define MAXN 20 //物品1,2...5的价值 int p[] = {0,6,3,5,4,6}; //物品1,2...5的体积 int w[] = {0,2,2,6,5,4}; //递推算法: void knapsack(int n,int V,int ft[][MAXN],int p[],int w[]); //回溯求最优解向量 void traceback(int x[],int n,int ft[][MAXN],int V); int ft[MAXN][MAXN]; //计算最大值 int max(int a,int b); //程序执行入口 int main() { printf("Using a recursion algorithm:\n"); knapsack(5,10,ft,p,w); printf("when the count of goods is %d and the total volume of the bag is %d,\nthe maxValue is %d\n",5,10,ft[5][10]); int x[MAXN]; traceback(x,5,ft,10); printf("the best answer is ("); for(int i=1;i<=5;i++) printf("%d,",x[i]); printf(")\n"); return 0; } int max(int a,int b){ return a>b?a:b; } //递推算法 p[] = {0,6,3,5,4,6}; w[] = {0,2,2,6,5,4}; 注意p,w中0只是拿来填充index = 0的时候没什么意义 void knapsack(int n,int V,int ft[][MAXN],int p[],int w[]) { //初始化 for(int i=0;i<=V;i++){ ft[0][i] = 0; } for(int i=1;i<=n;i++) { for(int j=1;j<=V;j++) { if(j<w[i]) ft[i][j] = ft[i-1][j]; else ft[i][j] = max(ft[i-1][j],ft[i-1][j-w[i]]+p[i]); } } } //回溯寻找最优解向量 void traceback(int x[],int n,int ft[][MAXN],int V) { //基本思路就是在最优解成立情况下,如果i物品被放进背包(即x[i] = 1) //那么ft[i][V]就是对应的最优子结构,这样给剩下物品提供的总体积减少w[i] //如果i物品不放进背包那么x[i] = 0,当然给剩下物品的总体积不变 for(int i=1;i<=n;i++){ //这一步比较就是来说明i物品有没有放进去 //如果放进去f[i][V]和f[i-1][V]肯定是不会相同的 if(ft[i][V]==ft[i-1][V]) x[i] = 0; else{ x[i] = 1; V-=w[i]; } } }