zoukankan      html  css  js  c++  java
  • 动态规划:01背包问题(使用迭代方法,避免重复计算)

    // 15x2.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include <iostream.h>
    #include <stdlib.h>
    #include "dosmax.h"  // has max() and min()
    #include "make2db.h" // void Make2DArray(T ** &x, int rows, int cols)

    // 动态规划:0-1背包问题(使用迭代方法,避免重复计算)
    template<class T>
    void Knapsack(T p[], int w[], int c, int n, T** f)
    {
       // Compute f[i][y] for all i and y.
       // initialize f[n][]
       // 这已经相当于是cas d'arret了,直接比较c与最后一个物品的重量,取小的那个值。
       // w[n]-1太小,永远取不到,相当于是一个无效的值。
       int yMax = min(w[n]-1, c); // 取总容量c与最后一个物品价值-1的最小值,=3
       cout<< "w[n]="<<w[n]<< endl;
       cout<< "yMax="<<yMax<< endl;

       // 全是最底层的值,要分成两部分进行初始化,一部分是放不下最后一个物品,最优值为0
       // 另一部分放得下,初始化为最后一个物品的价值(至少为最后一个物品的价值w[n])
       // 数组第二维的坐标,也就是剩余容量最多等于10,所以只要计算从f[][0]到f[][10]就可以了。
       for (int y=0; y<=yMax; y++) // 初始化了4个值,f[5][0],f[5][1],f[5][2],f[5][3]=0
          f[n][y] = 0;
       for (y=w[n]; y<=c; y++) // 初始化了7个值,f[5][4],f[5][5],f[5][6],f[5][7],f[5][8],f[5][9],f[5][10]=6
          f[n][y] = p[n];
      
       // compute remaining f's
       // 有了最底层的全部值以后,就可以开始计算了:
       // i的值依次是:4, 3, 2。之前计算了最底层(第五层),现在开始要往上计算了:
       for (int i=n-1; i>1; i--) { // i=4..2,共3次,注意:这回是w从倒数第二个往前直到第二个。
          cout << "i=" << i << endl;
          // 计算yMax=放不下当前物品时候的价值
       // yMax的值依次是:4,5,1 (从倒数第二个物品开始计算,倒数第一个已经计算过了)
          yMax = min(w[i]-1, c); // 比较倒数第二个物品重量与c的容量,取小的那个值。剩余容量不会比w[i]-1还要小。
       // 这部分f[][]都放不下当前物品的重量,因此全部直接等于下一级的值(动态规划的值是自底向上的)
       // y的值依次是:0,1,2,3,4    --- f[4][0],f[4][1],f[4][2],f[4][3],f[4][4],在i的当前循环内还会把剩下所有的f[4][]都计算完,所以不必担心第二轮f[3][5]=f[4][5]
       //             0,1,2,3,4,5 --- f[3][0],f[3][1],f[3][2],f[3][3],f[3][4],f[3][5]
       //             0,1,2          --- f[2][0],f[2][1],f[2][2]
          for (int y=0; y<=yMax; y++) {
             cout << "  y=" << y ;
             f[i][y] = f[i+1][y]; // 关键时刻:如果f[i+1][y]已经算过,就直接取值了,不再重复计算
       }
       cout << endl;

       // 放得下当前物品的重量的时候,就要开始比较了:取大的那个值。
       // y的值依次是:5,6,7,8,9,10 --- f[4][5],f[4][6],f[4][7],f[4][8],f[4][9],f[4][10]
       //                              --- 比如 f[4][5]=max{f[5][5],f[5][0]+p[4]},
       //             6,7,8,9,10    --- f[3][6],f[3][7],f[3][8],f[3][9],f[3][10]
       //             2,3,4,5,6,7,8,9,10 --- f[2][2],f[2][3],f[2][4],f[2][5],f[2][6],f[2][7],f[2][8],f[2][9],f[2][10]
          for (y=w[i]; y<=c; y++) {
             cout << "  y=" << y ;
       // 关键时刻:如果f[i+1][y]或者f[i+1][y-w[i]]已经算过(因为都是下一层的数据),就直接取值了,不再重复计算
             f[i][y] = max(f[i+1][y], // 下一级,剩余容量不变
                           f[i+1][y-w[i]] + p[i]); // 下一级,但一部分容量在这级被分流掉了。  
          }
          cout << endl;
       }

       f[1][c] = f[2][c];
       if (c >= w[1])
          f[1][c] = max(f[1][c], f[2][c-w[1]] + p[1]);
    }

    // 回溯取得最优路径
    template<class T>
    void Traceback(T **f, int w[], int c, int n, int x[])
    {
       // Compute x for optimal filling.
       for (int i = 1; i < n; i++)
          if (f[i][c] == f[i+1][c])  // 判断:当前节点与它的左孩子节点的最优值是否相等。
             x[i] = 0;            // 如果相等,说明当前节点取值为0
          else {x[i] = 1;            // 如果不等,路径取值为1
                c -= w[i];}          // 同时总容量减去当前节点的重量,很巧妙
       x[n] = (f[n][c]) ? 1 : 0;     // 最后一个节点不能再跟它的“孩子”比较了,只要看它自己是否为0就可以了。
    }

    void main(void)
    {
       int p[6] = {0, 6, 3, 5, 4, 6}; // 物品价值
       int w[6] = {0, 2, 2, 6, 5, 4}; // 物品重量
       int x[6]; // 最优路径
       int **f;  // 二叉树所有节点的值
       int n = 5;  // 物品数量
       int c = 10; // 背包总容量
       Make2DArray(f, n+1, c+1); // 根据物品数量n和总容量c建立一个二维数组,大小为(n+1)*(c+1)
                                 // 首行值全部抛弃(为了保持下标统一),11列的值都有用
       Knapsack(p, w, c, n, f);  //
       cout << "Optimal value is: ";
       cout << f[1][c] << endl << endl;  // 最优值一定存在f[1][c]里(二叉树的顶点),也就是f[1][10]里(看图)。而不是f[1][n]里。

    // 打印所有中间值
    // 除了f[1][]含有最优值(抛弃其余不是最优值的f[1][]),剩下都要打印:
    // f[2][0],f[2][1],f[2][2],f[2][3],f[2][4],f[2][5],f[2][6],f[2][7],f[2][8],f[2][9],f[2][10]
    // f[3][0],etc.
    // f[4][0],etc.
    // f[5][0],etc.
       cout << "Rest of table is:" << endl;
       for (int i = 2; i <= n; i++) {
          for (int j = 0; j <= c; j++)
             cout << f[i][j] << ' ';
          cout << endl;
       }
       cout << endl;

    // 有了中间值以后,用它回溯得到最优路径:
       Traceback(f,w,c,n,x);
       for (i = 1; i <= n; i++)
          cout << x[i] << ' ';
       cout << endl;
    }

    // =============================make2db.h=====================================================

    #ifndef Make2DArray_
    #define Make2DArray_

    template <class T>
    void Make2DArray(T ** &x, int rows, int cols)
    {// Create a two-dimensional array.

       // create pointers for the rows
       x = new T * [rows];
         
       // get memory for each row
       for (int i = 0; i < rows; i++)
          x[i] = new T [cols];
    }

    #endif

  • 相关阅读:
    [转]jquery开发自定义的插件总结
    [转]net中哈希表的使用 Hashtable
    [转]C#编程总结(三)线程同步
    [转]大白话系列之C#委托与事件讲解(三)
    [书目20160706]成功销售实用经典10步骤(美国培训与发展协会实用经典10步系列)
    [转]backbone.js template()函数
    [转]ASP.NET MVC Dynamic Themes
    [转]C#网络编程(订立协议和发送文件)
    [转]C#网络编程(异步传输字符串)
    [转]C#网络编程(同步传输字符串)
  • 原文地址:https://www.cnblogs.com/findumars/p/2330648.html
Copyright © 2011-2022 走看看