给定n种物品和一背包。物品i的体积是wi,其价值为vi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
注:物品只能选择不装或者装入背包,而不分切一小部分装入,即0或者1。因此,0-1背包问题是一个特殊的整数规划问题。
形式化描述:
输入:C > 0, wi > 0, vi > 0, 1≤ i ≤ n;
输出:(x1, x2, …, xn),xi∈{0, 1},使得∑1≤i≤n vi·xi最大。
(1)描述0-1背包问题的最优子结构,并利用“剪切-粘贴”技术证明;
(2)利用最优子结构,写出原问题最优解的递归表达式;
(3)利用自底向上的方法计算最优解的值和最优解,用伪代码进行描述,并分析算法的时间复杂度;
(4)用高级编程语言实现(3)中的求解0-1背包问题的动态规划算法,并通过问题实例测试程序,对运行结果截图。
0-1背包问题是很典型的动态规划问题,若对背包的限定条件再加上重量限制等又可变为二重0-1背包问题,但是万变不离其宗。
二重0-1背包问题:https://blog.csdn.net/worthyyyou/article/details/112846886
一重0-1背包问题:https://blog.csdn.net/qq_50985215/article/details/125879917
本笔记是算法导论实验的粗略版本,有空时更改更好的排版。尽量按照算法导论书上四个步骤来解答,只是将leetcode上那种状态转移方程式等术语用算法导论的术语表达。
首先看原问题,是个典型的0-1规划问题,数学描述如下:
目标: m a x ∑ i = 1 n v i x i 约束条件: { ∑ i = 1 n w i x i ≤ C x i ∈ { 0 , 1 } , 1 ≤ i ≤ n 目标:max\sum_{i=1}^{n}v_{i}x_{i}\\ 约束条件: \begin{cases} \sum_{i=1}^{n}w_{i}x_{i}\le C\\ x_{i}\in\{0,1\},1\le i \le n \end{cases} 目标:maxi=1∑nvixi约束条件:{∑i=1nwixi≤Cxi∈{0,1},1≤i≤n
子问题:
目标: m a x ∑ t = 1 i v t x t 约束条件: { ∑ t = 1 i w t x t ≤ j x t ∈ { 0 , 1 } , 1 ≤ t ≤ i 目标:max\sum_{t=1}^{i}v_{t}x_{t}\\ 约束条件: \begin{cases} \sum_{t=1}^{i}w_{t}x_{t}\le j \\ x_{t}\in\{0,1\},1\le t \le i \end{cases} 目标:maxt=1∑ivtxt约束条件:{∑t=1iwtxt≤jxt∈{0,1},1≤t≤i
利用“剪切-粘贴”证明最优子结构性:
暂略,有空补(不难,根据ppt上照搬)
最优值记为m(i,j),为背包容量为j,可选择物品为1,2,,,i是0-1背包问题的最优解。递归式如下:
m ( i , j ) = { m a x { m ( i − 1 , j ) , m ( i − 1 , j − w i ) + v i } , w i ≤ j m ( i − 1 , j ) , 0 ≤ j ≤ w i m(i,j)= \begin{cases} max\{m(i-1,j),m(i-1,j-w_{i})+v_{i}\},w_{i}\le j\\ m(i-1,j),0\le j \le w_{i} \end{cases} m(i,j)={max{m(i−1,j),m(i−1,j−wi)+vi},wi≤jm(i−1,j),0≤j≤wi
解释:
当考虑前 i 种物品时,对于第 i 种物品,有两种方法,一种是可以加入背包(条件就是自身体积必须小于当前容量),一种是不能能加入背包(条件是自身容积容量大于限制条件,因此,它的值和考虑前 i - 1种物品的值是一样的 )。
当可以加入背包时,要考虑两种情况,进而来选出最大值。一种时不加入第 i 种物品时的值,一种是加入第 i 种物品的值,加入i商品的值就等于i的本身的 v i v_{i} vi值加上第i-1的物品时容量已为 j − w i j-w_{i} j−wi的最优值。
采用自底向上的方法
伪代码:
暂略
c++实现:
#include
#include
#define MAX 10005
using namespace std;
int maxx(int x,int y){
if(x <= y){
return y;
}
else{
return x;
}
}
int backpack_dp(int n,int c,int w[],int v[]){
int dp[50][50] = { 0 }; //即上面提到的m(i,j)
for (int i = 1; i <= n; i++){
for (int j = 1; j <= c; j++){
if (w[i] <= j){
dp[i][j] = maxx(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
}
else{
dp[i][j] = dp[i - 1][j];
}
}
}
cout << "背包能放物品的最大价值为:" << dp[n][c] << endl;
//构造最优解
int x[MAX] = { 0 }; //记录是否被选中
for (i = n; i > 1; i--)
if (dp[i][c] == dp[i - 1][c])
x[i] = 0;
else {
x[i] = 1;
c -= w[i];
}
//当dp[1][c]为空值(0)时x[1]为0,不为空值(0)则为1
x[1] = (dp[1][c]) ? 1 : 0;
if
cout << "被选入背包的物品的编号,体积,价值分别是:" << endl;
for (i = 1; i< n + 1; i++)
if (x[i] == 1)
cout << "第" << i << "个物品:" << w[i] << " " << " " << v[i] << endl;
}
int main()
{
int n, c;
int w[MAX] = { 0 }; //体积
int v[MAX] = { 0 }; //价值
cout << "请输入物品个数:";
cin >> n;
cout << "请输入背包的容量:";
cin >> c;
cout << "请依次输入各个物品的重量,价值:(共" << n << "个)" << endl;
for (i = 1; i < n + 1; i++)
{
cin >> w[i] >> v[i];
}
backpack_dop(n,c,w,v);
return 0;
}
复杂度:
粗略根据for的嵌套次数得出是 O ( n 2 ) O(n^{2}) O(n2)
//构造最优解
int x[MAX] = { 0 }; //记录是否被选中
for (i = n; i > 1; i--)
if (dp[i][c] == dp[i - 1][c])
x[i] = 0;
else {
x[i] = 1;
c -= w[i];
}
//当dp[1][c]为空值(0)时x[1]为0,不为空值(0)则为1
x[1] = (dp[1][c]) ? 1 : 0;
if
cout << "被选入背包的物品的编号,体积,价值分别是:" << endl;
for (i = 1; i< n + 1; i++)
if (x[i] == 1)
cout << "第" << i << "个物品:" << w[i] << " " << " " << v[i] << endl;