原创作品 转载请注明出处http://blog.youkuaiyun.com/always2015/article/details/46530249
一、问题描述
0-1背包问题,部分背包问题
(1)实现0-1背包的动态规划算法求解
(2)实现部分背包的贪心算法求解
实现0-1背包的动态规划算法求解
给定N中物品和一个背包。物品i的重量是Wi,其价值位Vi ,背包的容量为C。问应该如何选择装入背包的物品,使得转入背包的物品的总价值为最大。在选择物品的时候,对每种物品i只有两种选择,即装入背包或不装入背包。不能讲物品i装入多次,也不能只装入物品的一部分。因此,该问题被称为0-1背包问题。
实现部分背包的贪心算法求解
每一个物品都可以分割成单位块,单位块的利益越大显然总收益越大,所以它局部最优满足全局最优,可以用贪心法解答
二、算法原理:
1、 0-1背包问题
动态规划法将待求解问题分解成若干个相互重叠的子问题,每个子问题对应决策过程的一个阶段,一般来说,子问题的重叠关系表现在对给定问题求解的递推关系(也就是动态规划函数)中,将子问题的解求解一次并填入表中,当需要再次求解此子问题时,可以通过查表获得该子问题的解而不用再次求解,从而避免了大量重复计算。
动态规划法设计算法一般分成三个阶段:
(1)分段:将原问题分解为若干个相互重叠的子问题;
(2)分析:分析问题是否满足最优性原理,找出动态规划函数的递推式;
(3)求解:利用递推式自底向上计算,实现动态规划过程。
0/1背包问题可以看作是决策一个序列(x1, x2, …, xn),对任一变量xi的决策是决定xi=1还是xi=0。在对xi-1决策后,已确定了(x1, …, xi-1),在决策xi时,问题处于下列两种状态之一:
(1)背包容量不足以装入物品i,则xi=0,背包不增加价值;
(2)背包容量可以装入物品i,则xi=1,背包的价值增加了vi。
这两种情况下背包价值的最大者应该是对xi决策后的背包价值。令V(i, j)表示在前i(1≤i≤n)个物品中能够装入容量为j(1≤j≤C)的背包中的物品的最大值,则可以得到如下动态规划函数:
最大值,则可以得到如下动态规划函数:
V(i, 0)= V(0, j)=0 (式3)
(式4)
式3表明:把前面i个物品装入容量为0的背包和把0个物品装入容量为j的背包,得到的价值均为0。式4的第一个式子表明:如果第i个物品的重量大于背包的容量,则装入前i个物品得到的最大价值和装入前i-1个物品得到的最大价值是相同的,即物品i不能装入背包;第二个式子表明:如果第i个物品的重量小于背包的容量,则会有以下两种情况:(1)如果把第i个物品装入背包,则背包中物品的价值等于把前i-1个物品装入容量为j-wi的背包中的价值加上第i个物品的价值vi;(2)如果第i个物品没有装入背包,则背包中物品的价值就等于把前i-1个物品装入容量为j的背包中所取得的价值。显然,取二者中价值较大者作为把前i个物品装入容量为j的背包中的最优解。
实验数据
对于部分背包问题与0-1背包问题我们给定相同的物品情况与包的容量。
给定物品:[weight: 1 value: 6][weight: 2 value: 10][weight: 3 value: 12]
给定包的容量: 5,也可以根据自己的需要输入不同的数据。
运行结果如下:
从上述结果图来看,第二行到第四行表示V(i,j)一步一步改变的过程。
我的代码如下:
#include <iostream>
#define OBJECT_NUM 3 //物品的个数
#define KNAP_WEIGHT 5 //背包的重量
using namespace std;
//物品包括价值和重量
struct input_object
{
int value;
int weight;
};
//比较两者最大一个
int bigger(int a,int b)
{
if(a>b)return a;
else return b;
}
//获取最大的价值
//may_result存放可行解
int get_max_value(input_object*object,int *may_result)
{
int max_value;
int value[OBJECT_NUM+1][KNAP_WEIGHT+1];//value[i][j]把i个物体装入容量j的背包获得的最大价值
/*把前面i个物品装入容量为0的背包
和把0个物品装入容量为j的背包,
得到的价值均为0
*/
for(int i=0; i<=OBJECT_NUM; i++)
{
value[i][0]=0;//初始化第零列
}
for(int j=0; j<=KNAP_WEIGHT; j++)
{
value[0][j]=0;//初始化第零列
}
/*
计算value的值
在这里要注意object[]数组范围为0-OBJECT_NUM-1,下面的i-1表示第i个物品
*/
for(int i=1; i<=OBJECT_NUM; i++)
{
for(int j=1; j<=KNAP_WEIGHT; j++)
{
if(object[i-1].weight>j)
{
value[i][j]=value[i-1][j];
//输出
cout<<value[i][j]<<" ";
}
else
{
value[i][j]=bigger(value[i-1][j],(value[i-1][j-object[i-1].weight]+object[i-1].value));
cout<<value[i][j]<<" ";
}
}
cout<<endl;
}
//逆推求解,这一步是求出放入哪些物品
int knapweight=KNAP_WEIGHT;
for(int i=OBJECT_NUM; i>0; i--)
{
if(value[i][knapweight]>value[i-1][knapweight])
{
may_result[i-1]=1;//第i个物品可以放入,may_result从0开始
knapweight=knapweight-object[i-1].weight;//减去第i个物品的重量
}
else
{
may_result[i-1]=0;
}
}
max_value=value[OBJECT_NUM][KNAP_WEIGHT];
return max_value;
}
int main(void)
{
input_object*object;
int maxValue,*mayResult;
object=new input_object[OBJECT_NUM];
mayResult=new int[OBJECT_NUM];
//输入物品
for(int i=0; i<OBJECT_NUM; i++)
{
cin>>object[i].weight>>object[i].value;
}
maxValue=get_max_value(object,mayResult);
cout <<endl<< "最大价值为:"<<maxValue << endl<<endl;
//输出放入物品的状态
cout<<"各个物品的状态为(0表示不放入,1表示放入):"<<endl;
for(int k=0; k<OBJECT_NUM; k++)
{
cout<<mayResult[k]<<" ";
}
cout<<endl;
return 0;
}
二、部分背包问题
因为每一个物品都可以分割成单位块,单位块的利益越大显然总收益越大,所以它局部最优满足全局最优,可以用贪心法解答。
(1)先将单位块收益按从大到小进行排序;
(2)初始化背包的剩余体积和当前价值;
(3)从前到后考虑所有物品:a.如果可以完全放入,当前价值加上物品总价值,剩余体积减去物品总体积;b.如果可以部分放进,当前价值加上物品价值*剩余体积,使剩余体积为0.
实验数据
对于部分背包问题与0-1背包问题我们给定相同的物品情况与包的容量。
给定物品:[weight: 10 value: 60][weight: 20 value: 100][weight: 30 value: 120]
给定包的容量: 50
运行结果如下:
我的代码如下:
#include <iostream>
#include<string.h>
#include<algorithm>
#define OBJECT_NUM 3 //物品的个数
#define KNAP_WEIGHT 50 //背包的重量
using namespace std;
struct input_object
{
int weight;
int value;
double ratio;
};
/*
调用sort排序函数,按照价值与重量比排序,如果贪心
比值相等则按照重量排序
*/
bool bigger(input_object a,input_object b)
{
if(a.ratio==b.ratio)return a.weight<b.weight;
else return a.ratio>b.ratio;
}
int main(void)
{
input_object *object;
int curr_weight=0;//当前背包的总重量
double value=0;//背包当前的总价值
//分配空间
object=new input_object[OBJECT_NUM];
//输入各物品
for(int i=0; i<OBJECT_NUM; i++)
{
cin>>object[i].weight>>object[i].value;
object[i].ratio=(double)object[i].value/object[i].weight;//价值和重量之比
}
//对各个物品重新排序
sort(object,object+OBJECT_NUM,bigger);
for(int j=0; j<OBJECT_NUM; j++)
{
for(int k=1; k<=object[j].weight; k++)
{
//每次只增加一个重量单位
if(curr_weight+1<=KNAP_WEIGHT)
{
curr_weight+=1;
value+=object[j].ratio;
}
}
}
cout<<"The total value: "<<value<<endl;
return 0;
}
三、结果分析
正确性分析
(1) 0-1背包问题
因为物品不能拆分,所以对于只有5kg容量的背包可以放的物品,列出所有的情况为:
[weight: 1 value: 6] [weight: 2 value: 10] 总价值为16
[weight: 1 value: 6] [weight: 3 value: 12] 总价值为18
[weight: 2 value: 10][weight: 3 value: 12] 总价值为22
所有结果中,选取[weight: 2 value: 10][weight: 3 value: 12]时的总价值最大,为22.与我们用动态规划做出来的数据是一致的。所以我们的算法正确。
(2) 部分背包问题
物品可以拆分,所以首先选取单位价格最大的物品放入包中,在给出的物品中可知:
[weight: 10 value: 60] 平均价值60
[weight: 20 value: 100] 平均价值50
[weight: 30 value: 120] 平均价值40
所以先放平均价值高的物品,将[weight: 10 value: 60],[weight: 20 value: 100]装进去以后,还能装20kg的[weight: 30 value: 120]。所以总的价值为240.与我们用贪心算法做出来的结果是一样的。
如果对上述还有讲解不清楚的请海涵,也可以私信我一起讨论。