问题描述:给定n种物品和一个背包物品i的重量是wi,其价值为vi,背包最大承重量为C。物品是不可分割的,应如何选择装入背包的物品,使得装入背包的总价值最大?
1.动态规划:
(1)定义一个二维数组dp[i][j],行代表前i个物品,共有n个物品,需要决策n次。每一次决策完成之后,dp[i][j]由之前的决策结果更新而得出来。
(2)如果背包的剩余容量小于物品的重量,那么不装该物品,此时dp[i][j]=dp[i-1][j],也就是最优解即为前i-1个物品的最优解。
(3)如果背包容量可以装下物品,此时需要决策是否装入该物品,利用max函数决策dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]),进而取得最大价值。
代码展示如下:
//动态规划
int DP(){
for(int i = 1; i <= N; i++)
for(int j = 0; j <= C; j++)
{
//i物品装不下,则此时的最优解与i-1的相同
if(j<w[i])
dp[i][j] = dp[i - 1][j];
else //判断是否要装i物品
dp[i][j] = max(dp[i-1][j], dp[i - 1][j - w[i]] + v[i]);
}
return dp[N][C];
}
2.回溯
深度优先从根节点开始,首先定义当前的最大价值now_v和当前总重量now_w。如果从根节点到下一个子节点加上物品的重量后背包还有剩余,调用递归函数继续向下搜索。如果到达了最下面一层,然后进行最优价值与当前最大价值进行判断。进而获得最优解。
代码如下:
//回溯法
int LookBack(int i,int now_w,int now_v){
int j;
if(i>N){
if(now_v>mostvalue)
mostvalue=now_v;
}else{
if(now_w+w[i]<=C){
now_w+=w[i];
now_v+=v[i];
LookBack(i+1,now_w,now_v);
//从背包里取出物品进行回溯
now_w-=w[i];
now_v-=v[i];
}
LookBack(i+1,now_w,now_v);
}
return mostvalue;
}
3.分支限界
广度优先,定义当前的最大价值now_v和当前总重量now_w,建立约束条件,now_w<=c以及限界条件now_v+r[i+1]>bestvalue。其中r[i]是计算当前节点后面所有节点的重量总和用于剪枝。若符合则进行递归调用否则返回最优解。
代码如下:
//分支限界法
int BoundBack(int i,int now_w,int now_v){
int j;
if(i>N){
if(now_v>bestvalue)
bestvalue=now_v;
}else{
for(j=0;j<=1;j++){
now_v+=v[i]*j;
now_w+=w[i]*j;
if(now_w<=C&&now_v+r[i+1]>bestvalue)
BoundBack(i+1,now_w,now_v);
}
}
return bestvalue;
}
4.贪心
首先创建排序函数对物品的单价进行排序,大的优先,然后通过调用排序函数获得新的物品顺序,然后按照顺序用当前价值value+=v[i]来更新当前价值,用背包容量依次减去w[i],直到下一个物品重量大于剩余容量,最终获得最优解。
代码如下:
//贪心算法
void Sort(float w1[],float v1[]){
float temp1,temp2;
for(int i=1;i<=N;i++){
for(int j=i;j<=C;j++){
temp1=v1[i]/w1[i];
temp2=v1[j]/w1[j];
if(temp1<temp2){
swap(w1[j],w1[i]);
swap(v1[j],v1[i]);
}
}
}
}
int tanxin(int m){
float w1[N],v1[N];
int value;
for(int i=1;i<=N;i++)
{
v1[i]=rand()%5+1;
w1[i]=rand()%5+1;
cout<<"物品"<<i<<",价值"<<v1[i]<<",重量"<<w1[i]<<endl;
}
Sort(w1,v1);
for(int i=1;i<=N;i++){
//cout<<"排序后的物品"<<i<<",价值"<<v1[i]<<",重量"<<w1[i]<<endl;
}
for(int i=1;i<=N;i++){
if(w1[i]<=m){
m-=w1[i];
value+=v1[i];
}else{
return value;
}
}
return value;
}
复杂度分析:
1.动态规划:时间复杂度:O(n*c),c为背包容量,嵌套两次for循环,从开始到结束最多需要执行n*c次。
空间复杂度:O(n*c),建立了一个dp[n][c]二维数组,空间占用为n*c。
2.回溯法:时间复杂度:O(2^n),使用了二叉树,共有n层,需要搜索2^n个节点,所以最多需要执行2^n次。
空间复杂度: O(2^n),二叉树有2^n个节点。
3.分支限界法:时间复杂度:O(n*2^n),最多需要遍历到第n层,还有限界函数O(n)判断,所以最多共n*2^n次。
空间复杂度:O(2^n),二叉树有2^n个节点。
4.贪心法:时间复杂度:O(n),一层for循环,执行了n次。
空间复杂度:O(n),排序函数将n个物品进行了排序,占用了包含n个数据的一维数组。