下面讲解一下动态规划中的经典例题---背包问题,其中背包问题又分为0-1背包问题、物品无限背包问题和多重背包问题。
做动态规划习题的时候,最重要的求解过程是列表格,将问题分解为众多子问题。
0-1背包问题
1 | 2 | 3 | 4 | |
v | 2 | 3 | 4 | 5 |
w | 3 | 4 | 5 | 6 |
其中capacity = 8
将背包问题抽象化(X1,X2,...,Xn,其中Xi取0或1),vi表示第i个物品的体积,wi表示第i个物品的重量。
建立模型,即求max(w1X1+...+wnXn)
约束条件,v1X1+...+vnXn<=capacity
定义f(i,j):当前背包容量为j,前i个物品的最佳组合
1.包的剩余容量小于当前物品的体积,装不上, f(i,j)=f(i-1,j)
2.包的剩余容量大于等于当前物品的体积,但是装上当前物品也不一定是最优解,f(i,j)=max(f(i-1,j),f(i-1,j-v[i])+w[i])
初始化,当j==0或i==0时f[i][j]=0
i/j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | ||||||||
2 | 0 | ||||||||
3 | 0 | ||||||||
4 | 0 |
void FindMax(int n,int capacity)//动态规划填表
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=capacity;j++)
{
if(j<v[i]) //包装不进
f[i][j]=f[i-1][j];
else //能装
{
if(f[i-1][j]>f[i-1][j-v[i]]+w[i])//不装当前物品重量大
f[i][j]=f[i-1][j];
else
f[i][j]=f[i-1][j-v[i]]+w[i];//装前i-1种物品的最优解与当前物品之和的重量大
}
}
}
}
i/j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
3 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 9 |
4 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 10 |
即f(n,capacity)即为最优解
表格填完,动态规划的时间复杂度就是O(n*capacity)
此时还不知道解的组成,可通过回溯法求出解
void FindWhat(int i,int j)
{
if(i>0)
{
if(f[i][j]==f[i-1][j])//未装入第i个物品
{
FindWhat(i-1,j);
vis[i]=0;
}
else if(f[i][j]==f[i-1][j-v[i]]+w[i])//装入了第i个物品
{
FindWhat(i-1,j-v[i]);
vis[i]=1;
}
}
}
物品无限背包问题