动态规划: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

原文地址:https://www.cnblogs.com/findumars/p/2330648.html