1 问题描述
有n个物品,它们有各自的重量和价值,现有一给定最大载重的背包,如何让背包里装入的物品具有最大的价值总和而又不超过最大载重?
比如:
商品 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
重量 | 1 | 2 | 3 | 4 | 5 |
价值 | 3 | 4 | 5 | 6 | 7 |
假设背包最大载重为8,可以求得以下两种最优方案
商品1 商品2 商品5
总价值:14
总重量:8 <= 最大载重
商品1 商品3 商品4
总价值:14
总重量:8 <= 最大载重
2 基本原理
定义数组V[0…n][0…maxWeight]
其中,n表示商品的总数,maxWeight表示背包的最大载重量。
定义V[i][w]为包含前i件商品且最大载重为w时的子问题的最优方案的总价值。
只需要讨论当前商品i是否可能在当前子问题的最优方案中便可以得到以下递推式:
若 weigth[i] > w, 即当前商品i太重了,故不可能出现在当前子问题的最优方案里,因此
V[i][w] = V[i-1][w]
//若 weigth[i] <= w ,则说明当前商品i可能在当前子问题的最优方案中,因此需要做比较选择,故有
V[i][w] = max( V[i-1][w] , V[i-1][w-weigth[i]] + value[i] )
3代码实现
利用上面的递推式便能使用动态规划算法解决问题!
c++代码如下:
//Albert Chow 2020.08.30
# include <iostream>
using namespace std;
/***********************************************************************************/
//用于动态创建二维数组
//利用了模板
template<typename T>
T** buildArray2D(int row, int column)
{
T* p = new T[row * column]; //开辟一片临时空间
T** a = new T * [row]; //创建行指针
for (int i = 0; i < row; i++)
{
a[i] = p + column * i; //给行指针赋值
}
return a;
};
template<typename T>
void deleteArray2D(T** a)
{
delete[] * a;
delete[] a;
}
/***********************************************************************************/
//0-1背包问题算法
//简单版本,用于理解算法
int knapsack_0_1_v1(int n, int* weigth, int* value, int maxWeight)
{
int** V = buildArray2D<int>(n+1, maxWeight+1); //动态创建一个二维数组
//其中A[i][w] 表示 包含前i件商品,最大重量为w时的子问题的最大价值
//因此可分最优方案包不包含当前商品i而得到如下的递推式:
//若 weigth[i] > w, 即当前商品i太重了,故不可能出现在当前子问题的最优方案里,因此
//V[i][w] = V[i-1][w]
//若 weigth[i] <= w ,则说明当前商品i可能在当前子问题的最优方案中,因此需要做比较选择
//V[i][w] = max( V[i-1][w] , V[i-1][w-weigth[i]] + value[i] )
//根据以上的递推式设计自底向上的dp算法如下:
int i,w;
//对V做下初始化
for (w = 0; w <= maxWeight; w++)
V[0][w] = 0;
for (i = 1; i <= n; i++)
V[i][0] = 0;
for(i = 1;i<=n;i++)
for (w = 1; w <= maxWeight; w++)
{
if (weigth[i -1] > w) //当前商品大于背包容量时的递推式
{
V[i][w] = V[i - 1][w];
}
else //当前商品可能出现在最优解里面的递推式
{
//V[i][w] = max( V[i-1][w] , V[i-1][w-weigth[i]] + value[i] )
//注意cpp中数组下标从0开始
if ( V[i - 1][w] > (V[i - 1][w - weigth[i - 1]] + value[i - 1]) )
{
V[i][w] = V[i - 1][w];
}
else
{
V[i][w] = V[i - 1][w - weigth[i - 1]] + value[i - 1];
}
}
}
int res = V[n][maxWeight];
deleteArray2D<int>(V);//手动释放内存
return res;
}
//原理同v1,只是在内存上作了优化
int knapsack_0_1_v2(int n, int* weigth, int* value, int maxWeight)
{
int* V = new int[maxWeight + 1];
int i, w;
//对V做下初始化
for (w = 0; w <= maxWeight; w++)
V[w] = 0;
for (i = 1; i <= n; i++)
//为了防止数据覆盖,这里w采用逆序
//由于w < weigth[i - 1]时 无需更新数组,因此w的遍历只到weigth[i - 1]
for (w = maxWeight; w >= weigth[i - 1]; w--)
{
if (V[w - weigth[i - 1]] + value[i - 1] > V[w])
{
V[w] = V[w - weigth[i - 1]] + value[i - 1];
}
}
int res = V[maxWeight];
delete[] V;
return res;
}
//定义一个结构体,用于函数返回多个变量
typedef struct
{
int val;
bool** choice;
} RES;
//原理同v1,只是为重现最优解做了一些修改
RES knapsack_0_1_v3(int n, int* weigth, int* value, int maxWeight)
{
int** V = buildArray2D<int>(n + 1, maxWeight + 1); //动态创建一个二维数组
bool **choice = buildArray2D<bool>(n, maxWeight);
int i, w;
//对V做下初始化
for (w = 0; w <= maxWeight; w++)
V[0][w] = 0;
for (i = 1; i <= n; i++)
V[i][0] = 0;
for (i = 1; i <= n; i++)
for (w = 1; w <= maxWeight; w++)
{
if (weigth[i - 1] > w) //当前商品大于背包容量时的递推式
{
V[i][w] = V[i - 1][w];
choice[i -1][w -1] = 0; //注意c语言下标从0开始
}
else //当前商品可能出现在最优解里面的递推式
{
//V[i][w] = max( V[i-1][w] , V[i-1][w-weigth[i]] + value[i] )
//注意cpp中数组下标从0开始
if (V[i - 1][w] > (V[i - 1][w - weigth[i - 1]] + value[i - 1]))
{
V[i][w] = V[i - 1][w];
choice[i - 1][w - 1] = 0;
}
else
{
V[i][w] = V[i - 1][w - weigth[i - 1]] + value[i - 1];
choice[i - 1][w - 1] = 1;
}
}
}
RES res;
res.val = V[n][maxWeight];
res.choice = choice;
//手动释放内存
deleteArray2D<int>(V);
return res;
}
//原理同v2,只是为重现最优解做了一些修改,注意多与v3对比
RES knapsack_0_1_v4(int n, int* weigth, int* value, int maxWeight)
{
int* V = new int[maxWeight + 1];
bool** choice = buildArray2D<bool>(n, maxWeight);
int i, w;
//对V做下初始化
for (w = 0; w <= maxWeight; w++)
V[w] = 0;
for (i = 1; i <= n; i++)
{//为了防止数据覆盖,这里w采用逆序
//由于w < weigth[i - 1]时 无需更新数组,因此w的遍历只到weigth[i - 1]
for (w = maxWeight; w >= weigth[i - 1]; w--)
{
if (V[w - weigth[i - 1]] + value[i - 1] > V[w])
{
V[w] = V[w - weigth[i - 1]] + value[i - 1];
choice[i - 1][w - 1] = 1;
}
else
choice[i - 1][w - 1] = 0;
}
for (w = weigth[i - 1]-1; w>=1; w--) //补零
choice[i -1][w -1] = 0;
}
RES res;
res.val = V[maxWeight];
res.choice = choice;
delete[] V;
return res;
}
//仿照算法导论p225的程序得到的
void printKnapsack(bool** choice, int *weight,int i,int w)
{
if (i == 0 || w == 0)
return;
if (choice[i -1][w -1] == 1)
{
printKnapsack(choice, weight, i - 1, w - weight[i - 1] );
cout << "商品" << i << " ";
}
else
{
printKnapsack(choice, weight, i - 1, w);
}
}
int main()
{
//可以自己修改数据验证算法是否正确
// 1 2 3 4 5 6 7 8 9 10
int weight[] = {1, 2, 3, 4, 5};
int value[] = {3, 4, 5, 6, 7};
int n = sizeof(weight)/sizeof(weight[0]);
int maxWeight = 8;
/************************************************************************/
int r1 = knapsack_0_1_v1(n,weight, value, maxWeight);
cout << r1 << endl;
int r2 = knapsack_0_1_v2(n, weight, value, maxWeight);
cout << r2 << endl;
RES r3 = knapsack_0_1_v3(n, weight, value, maxWeight);
cout << r3.val << endl;
printKnapsack(r3.choice, weight, n, maxWeight);
deleteArray2D<bool>(r3.choice);
cout << endl;
RES r4 = knapsack_0_1_v4(n, weight, value, maxWeight);
cout << r4.val << endl;
printKnapsack(r4.choice, weight, n, maxWeight);
deleteArray2D<bool>(r4.choice);
cout << endl;
return 0;
}