赞
踩
一、【问题描述】
有n中重量和价值分别为wi、vi(1<=i<=n)的物品,从这些物品中挑选总重量不超过w的物品,求出挑选物品总价值和最大的挑选方案,这里每种物品可以挑选多件。
二、【问题求解】
1.设置动态规划二维数组dp,dp[i][j]表示从前i个物品中选出重量不超过j(或者剩余容量为j)的物品的最大总价值。
①显然有边界条件:dp[i][0]=0(背包不能装入任何物品时,总价值为0),dp[0][j]=0(没有任何物品可装入时,总价值为0),可以采用memset函数一次性初始化为0.
②另外设置二维数组fk,其中fk[i][j]存放dp[i][j]得到最大值时物品i挑选的件数。
2.状态转移方程
这样,dp[n][W]便是背包容量为W、考虑所有n个物品(同一物品允许多次选择)后得到的背包最大价值,即问题的最优结果。
接下来用一个例子来加深理解其机理:
题目:
例如一个完全背包问题,n=4,w={3,2,6,2},v={7,2,5,3}(下标从1开始),W=9。求出dp数组和fk数组,及描绘出由dp[4][9]求最优解过程。
解答:我们可以由上述得到数组dp[i][j]和fk[i][j]
fk[i][j]:
dp[i][j]:
回推过程:
(1)i=4,dp[4][9]=21,fk[4][9]=0,物品4挑选了0件。
(2)i=3,dp[3][9]=21,fk[3][9]=0,物品3挑选了0件。
(3)i=2,dp[2][9]=21,fk[2][9]=0,物品2挑选了0件。
(4)i=1,dp[1][9]=21,fk[1][9]=3,物品1挑选了3件。
仔细了解了推导过程之后接下来查看代码实现:
//求解完全背包问题的算法 #include <stdio.h> #include <string.h> #define MAXN 20 //最多物品数 #define MAXW 100 //最大限制重量 #define max(x,y) ((x)>(y)?(x):(y)) //问题表示 int n,W; int w[MAXN],v[MAXN]; //求解结果表示 int dp[MAXN+1][MAXW+1],fk[MAXN+1][MAXW+1]; int solve() //求解多重背包问题 { int i,j,k; for (i=1;i<=n;i++) { for (j=0;j<=W;j++) for (k=0;k*w[i]<=j;k++) { if (dp[i][j]<dp[i-1][j-k*w[i]]+k*v[i]) { dp[i][j]=dp[i-1][j-k*w[i]]+k*v[i]; fk[i][j]=k; //物品i取k件 } } } return dp[n][W]; } void Traceback() //回推求最优解 { int i=n,j=W; while (i>=1) { printf("物品%d共%d件 ",i,fk[i][j]); j-=fk[i][j]*w[i]; //剩余重量 i--; } printf("\n"); } void prin(){ //查看dp数组与fk数组 int i,j,k; printf("fk[i][j]:\n"); for (i=1;i<=n;i++) { for (j=0;j<=W;j++){ printf("%d ",fk[i][j]); } printf("\n"); } printf("dp[i][j]:\n"); for (i=1;i<=n;i++) { for (j=0;j<=W;j++){ printf("%d ",dp[i][j]); } printf("\n"); } } int main() { w[1]=3; w[2]=2; w[3]=6;w[4]=2; v[1]=7; v[2]=2; v[3]=5;v[4]=3; n=4; W=9; memset(dp,0,sizeof(dp)); memset(fk,0,sizeof(fk)); printf("最优解:\n"); printf(" 总价值=%d\n",solve()); printf(" 方案: ");Traceback(); printf("\n"); prin(); return 0; }
在如上代码中有添加debug部分,可以自行运行进行观察。
改进算法:实际上,上述算法可以不必使用k循环,可以修改为在挑选物品i时直接多次重复挑选,因为计算dp[i][j]中选择k(k>=1)个的情况与在dp[i][j-w[i]]的计算中选择k-1的情况是相同的,所以dp[i][j]的递推中k>=1部分的计算已经在dp[i][j-w[i]]的计算中完成了,如下是改进算法的代码,多余部分进行了注释。
//求解完全背包问题的算法 #include <stdio.h> #include <string.h> #define MAXN 20 //最多物品数 #define MAXW 100 //最大限制重量 #define max(x,y) ((x)>(y)?(x):(y)) //问题表示 int n,W; int w[MAXN],v[MAXN]; //求解结果表示 int dp[MAXN+1][MAXW+1],fk[MAXN+1][MAXW+1]; int solve() //求解多重背包问题 { int i,k,j; for (i=1;i<=n;i++) for (j=0;j<=W;j++) if (j<w[i]) dp[i][j]=dp[i-1][j]; else dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]); return dp[n][W]; //返回总价值 } //void Traceback() //回推求最优解 //{ // int i=n,j=W; // while (i>=1) // { // printf("物品%d共%d件 ",i,fk[i][j]); // j-=fk[i][j]*w[i]; //剩余重量 // i--; // } // printf("\n"); //} void prin(){ //查看dp数组与fk数组 int i,j,k; // printf("fk[i][j]:\n"); // for (i=1;i<=n;i++) // { // for (j=0;j<=W;j++){ // printf("%d ",fk[i][j]); // } // printf("\n"); // } printf("dp[i][j]:\n"); for (i=1;i<=n;i++) { for (j=0;j<=W;j++){ printf("%d ",dp[i][j]); } printf("\n"); } } int main() { w[1]=3; w[2]=2; w[3]=6;w[4]=2; v[1]=7; v[2]=2; v[3]=5;v[4]=3; n=4; W=9; memset(dp,0,sizeof(dp)); // memset(fk,0,sizeof(fk)); // printf("最优解:\n"); printf(" 总价值=%d\n",solve()); // printf(" 方案: ");Traceback(); // printf("\n"); prin(); return 0; }
博文参考自《算法设计与分析》李春葆第二版
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。