动态规划算法求解0-1背包

之前我们介绍了如何用动态规划算法求解最长公共子序列问题,本文继续介绍如何用动态规划算法求解0-1背包问题,并使用C++进行代码实现。

1. 0-1背包问题

给定 n n n种物品和一背包。物品 i i i的体积是 w i w_i wi,其价值为 v i v_i vi,背包的容量为 C C C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?这个问题就是0-1背包问题,在运筹学中这属于一个特殊的整数规划问题, K n a p ( 1 , n , C ) Knap(1, n, C) Knap(1,n,C)定义如下:

max ⁡ ∑ i = 1 n v i x i v i > 0 \begin{matrix}\max{\sum_{i=1}^{n}v_ix_i} & v_i > 0\end{matrix} maxi=1nvixivi>0

{ ∑ i = 1 n w i x i ≤ C w i > 0 x i ∈ { 0 , 1 } 1 ≤ i ≤ n \left\{\begin{matrix} \sum_{i=1}^{n}w_ix_i \leq C & w_i > 0 \\ x_i \in \{0,1\} & 1 \leq i \leq n \end{matrix}\right. {i=1nwixiCxi{0,1}wi>01in

例如: w = ( w 1 , w 2 , w 3 ) = ( 2 , 3 , 4 ) w=(w_1, w_2, w_3)=(2, 3, 4) w=(w1,w2,w3)=(2,3,4) v = ( v 1 , v 2 , v 3 ) = ( 1 , 2 , 5 ) v=(v_1, v_2, v_3)=(1, 2, 5) v=(v1,v2,v3)=(1,2,5),求 K n a p ( 1 , 3 , 6 ) Knap(1, 3, 6) Knap(1,3,6)

  • x = ( 1 , 0 , 1 ) x = (1, 0, 1) x=(1,0,1)时, K n a p ( 1 , 3 , 6 ) = ( v 1 x 1 + v 2 x 2 + v 3 x 3 ) = 1 × 1 + 2 × 0 + 5 × 1 = 6 Knap(1, 3, 6) = (v_1x_1+v_2x_2+v_3x_3)=1 \times 1 + 2 \times 0 + 5 \times 1 = 6 Knap(1,3,6)=(v1x1+v2x2+v3x3)=1×1+2×0+5×1=6最大。

如果用穷举法求解,时间复杂度为 O ( n 2 n ) O(n2^n) O(n2n)。下面我们使用动态规划算法来解决这个问题。

2. 动态规划算法思路

2.1 0-1背包问题的子问题

设所给0-1背包问题的子问题记为 K n a p ( i , n , j ) Knap(i, n, j) Knap(i,n,j) j ≤ C j \leq C jC (假设 C C C w i w_i wi取整数),其定义为:
max ⁡ ∑ k = i n v k x k \max{\sum_{k=i}^{n}v_kx_k} maxk=invkxk

{ ∑ k = i n w k x k ≤ j x k ∈ { 0 , 1 } , i ≤ k ≤ n \left\{\begin{matrix} \sum_{k=i}^{n}w_kx_k \leq j \\ x_k \in \{0,1\}, i \leq k \leq n \end{matrix}\right. {k=inwkxkjxk{0,1},ikn

其中,子问题的背包容量 j j j在不断变化。令m(i, j)代表第 i … n i…n in个物体在背包容量为 j j j时的最大价值,可以推出如下递归式。

2.2 最优值的递归式

m ( i , j ) = { max ⁡ { 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{matrix} \max \{m(i + 1, j), m(i + 1, j - w_i) +v_i\} & j \geq w_i \\ m(i+1, j) & 0 \leq j < w_i \end{matrix}\right. m(i,j)={max{m(i+1,j),m(i+1,jwi)+vi}m(i+1,j)jwi0j<wi

说明:

  • j < w i j<w_i j<wi时,只有 x i = 0 x_i=0 xi=0,所以 m ( i , j ) = m ( i + 1 , j ) m(i,j)=m(i+1, j) m(i,j)=m(i+1,j)
  • j ≥ w i j \geq w_i jwi时, { 取 x i = 0 时, 为 m ( i + 1 , j ) 取 x i = 1 时, 为 m ( i + 1 , j − w i ) + v i \left\{\begin{matrix} 取x_i=0时, & 为m(i+1, j) \\ 取x_i=1时, & 为m(i + 1, j - w_i) +v_i \end{matrix}\right. {xi=0时,xi=1时,m(i+1,j)m(i+1,jwi)+vi

临界条件:

  • m ( n , j ) = { v n j ≥ w n 0 0 ≤ j < w n m(n, j)=\left\{\begin{matrix} v_n & j \geq w_n \\ 0 & 0 \leq j < w_n \end{matrix}\right. m(n,j)={vn0jwn0j<wn

m ( i , j ) m(i, j) m(i,j)的递归式容易看出,动态规划算法需要 O ( n C ) O(nC) O(nC)计算时间。

3. 程序代码

给定背包容量为55,物体数量为14,物品体积:{3, 5, 11, 7, 9, 2, 13, 17, 26, 24, 19, 14, 12, 6},物品价值:{0, 5, 7, 15, 8, 9, 1, 10, 17, 30, 26, 20, 17, 9, 6}。以下C++代码实现了动态规划算法求解0-1背包问题。

//动态规划法求解0-1背包问题 
#include <iostream>

using namespace std;

int max(int a, int b) {  //求a,b元素中的最大值 
	return (a > b) ? a : b;
}

int min(int a, int b) {  //求a,b元素中的最小值 
	return (a < b) ? a : b;
}

//求背包中物体总价值的最大值 
void knapsack(int itemSize[], int itemValue[], int capacity, int itemNumber, int **m) {
	//物体数量为itemNumber,物体i的体积为itemsize[i],价值为itemValue[i],背包容量为capacity
	//m[itemNumber+1][capacity+1]存储动态规划表
	//m[i][j]表示第i..itemNumber个物体在背包容量为j时背包所能装入的最大价值 
	int jMax = min(itemSize[itemNumber] - 1, capacity); 
	int j;
	for (j = 0; j <= jMax; j++) {
		m[itemNumber][j] = 0;
	}
	for (j = itemSize[itemNumber]; j <= capacity; j++) {
		m[itemNumber][j] = itemValue[itemNumber];
	}
	int i;
	for (i = itemNumber - 1; i >= 2; i--) { //i>1,表示对i=1暂时不处理,i=1时只需求m[1][capacity] 
		jMax = min(itemSize[i] - 1, capacity);
		for (j = 0; j <= jMax; j++) {
			m[i][j] = m[i + 1][j];
		}
		for (j = itemSize[i]; j <= capacity; j++) {
			m[i][j] = max(m[i + 1][j], m[i + 1][j - itemSize[i]] + itemValue[i]);
		}
	}
	if (capacity >= itemSize[1]) {
		m[1][capacity] = max(m[2][capacity], m[2][capacity - itemSize[1]] + itemValue[1]);
	}
	else {
		m[1][capacity] = m[2][capacity];
	}
}

//回溯输出解x[1..itemNumber] 
void traceback(int itemSize[], int capacity, int itemNumber, int **m, int *x) {
	int i;
	for (i = 0; i < itemNumber; i++) {
		//在背包容量为capacity时,考虑有第i..itemNumber个物体和有第i+1..itemNumber个物体
		//如果二者的最大价值相同,则说明第i个物体未放入背包。 
		if (m[i][capacity] == m[i+1][capacity]) {  
			x[i] = 0;
		}
		else {  //否则说明第i个物体放入背包 
			x[i] = 1;  
			capacity -= itemSize[i]; //背包容量减去第i个物体的体积 
		}
	}
	x[itemNumber] = (m[itemNumber][capacity]) ? 1 : 0;
}

int main() {
	int capacity = 55;
	int itemNumber = 14;
	int itemSize[] = {0, 3, 5, 11, 7, 9, 2, 13, 17, 26, 24, 19, 14, 12, 6};
	int itemValue[] = {0, 5, 7, 15, 8, 9, 1, 10, 17, 30, 26, 20, 17, 9, 6};
	//为动态规划表m,解向量x开空间 
	int **m = new int*[itemNumber + 1];
	int i;
	for (i = 0; i < itemNumber + 1; i++) {
		m[i] = new int[capacity + 1];
	} 
	int *x = new int[itemNumber + 1];
	
	knapsack(itemSize, itemValue, capacity, itemNumber, m);
	traceback(itemSize, capacity, itemNumber, m, x);
	for (i = 1; i <= itemNumber; i++) { //输出解x[1..itemNumber],一个01序列,0代表没选,1代表选中 
		cout << x[i] << " ";
		if (i == itemNumber) {
			cout << endl;
		}
	}
	for (i = 1; i <= itemNumber; i++) { //输出装入背包的物体编号 
		if (x[i] == 1) {
			cout << "Item " << i << " is chosen, ";
			cout << "its value is " << itemValue[i] << ", its size is " << itemSize[i] << endl; 
		}
	}  
	cout << "The optimal solution (the largest value of the knapsack) is ";
	cout << m[1][capacity] << endl; //输出最优解,即背包中物体总价值的最大值 
	//释放动态规划表m,解向量x占据的空间 
	for (i = 0; i < itemNumber + 1; i++) {
		delete[]m[i]; 
	} 
	delete[]m;
	delete[]x;
	return 0;
}

4. 运行结果

运行上述代码,程序会输出表示物体是否被选中的01向量序列,并输出最优解的值(即背包的最大价值),如下图所示。

在这里插入图片描述

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fufufunny

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值