http://www.stevenwang.name/dpa-knapsack-problem-31001.html
一、动态规划算法介绍
动态规划算法(Dynamic Programming Algorithm, DPA)与分治法类似,其基本思想是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划法求解的问题,经分解得到的子问题往往不是互相独立的,若用分治法解这类问题,则分解得到的子问题数目太多,以至于最后解决原问题需要耗费过多的时间。在用分治法求解时,有些子问题被重复计算了许多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算。为了达到此目的,可以用一个表来记录所有已解决的子问题的答案。这就是动态规划法的基本思想。
动态规划算法使用于解最优化问题。通常可按以下4个步骤设计:
1、找出最优解的性质,并刻画其结构特征;
2、递归地定义最优值;
3、以自底向上的方式计算出最优值;
4、根据计算最优值时得到的信息,构造最优解。
步骤1~3是动态规划算法的基本步骤。在只需要求出最优值的情形,步骤4可以省去。若需要求出问题的最优解,则必须执行步骤4。此时,在步骤3中计算最优值时,通常需记录更多的信息,以便在步骤4中,根据所记录的信息,快速构造出一个最优解。
二、0-1背包问题
【问题描述】:给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包容量为c。问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大。在选择装入背包的物品时,对每种物品i只有两种选择,即装入背包或不装入背包。不能将物品i装入背包多次,也不能只装入部分的物品i。因此,该问题称为0-1背包问题。
【算法分析】:0-1背包问题的最优子结构,设(y1,y2,...,yn)是所给0-1背包问题的一个最优解,则(y2,y3,...,yn)是经过一次选择后的0-1背包问题的最优解。0-1背包问题的递归关系,设当前子问题的最优值为m(i,j),即m(i,j)使背包容量为j,可选择物品为i,i+1,...,n时0-1背包问题的最优值。由0-1背包问题的最优子结构性质,可以建立计算m(i,j)的递归式:
当i=n时,若j>=wn,则m(i,j)=vn;若0<=j<wn,则m(i,j)=0。
当i<n时,若j>=wi,则m(i,j)=max{m(i+1,j),m(i+1,j-wi)+vi};若0<=j<wi,则m(i,j)=m(i+1,j)。
【算法实现】Knapsack算法的C++实现如下:
(其中背包信息从文件中读取,点击进入该文件下载界面)
#include "iostream"
using namespace std;
#define n 50 //物品的数量
//物体重量、收益、背包容量
int weight[n], profit[n], contain, x[n], **m;
//从文件中读取背包信息
int read_infor()
{
FILE *fp;
int i;
if ((fp=fopen("knapsack.txt","r"))==NULL)
{
printf("The file is not found!");
return 0;
}
//读取物体收益信息
for(i = 0;i < n;i++)
{
fscanf(fp, "%d", &profit[i]);
}
//读取物体重量信息
for(i = 0;i < n;i++)
{
fscanf(fp, "%d", &weight[i]);
}
//读取背包容量
fscanf(fp, "%d", &contain);
fclose(fp);
return 1;
}
void Knapsack()
{
int jMax = min(weight[n - 1] - 1, contain);
int i, j;
for(j = 0;j <= jMax;j++)
{
m[n - 1][j] = 0;
}
for(j = weight[n - 1];j <= contain;j++)
{
m[n - 1][j] = profit[n - 1];
}
for(i = n - 2;i > 0;i--)
{
jMax = min(weight[i] - 1, contain);
for(j = 0;j <= jMax;j++)
{
m[i][j] = m[i + 1][j];
}
for(j = weight[i];j <= contain;j++)
{
m[i][j] = max(m[i + 1][j], m[i + 1][j - weight[i]] + profit[i]);
}
}
m[0][contain] = m[1][contain];
if(contain >= weight[0])
{
m[0][contain] = max(m[1][contain], m[1][contain - weight[0]] + profit[0]);
}
}
void Traceback()
{
int c = contain;
for(int i = 0;i < n - 1;i++)
{
if(m[i][c] == m[i + 1][c])
{
x[i] = 0;
}
else
{
x[i] = 1;
c -= weight[i];
}
}
x[n - 1] = (m[n - 1][c]) ? 1 : 0;
}
void main()
{
int i, sumWeight = 0;
if(read_infor())
{
m = (int**)malloc(n * sizeof(int*));
for(i = 0;i < n;i++)
{
m[i] = (int*)malloc(contain * sizeof(int));
}
Knapsack();
Traceback();
}
printf("The choice is: \n");
for(i = 0;i < n;i++)
{
sumWeight += weight[i] * x[i];
if(i != 0 && i % 5 == 0)
{
printf(" ");
}
printf("%1d", x[i]);
}
printf("\nThe maximum profit is: %d.", m[1][contain]);
printf("\nThe knapsack weight is %d.\n", sumWeight);
scanf("%d", &i);
for(i = 0;i < n;i++)
{
free(m[i]);
}
free(m);
}
【算法复杂度分析】:从计算m(i,j)的递归式容易看出,上述算法Knapsack需要O(nc)计算时间,而Traceback需要O(n)计算时间。其中,算法Knapsack还存在可以改进的地方,改进算法将在后续的文章中给出。