0-1背包问题
背包问题(Knapsack Problem)
问题描述:一个旅行者准备随身携带一个背包,可以放入背包的物品有 n n n种,物品 j j j的重量和价值分别为 w j , v j , j = 1 , 2 , … , n w_j,v_j,j=1,2,…,n wj,vj,j=1,2,…,n,如果背包的最大重量限制是 b b b,怎样选择放入背包物品以使得背包的价值最大?
这是一个组合优化问题,设
x
j
x_j
xj表示装入背包的第
j
j
j种物品的数量,那么目标函数与约束条件是:
目标函数
m
a
x
∑
j
=
1
n
v
j
x
j
约束条件
{
∑
j
=
1
n
w
j
x
j
≤
b
x
j
∈
N
\text{目标函数}\space max\sum_{j=1}^nv_jx_j\\ \text{约束条件}\space \begin{cases} \sum_{j=1}^nw_jx_j\le b\\ x_j\in \text{N} \end{cases}
目标函数 maxj=1∑nvjxj约束条件 {∑j=1nwjxj≤bxj∈N
当
x
j
=
0
或
1
x_j=0或1
xj=0或1时的背包问题称之为0-1背包问题。
下面我们讨论其解法。
这个问题是否能由转成子问题的解的组合呢?首先我们可以知道已知条件: n n n个已知重量与其价值的物品,以及背包的最大容量 b b b。假设最大价值为 F F F,对于一个物品而言,它要么装进背包,要么不装进背包。于是我们可以得到两种情况一种是一个物品装进背包以及其不装进,比较其大小就得到了最大价值。当然如果背包的容量已经装不下该物品,则可以认为最优解是不装该物品。
我们假设这个物品是最后一个物品。于是得到了以下的递推形式:
F
(
n
,
b
)
=
{
m
a
x
{
F
(
n
−
1
,
b
)
,
F
(
n
−
1
,
b
−
w
n
)
+
v
n
}
b
≥
w
n
F
(
n
−
1
,
b
)
F(n,b)=\begin{cases}max\{F(n-1,b),F(n-1,b-w_n)+v_n\} \space b\ge w_n\\ F(n-1,b) \end{cases}
F(n,b)={max{F(n−1,b),F(n−1,b−wn)+vn} b≥wnF(n−1,b)
可知这个问题满足最优子结构的特征。可以使用动态规划求解。为了保存最大价值我们用一个二维数组来保存对应的
F
(
n
,
b
)
F(n,b)
F(n,b),为了构造最优解我们通过一个二维数组来保存每次的比较结果
S
(
n
,
b
)
S(n,b)
S(n,b),其定义如下:
S
(
n
,
b
)
=
{
1
b
≥
w
n
且
F
(
n
−
1
,
b
)
≤
F
(
n
−
1
,
b
−
w
n
)
+
v
n
0
其
他
S(n,b) = \begin{cases} 1\space\space\space\space b\ge w_n且F(n-1,b)\le F(n-1,b-w_n)+v_n\\ 0\space\space\space\space 其他 \end{cases}
S(n,b)={1 b≥wn且F(n−1,b)≤F(n−1,b−wn)+vn0 其他
我们通过
S
(
n
,
b
)
S(n,b)
S(n,b)就能构造出最优解。
具体伪代码如下:
MakeBest(S,n,b,w)
ans[1……n] = {0,……,0}//最优解
while(b>=0&&n>=0)
ans[n] = S(n,b)
b-=ans[n]*W[n]
n--;
return ans
/**
* 背包问题
* */
#include <iostream>
#include <vector>
using namespace std;
pair<int,vector<int>> KP(vector<int> &w,vector<int> &v,int n,int b){
vector<vector<int>> F(b+1,vector<int>(n+1,0));
vector<vector<int>> S(b+1,vector<int>(n+1,0));
vector<int> ans(n,0);
for (int i = 1; i < b+1; i++)
{
for (int j = 1; j < n+1; j++)
{
if(i-w[j-1]<0) {F[i][j] = F[i][j-1];continue;}
if(F[i][j-1]<F[i-w[j-1]][j-1]+v[j-1]){
F[i][j] = F[i-w[j-1]][j-1]+v[j-1];
S[i][j] = 1;
}
else F[i][j] = F[i][j-1];
}
}
int ANS = F[b][n];
//打印中间过程
for (int i = 1; i < n+1; i++)
{
for (int j = 1; j < b+1; j++)
{
cout<<F[j][i]<<",";
}
cout<<endl;
}
//构造最优解
while(n>=1&&b>=1){
ans[n-1] = S[b][n];
b-=ans[n-1]*w[n-1];
n--;
}
return make_pair(ANS,ans);
}
int main(){
int n = 4,b = 10;
vector<int> w = {2,3,4,7};
vector<int> v = {1,3,5,9};
auto ans = KP(w,v,n,b);
cout<<ans.first<<endl;
for(auto x : ans.second){
cout<<x<<",";
}
system("pause");
}
Reference
《算法设计与分析》第二版 屈婉玲,第三章3.3