背包问题再理解

菜鸟都能理解的0-1背包问题的空间优化

如果你不知道什么叫做0-1背包问题,下面是0-1背包问题的简单描述

假设有n件物品

每件物品的体积为w1, w2……wn

   相对应的价值为 v1, v2.……vn。

01背包是在n件物品取出若干件放在空间为total_weight的背包里,使得背包的总体积最大

关于0-1背包问题没有优化版本,请看

#include 
   
   
    
    
#define MAX_NUM 5
#define MAX_WEIGHT 10
using namespace std;

//动态规划求解
int zero_one_pack(int total_weight, int w[], int v[], int flag[], int n) {
  int c[MAX_NUM+1][MAX_WEIGHT+1] = {0}; //c[i][j]表示前i个物体放入容量为j的背包获得的最大价值
  // 
    
    状态转移方程:c[i][j] = max{c[i-1][j], c[i-1][j-w[i]]+v[i]}
  //状态转移方程的解释:第i件物品要么放,要么不放
  //                 如果第i件物品不放的话,就相当于求前i-1件物体放入容量为j的背包获得的最大价值
  //                 如果第i件物品放进去的话,就相当于求前i-1件物体放入容量为j-w[i]的背包获得的最大价值
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= total_weight; j++) {
      if (w[i] > j) {
        // 说明第i件物品大于背包的重量,放不进去
        c[i][j] = c[i-1][j];
      } else {
        //说明第i件物品的重量小于背包的重量,所以可以选择第i件物品放还是不放
          if (c[i-1][j] > v[i]+c[i-1][j-w[i]]) {
            c[i][j] = c[i-1][j];
          }
          else {
            c[i][j] =  v[i] + c[i-1][j-w[i]];
          }
      }
    }
  }

  //下面求解哪个物品应该放进背包
  int i = n, j = total_weight;
  while (c[i][j] != 0) {
    if (c[i-1][j-w[i]]+v[i] == c[i][j]) {
      // 如果第i个物体在背包,那么显然去掉这个物品之后,前面i-1个物体在重量为j-w[i]的背包下价值是最大的
      flag[i] = 1;
      j -= w[i];
      //--i; 移到外面去
    }--i;
  }
  return c[n][total_weight];
}


int main() {
  int total_weight = 10;
  int w[4] = {0, 3, 4, 5};
  int v[4] = {0, 4, 5, 6};
  int flag[4]; //flag[i][j]表示在容量为j的时候是否将第i件物品放入背包
  int total_value = zero_one_pack(total_weight, w, v, flag, 3);
  cout << "需要放入的物品如下" << endl;
  for (int i = 1; i <= 3; i++) {
    if (flag[i] == 1)
      cout << i << "重量为" << w[i] << ", 价值为" << v[i] << endl;
  }
  cout << "总的价值为: " << total_value << endl;
  return 0;
}
#include 
    
    
     
     
#define MAX_NUM 5
#define MAX_WEIGHT 10
using namespace std;

//动态规划求解
int zero_one_pack(int total_weight, int w[], int v[], int flag[], int n) {
  int c[MAX_NUM+1][MAX_WEIGHT+1] = {0}; //c[i][j]表示前i个物体放入容量为j的背包获得的最大价值
  // 
     
     状态转移方程:c[i][j] = max{c[i-1][j], c[i-1][j-w[i]]+v[i]}
  //状态转移方程的解释:第i件物品要么放,要么不放
  //                 如果第i件物品不放的话,就相当于求前i-1件物体放入容量为j的背包获得的最大价值
  //                 如果第i件物品放进去的话,就相当于求前i-1件物体放入容量为j-w[i]的背包获得的最大价值
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= total_weight; j++) {
      if (w[i] > j) {
        // 说明第i件物品大于背包的重量,放不进去
        c[i][j] = c[i-1][j];
      } else {
        //说明第i件物品的重量小于背包的重量,所以可以选择第i件物品放还是不放
          if (c[i-1][j] > v[i]+c[i-1][j-w[i]]) {
            c[i][j] = c[i-1][j];
          }
          else {
            c[i][j] =  v[i] + c[i-1][j-w[i]];
          }
      }
    }
  }

  //下面求解哪个物品应该放进背包
  int i = n, j = total_weight;
  while (c[i][j] != 0) {
    if (c[i-1][j-w[i]]+v[i] == c[i][j]) {
      // 如果第i个物体在背包,那么显然去掉这个物品之后,前面i-1个物体在重量为j-w[i]的背包下价值是最大的
      flag[i] = 1;
      j -= w[i];
      //--i; 移到外面去
    }--i;
  }
  return c[n][total_weight];
}


int main() {
  int total_weight = 10;
  int w[4] = {0, 3, 4, 5};
  int v[4] = {0, 4, 5, 6};
  int flag[4]; //flag[i][j]表示在容量为j的时候是否将第i件物品放入背包
  int total_value = zero_one_pack(total_weight, w, v, flag, 3);
  cout << "需要放入的物品如下" << endl;
  for (int i = 1; i <= 3; i++) {
    if (flag[i] == 1)
      cout << i << "重量为" << w[i] << ", 价值为" << v[i] << endl;
  }
  cout << "总的价值为: " << total_value << endl;
  return 0;
}

    
    
   
   

上面的核心代码是下面这一段

  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= total_weight; j++) {
      if (w[i] > j) {
        c[i][j] = c[i-1][j];
      } else {
          if (c[i-1][j] > v[i]+c[i-1][j-w[i]]) {
            c[i][j] = c[i-1][j];
          }
          else {
            c[i][j] =  v[i] + c[i-1][j-w[i]];
          }
      }
    }
  }


注意到状态转移方程 c[i][j] = max{c[i-1][j], c[i-1][j-w[i]]+v[i]}

每一次c[i][j]改变的值只与c[i-1][x] {x:1...j}  【我的理解:x是范围是 1 到j】

有关c[i-1][x]前一次i循环保存下来的值

因此,可以将c缩减成一维数组

状态转移方程转换为 c[j] = max(c[j], c[j-w[i]]+v[i]);
并且,我们注意到状态转移方程,
每一次推导c[i][j]是通过c[i-1][j-w[i]]来推导的,而不是通过c[i][j-w[i]]
因此,j的
扫描顺序应该改成从大到小

否则,第i次求c数组,必然先求的c[j-w[i]]的值(即c[i][j-w[i]]),再求c[j](即c[i][j])的值

由于j递增,那么状态方程就成为下面这个样子了

c[i][j] = max(c[i-1][j], c[i][j-w[i]]+v[i])显然不符合题意


【我的理解:每一次推导c[i][j]是通过c[i-1][j-w[i]]来推导的  ,

                 意思是 在 j 容量下放i 与不放i ,那个价值比较大。

                 c[i-1][j-w[i]]  表示在j的容积下准备放 i 物品

 

j的扫描顺序应该改成从大到小,


所以,上面的代码变为




 for (int i = 1; i <= n; i++) {
    for (int j = total_weight; j >= 1; j--) {
      if (w[i] > j) {
        c[j] = c[j]; //表示第i次与第i-1次相等,这里因为c[j]本来就保存这上一次的值,所以这里不需变化        

                       //而二维数组的写法是 c[ i ] [ j ] = c [ i-1 ] [ j ] ; 保存上一次的值 
      } else {
           //说明第i件物品的重量小于背包的重量,所以可以选择第i件物品放还是不放
          if (c[j] > v[i]+c[j-w[i]]) {      // 二维数组:if (c[i-1][j] > v[i]+c[i-1][j-w[i]])
            c[j] = c[j];                       // 二维数组:c[i][j] = c[i-1][j]; 不放入 i
          }
          else {
            c[j] =  v[i] + c[j-w[i]];      //二维数组:c[i][j] = v[i] + c[i-1][j-w[i]]; 放入i
          }
      }
    }
  }








 
【我的理解:c[ j - w[i] ]     与   c[i-1][ j-w[i] ]  的区别是: 

                         首先是 "j",前者是处理逆容量,后者是处理当前容量。

                         后者表示当前容量下放入i 前i的价值(value),前者表示 容量 - i的容量(费用)的价值

                         后者 + v[i]     ,前者 + v[i]  ,两个都加上i的价值,

                          然后    

            后者:【当前容量下放入i 前i的价值(value)】 + v[i]【i的价值】     【当前容量的不放入i的价值】       【 compare 】

                   c[i-1][j] 与 v[i]+c[i-1][j-w[i]] 比较

            前者:【容量 - i占用的容量(费用)  最后在此基础上得出的价值】     + v[i]【i的价值】     【容量的价值】 【 compare 】

                   c[j]  与 v[i]+c[j-w[i]]  比较(意思是假设容量不包括i占用容量的时候的价值,这样再加上i的价值,这个时候再与 容量的价值 比较,

如果 小于的话,表示不放入i 更有价值;如果大于的话,表示放入i 更有价值。)

  c[j] - v[i] > c[j-w[i]]   放入i,说明容量不匹配,说明c[j] 价值很大,反之当前容量的价值很小

一个逆的比较,一个是顺的比较,两者的目的是一样的,都是为了绝对放不放i, 但是又有区别,区别在空间上发生了变化,实现

同样目标尝试逆的方法和顺的方法,可以发现有不同的效果。



 

最后我们可以做下优化,把不必要的语句去掉即可完成优化

for (int i = 1; i <= n; i++)
  for (int j = total_weight; j >= w[i]; j--)
    if (c[j] <= v[i] + c[j-w[i]])
      c[j] = v[i] + c[j-w[i]];


如此优美的代码简直无法想象!

注意,

空间优化版本最后是求解不出来最优解序列的,

但是能求出最优解,也就是最大价值



背包01 问题的 二维数组 和 空间优化后一维数组 的实现

//
//  main.cpp
//  oneOfbag01
//
//  Created by 瑛峰 on 14/12/22.
//  Copyright (c) 2014年 angran. All rights reserved.
//

#include 
   
   
    
    


/**
#define MAX_NUM 5
#define MAX_WEIGHT 10
using namespace std;

//动态规划求解
int zero_one_pack(int total_weight, int w[], int v[], int flag[], int n) {
    int c[MAX_NUM+1][MAX_WEIGHT+1] = {0}; //c[i][j]表示前i个物体放入容量为j的背包获得的最大价值
    // 
    
    状态转移方程:c[i][j] = max{c[i-1][j], c[i-1][j-w[i]]+v[i]}
    //状态转移方程的解释:第i件物品要么放,要么不放
    //                 如果第i件物品不放的话,就相当于求前i-1件物体放入容量为j的背包获得的最大价值
    //                 如果第i件物品放进去的话,就相当于求前i-1件物体放入容量为j-w[i]的背包获得的最大价值
    for (int i = 1; i <= n; i++) {
        
        std::cout <<"\n\n";
        
        for (int j = 1; j <= total_weight; j++) {
            if (w[i] > j) {
                // 说明第i件物品大于背包的重量,放不进去
                c[i][j] = c[i-1][j];
            } else {
                //说明第i件物品的重量小于背包的重量,所以可以选择第i件物品放还是不放
                if (c[i-1][j] > v[i]+c[i-1][j-w[i]]) {
                    c[i][j] = c[i-1][j];
                }
                else {
                    c[i][j] =  v[i] + c[i-1][j-w[i]];
                }
            }
            
             std::cout << "c["<
    
    <<"]["<
     
     
      
      <<"]的值" << c[i][j] <<"\n";
        }
    }
    
    //下面求解哪个物品应该放进背包
    int i = n, j = total_weight;
    while (c[i][j] != 0) {
        if (c[i-1][j-w[i]]+v[i] == c[i][j]) {
            // 如果第i个物体在背包,那么显然去掉这个物品之后,前面i-1个物体在重量为j-w[i]的背包下价值是最大的
            flag[i] = 1;
            j -= w[i]; //表示既然i在背包里,就要去掉i占用的容量。
            //--i; 移到外面去
        }--i;
    }
    
    
    
    return c[n][total_weight];//最后一个获得的时总价值
}


int main() {
    int total_weight = 10;
    int w[4] = {0, 3, 4, 5};
    int v[4] = {0, 4, 5, 6};
    int flag[4]; //flag[i][j]表示在容量为j的时候是否将第i件物品放入背包
    int total_value = zero_one_pack(total_weight, w, v, flag, 3);
    cout << "需要放入的物品如下" << endl;
    for (int i = 1; i <= 3; i++) {
        std::cout << "flag["<
      
      <<"]的值" << flag[i] <<"\n";
        if (flag[i] == 1)
            cout << "\t" << i << "\t重量为" << w[i] << ", 价值为" << v[i] << endl;
    }
    cout << "总的价值为: " << total_value << endl;
    return 0;
}
 
*/


using namespace std;
#define MAX_WEIGHT 10
int Search(int n,int total_weight,int w[],int v[],int flag[])
{
    int c[MAX_WEIGHT+1] = {0};
    for (int i = 1; i <= n; i++) {
     std::cout <<"\n\n";
        for (int j = total_weight; j >= 1; j--) {
            if (w[i] > j) {
                c[j] = c[j]; //表示第i次与第i-1次相等,这里因为c[j]本来就保存这上一次的值,所以这里不需变化
            } else {
                //说明第i件物品的重量小于背包的重量,所以可以选择第i件物品放还是不放
                if (c[j] > v[i]+c[j-w[i]]) {
                    c[j] = c[j];
                }
                else {
                    c[j] =  v[i] + c[j-w[i]];
                }
            }
             std::cout << "c["<
       
       
         <<"]的值" << c[j] <<"\n"; } } //下面求解哪个物品应该放进背包 // int i = n, j = total_weight; // while (c[j] != 0) { // if (c[j-w[i]]+v[i] == c[j]) { // // 如果第i个物体在背包,那么显然去掉这个物品之后,前面i-1个物体在重量为j-w[i]的背包下价值是最大的 // flag[i] = 1; // j -= w[i]; //表示既然i在背包里,就要去掉i占用的容量。 // //--i; 移到外面去 // }--i; // } return c[total_weight]; } int main(int argc, const char * argv[]) { // insert code here... std::cout << "Hello, World!\n"; int total_weight = 10; int n = 3; int w[4] = {0,3,4,5}; int v[4] = {0,4,5,6}; int flag[4]; // for (int i=0; i<=10; i++) { // c[i] = 0; // } int total_value = Search(n,total_weight,w,v,flag); // 空间优化版本最后是求解不出来最优解序列的, // 但是能求出最优解,也就是最大价值 // cout << "需要放入的物品如下" << endl; // for (int i = 1; i <= 3; i++) { // std::cout << "flag["< 
        <<"]的值" << flag[i] <<"\n"; // if (flag[i] == 1) // cout << "\t" << i << "\t重量为" << w[i] << ", 价值为" << v[i] << endl; // } cout << "总的价值为: " << total_value << endl; return 0; }  
       
     
     
   
   

空间优化的输出

c[10]的值4

c[9]的值4

c[8]的值4

c[7]的值4

c[6]的值4

c[5]的值4

c[4]的值4

c[3]的值4

c[2]的值0

c[1]的值0



c[10]的值9

c[9]的值9

c[8]的值9

c[7]的值9

c[6]的值5

c[5]的值5

c[4]的值5

c[3]的值4

c[2]的值0

c[1]的值0



c[10]的值11

c[9]的值11

c[8]的值10

c[7]的值9

c[6]的值6

c[5]的值6

c[4]的值5

c[3]的值4

c[2]的值0

c[1]的值0

总的价值为: 11



------------------------

二维数组的 输出

c[1][1]的值0

c[1][2]的值0

c[1][3]的值4

c[1][4]的值4

c[1][5]的值4

c[1][6]的值4

c[1][7]的值4

c[1][8]的值4

c[1][9]的值4

c[1][10]的值4



c[2][1]的值0

c[2][2]的值0

c[2][3]的值4

c[2][4]的值5

c[2][5]的值5

c[2][6]的值5

c[2][7]的值9

c[2][8]的值9

c[2][9]的值9

c[2][10]的值9



c[3][1]的值0

c[3][2]的值0

c[3][3]的值4

c[3][4]的值5

c[3][5]的值6

c[3][6]的值6

c[3][7]的值9

c[3][8]的值10

c[3][9]的值11

c[3][10]的值11

需要放入的物品如下

flag[1]的值32767

flag[2]的值1

2 重量为4,价值为5

flag[3]的值1

3 重量为5,价值为6

总的价值为: 11


--------------------------------------------

观察其特征: 结果其实是一样的,只不过 一维数组  是倒着输出。!








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值