01揹包 学习笔记

学习揹包问题之前首先要了解动态规划的思想

动态规划

作者:王勐
链接:https://www.zhihu.com/question/23995189/answer/35429905
来源:知乎
著作权归作者所有,转载请联系作者获得授权。


动态规划是通过拆分问题(不管问题有没有时间轴的概念,抽象成时间先后关系)

  • 状态的定义
  • 状态转移方程的定义
  • 阶段是状态的容器

问题的分类由阶段中状态的转移方式决定的:

  • 每个阶段只有一个状态->递推;
  • 每个阶段的最优状态都是由上一个阶段的最优状态得到的->贪心;
  • 每个阶段的最优状态是由之前所有阶段的状态的组合得到的->搜索;
  • 每个阶段的最优状态可以从之前某个(不一定是上一个)阶段的某个或某些状态直接得到而不管之前这个状态是如何得到的->动态规划。
    每个阶段的最优状态可以从之前某个阶段的某个或某些状态直接得到,这个性质叫做最优子结构
    而不管之前这个状态是如何得到的,这个性质叫做无后效性

01揹包问题:有的问题往往需要对每个阶段的所有状态都算出一个最优值,然后根据这些最优值再来找最优状态。


01揹包问题

揹包问题——“01揹包”详解及实现(包含揹包中具体物品的求解)


状态转移方程F[i][j]=max{F[i-1][j] , F[i-1][j-W[i]]+V[i]}
不管前i个物品的状态 xxxxx ↑ xxxxxxx xxxxxx ↑ xxxxxxx
xxxxx xxxxxxx xxxx 第i个物品未选 xxx 第i个物品已选

揹包问题数据表:

编号i 1 2 3 4 5 6
体积W 2 3 1 4 6 5
价值V 5 6 5 1 19 7

揹包中得到的最大价值表F[i][j]:(i标号,C承重,W体积,V价值)

W V i\C 0 1 2 3 4 5 6 7 8 9 10
0 0 0 0 0 0 0 0 0 0 0 0
2 5 1 0 0 5☆ 5☆ 5☆ 5☆ 5☆ 5☆ 5☆ 5☆ 5☆
3 6 2√ 0 0 5 6☆ 6☆ 11☆ 11☆ 11☆ 11☆ 11☆ 11☆
1 5 3√ 0 5☆ 5 10☆ 11☆ 11☆ 16☆ 16☆ 16☆ 16☆ 16☆
4 1 4 0 5 5 10 11 11 16 16 16 16 17☆
6 19 5√ 0 5 5 10 11 11 19☆ 24☆ 24☆ 29☆ 30☆
5 7 6 0 5 5 10 11 11 19 24 24 29 30
  • →从左向右一行一行填表。
  • 每一列相当于一个阶段。有两种状态:选中,未选中。
  • 单元中状态的maxValue取决于:max{对应上一行F[i-1][j](未选中),上一行中F[i-1][j-W(i)] (选中)}
  • ☆代表Path[i][j]=1,即存储节点是否更新(即不管前i个物品存放关系,第i物品是否选中存放。选中是为1,不是为0)
    注:不是表示最终第i个是否被选中即可,只是其中各个阶段中是否被选中

代码实现(java):

public class package01 {
    //cow代表物品编号i;column代表承重量j
    int[][] Table;//存储各节点价值
    int[][] Path;//存储节点是否更新(即不管前i个物品存放关系,第i物品是否选中存放。选中是为1,不是为0)
    int Weight[], Value[];//物品重量,物品价值
    int nLen, nCapacity;//物品数量,揹包最大承重量
    //构造函数,初始化
    public package01(int W[], int V[], int N, int C){
        Weight = W;
        Value = V;
        nLen = N;
        nCapacity = C;
        Table = new int[N+1][C+1];
        Path = new int [N+1][C+1];
    }
    /*
     * 状态转移方程F[i][j]=max{F[i-1][j] , F[i-1][j-C[i]]+W[i]}
     */
    int progressing(boolean isPrint){
         for(int i = 1; i <= nLen; i++)  
            {  
                for(int j = 1; j <= nCapacity; j++)  
                {  
                    Table[i][j] = Table[i-1][j];  
                    Path[i][j] = 0;  
                    if(j >= Weight[i-1] && Table[i][j] < Table[i-1][j-Weight[i-1]]+Value[i-1])  
                    {  
                        Table[i][j] = Table[i-1][j-Weight[i-1]]+Value[i-1];  
                        Path[i][j] = 1;  
                    }  
                }  
            } 
         if(isPrint){       
             System.out.print("i:V:F\n");
             Print(nLen, nCapacity);             
         }

        return Table[nLen][nCapacity];

    }
    //打印选中节点(递归)
    void Print(int cow, int column){
        if(cow>0 && column>0){
            if(Path[cow][column] == 1){
                Print(cow-1, column-Weight[cow-1]);//不管有没有选中cow都要减1
                System.out.print(cow +":" + Value[cow-1] + ":" + Table[cow][column] + "\n");
            }
            else {
                Print(cow-1, column);
            }
        }   
    }

    /*
    //打印选中节点(逆序)
    void Print(int cow, int column){
        int i = cow;
        int j = column;
        while(i > 0 && j > 0)  
        {  
            if(Path[i][j] == 1)  
            {  
                System.out.print(i +":" + Value[i-1] + ":" + Table[i][j] + "\n");
                j -= Weight[i-1];  
            }  

            i--; 
        }
    }  
    */

}

时间及空间复杂度均为O(VN)


压缩空间,以降低空间复杂度
时间复杂度为O(VN),空间复杂度将为O(V)
F[i][j]只与F[i-1][j]和F[i-1][j-C[i]]有关,即只和i-1时刻状态有关,
只需要用一维数组F[]来保存i-1时的状态F[]。
状态转移方程F[j]=max{F[j] , F[j-W[i]]+V[i]}

代码实现:

public class package01_compress {
    //cow代表物品编号i;column代表承重量j
    int[] Table;//存储各节点价值,一维数组压缩空间复杂度
    int[][] Path;//存储节点是否更新(即不管前i个物品存放关系,第i物品是否选中存放。选中是为1,不是为0)
                  //注:不是表示最终第i个是否被选中即可,只是其中各个阶段中是否被选中
    int Weight[], Value[];//物品重量,物品价值
    int nLen, nCapacity;//物品数量,揹包最大承重量
    //构造函数,初始化
    public package01_compress(int W[], int V[], int N, int C){
        Weight = W;
        Value = V;
        nLen = N;
        nCapacity = C;
        Table = new int[C+1];
        Path = new int [N+1][C+1];
    }
    /* 
     * F[i][j]只与F[i-1][j]和F[i-1][j-C[i]]有关,即只和i-1时刻状态有关,
     * 只需要用一维数组F[]来保存i-1时的状态F[]
     */
    int progressing(boolean isPrint){
        for(int i = 0; i < nLen; i++)  
        {  
            //逆序遍历(正序遍历时,会收集前面某个阶段节点的信息,但是可能已经被更新了,导致错误)
            for(int j = nCapacity; j >= Weight[i]; j--)  
            {  
                Path[i+1][j] = 0;  
                if(Table[j] < Table[j-Weight[i]]+Value[i])  
                {  
                    Table[j] = Table[j-Weight[i]]+Value[i];  
                    Path[i+1][j] = 1;  
                }  
            }     
        } 
         if(isPrint){       
             System.out.print("i:V:F\n");
             Print(nLen, nCapacity);             
         }

        return Table[nCapacity];
    }
    //打印选中节点(递归)
        void Print(int cow, int column){
            if(cow>0 && column>0){
                if(Path[cow][column] == 1){
                    Print(cow-1, column-Weight[cow-1]);//不管有没有选中cow都要减1
                    System.out.print(cow +":" + Value[cow-1] + ":" + Table[column] + "\n");
                }
                else {
                    Print(cow-1, column);
                }
            }   
        }
}

测试代码:

public class test {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        int W[] = {2,3,1,4,6,5};  
        int V[] =  {5,6,5,1,19,7};  
        int C = 10;  
       // package01 pa = new package01(W, V, W.length, C);
        package01_compress pa =new package01_compress(W, V, W.length, C);
        System.out.println("maxValue:" + pa.progressing(true));
    }

}

结果:

i:V:F
2:6:10
3:5:11
5:19:30
maxValue:30
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章