【问题描述】
0-1背包问题:给定 n 种物品和一背包。物品 i 的重量是wi其价值为vi,背包的容量为c。问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大?
在选择装入背包的物品时,对每种物品 i 只有两种选择,即装人背包或不装入背包。不能将物品 i 装入背包多次,也不能只装入部分的物品 i 。因此,该问题称为0-1背包问题。
此问题的形式化描述是,给定c>0,wi>0,vi>0,1 ≤ i ≤ n,要求找出一个n元0-1向量(x1,x2,···,xn),xi ∈{0,1},1≤i<h,使得
∑
i
=
1
n
w
i
∗
x
i
≤
c
\sum_{i=1}^n{w~i~*x~i~}≤c
i=1∑nw i ∗x i ≤c,而且
∑
i
=
1
n
v
i
∗
x
i
\sum_{i=1}^n{v~i~*x~i~}
i=1∑nv i ∗x i 达到最大。因此,0-1背包问题是一个特殊的整数规划问题:
m
a
x
∑
i
=
1
n
v
i
∗
x
i
max\sum_{i=1}^n{v~i~*x~i~}
maxi=1∑nv i ∗x i
∑
i
=
1
n
w
i
∗
x
i
≤
c
\sum_{i=1}^n{w~i~*x~i~}≤c
i=1∑nw i ∗x i ≤c
x
i
∈
(
0
,
1
)
,
1
≤
i
≤
n
x~i~\in(0,1),1≤ i ≤ n
x i ∈(0,1),1≤i≤n
【算法分析】
1.最优子结构性质
0-1背包问题具有最优子结构性质。设(y1,y2,···,yn)是所给0-1背包问题的一个最优解,则(y2,y3,–·,yn)是下面相应子问题的一个最优解:
m
a
x
∑
i
=
2
n
v
i
∗
x
i
max\sum_{i=2}^n{v~i~*x~i~}
maxi=2∑nv i ∗x i
∑
i
=
2
n
w
i
∗
x
i
≤
c
−
w
i
∗
y
1
\sum_{i=2}^n{w~i~*x~i~}≤c-w~i~*y~1~
i=2∑nw i ∗x i ≤c−w i ∗y 1
x
i
∈
(
0
,
1
)
,
2
≤
i
≤
n
x~i~\in(0,1),2≤ i ≤ n
x i ∈(0,1),2≤i≤n
因若不然,设(z2,z3,···,zn)是上述子问题的一个最优解,而(y2,y3,··,yn)不是它的最优解。由此可知,
∑
i
=
2
n
v
i
∗
z
i
>
∑
i
=
2
n
v
i
∗
y
i
\sum_{i=2}^n{v~i~*z~i~}>\sum_{i=2}^n{v~i~*y~i~}
i=2∑nv i ∗z i >i=2∑nv i ∗y i ,且
w
1
∗
y
1
+
∑
i
=
2
n
w
i
∗
z
i
≤
c
w~1~*y~1~+\sum_{i=2}^n{w~i~*z~i~}≤c
w 1 ∗y 1 +i=2∑nw i ∗z i ≤c。因此,
v
1
∗
y
1
+
∑
i
=
2
n
v
i
∗
z
i
>
∑
i
=
1
n
v
i
∗
y
i
v~1~*y~1~+\sum_{i=2}^n{v~i~*z~i~}>\sum_{i=1}^n{v~i~*y~i~}
v 1 ∗y 1 +i=2∑nv i ∗z i >i=1∑nv i ∗y i
w
1
∗
y
1
+
∑
i
=
2
n
w
i
∗
z
i
≤
c
w~1~*y~1~+\sum_{i=2}^n{w~i~*z~i~}≤c
w 1 ∗y 1 +i=2∑nw i ∗z i ≤c
这说明(y1,z2,···,zn)是所给0-1背包问题的一个更优解,从而(y1,y2,···,yn)不是所给0-1背包问题的最优解。此为矛盾。
2.递归关系
设所给0-1背包问题的子问题
m
a
x
∑
k
=
i
n
v
k
∗
x
k
max\sum_{k=i}^n{v~k~ * x~k~}
maxk=i∑nv k ∗x k
m
a
x
∑
k
=
i
n
w
k
∗
x
k
≤
j
max\sum_{k=i}^n{w~k~ * x~k~}≤j
maxk=i∑nw k ∗x k ≤j
x
k
∈
(
0
,
1
)
,
i
≤
k
≤
n
x~k~\in(0,1),i≤ k ≤ n
x k ∈(0,1),i≤k≤n
的最优值为m(i,j),即m(i,j)是背包容量为j,可选择物品为i,i+1,···,n时0-1背包问题的最优值。由0-1背包问题的最优子结构性质,可以建立计算m(i,j)的递归式如下:
m
(
i
,
j
)
=
{
m
a
x
{
m
(
i
+
1
,
j
)
,
m
(
i
+
1
,
j
−
w
i
)
+
v
i
}
j
≥
w
i
m
(
i
+
1
,
j
)
0
≤
j
≤
w
i
m(i,j)=\left\{ \begin{array}{rcl} max\lbrace m(i+1,j),m(i+1,j-w~i~)+v~i~\rbrace & & { j≥w~i~}\\ m(i+1,j) & & {0≤j≤w~i~} \end{array} \right.
m(i,j)={max{m(i+1,j),m(i+1,j−w i )+v i }m(i+1,j)j≥w i 0≤j≤w i
m
(
n
,
j
)
=
{
v
n
j
≥
w
n
0
0
≤
j
≤
w
n
m(n,j)=\left\{ \begin{array}{rcl} v~n~ & & { j≥w~n~}\\ 0 & & {0≤j≤w~n~} \end{array} \right.
m(n,j)={v n 0j≥w n 0≤j≤w n
3.算法描述
基于以上讨论,当wi(1≤i≤n)为正整数时,用二维数组m[ ][ ]来存储m(i,j)的相应值,可设计解0-1背包问题的动态规划算法Knapsack(具体实现见【C++代码实现】)。
按上述算法Knapsack计算后,m[1 ][c ]给出所要求的0-1背包问题的最优值。相应的最优解可由算法Traceback计算如下。如果m[1 ][ c]=m[ 2][c ],则x1=0,否则x1=1,当x1=0时,由mm[ 2][c ]继续构造最优解。当x1=1时,由m[ 2][c -w1]继续构造最优解。依次类推,可构造出相应的最优解(x1,x2,···,xn)。
【C++代码实现】
//解0-1背包问题的动态规划算法
template<class Type>
void Knapsack(Type v, int w, int c, int n, Type** m)
{
int jMax = min(w[n] - 1, c);
for (int j = 0; j <= jMax; j++)m[n][j] = 0;
for (int j = w[n]; j <= c; j++)m[n][j] = v[n];
for (int i = n - 1; i > 1; i--) {
jMax = min(w[i] - 1, c);
for (int j = 0; j <= jMax; j++)m[i][j] = m[i + 1][j];
for (int j = w[i]; j <= c; j++)m[i][j] = max(m[i + 1][j], m[i + 1][j - w[i]] + v[i]);
}
m[1][c] = m[2][c];
if (c >= w[1])m[1][c] = max(m[1][c], m[2][c - w[1]] + v[1]);
}
//构造最优解
template<class Type>
void Traceback(Type** m, int w, int c, int n, int x)
{
for (int i = 1; i < n; i++)
if (m[i][c] == m[i + 1][c])x[i] = 0;
else { x[i] = 1; c -= w[i]; }
x[n] = (m[n][c]) ? 1 : 0;
}