// 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