0-1背包问题
本文内容来来源于《计算机算法设计与分析》(王晓东著),是笔者的学习笔记,内容不当处,欢迎留言探讨
- 问题描述:给定n中物品和一背包,物品i的重量是,价值为,背包容量为c。应如何选择装入背包中的物品,使得装入背包中物品的总价值最大。
- 目标函数:即装入背包中的物品价值达到最大。
- 约束条件:,即装入背包中的物品总重量小于背包的容量。
好吧,其实我看到这里已经要睡着了,脑子里想到的画面是装到背包里的东西,和重量有个线球关系,你该考虑的不该是体积么,反正我装背包,从来不担心重量。不过不要紧,清醒一下,洗把脸,心里默念,去~~~的
1、最优子结构
动态规划的本质是对问题进行分解,对每个子问题求解,从而得到原问题的解,当然,其灵魂是建立一个表格,或者什么都行,存储一部分子问题的解,避免进行重复计算。
- 设是0-1背包问题的最优解,则是一个子问题的最优解,该子问题的最优解情况如下:
书中写的是,目测笔误,下版建议修改。
2、递归关系
- 子问题i的问题描述: 背包容量为,可选择物品编号为,对应的价值的最优值为。数学表达:
笔者一开始对放入背包中的物品编号从到感觉很不适应,觉得如果从1到 的话也不是不行。 - 递归关系构建:
笔者这里看到,第一反应是背包剩余容量大于物品i的重量,但这是与的定义是相反的,再次强调表示的是背包容量,是子问题的背包容量。
递归关系公式中,第一个式子表示的含义是,物品编号为从到,背包容量为的子问题的最佳解有两种情况:- 背包容量小于物品的重量: 此时物品是放不进背包中的,最佳解的值就是我们已经得到的解。
- 背包容量大于物品的重量:此时物品是可以放到背包中的,那我们要分别作出讨论。**背包中不放入物品 **,因为不放入物品并不影响背包容量,所以此时最佳解的值为; 背包中放入物品,放入后,背包中已有物品价值是,背包原本容量为,此时剩余,所以最佳解的值为。此时最佳解就是这两个值中的最大值。
递归分析,我们的初始问题肯定是求解,逐层递归,最后一个子问题是物品的子问题,再将值逐层返回上层。
3、代码实现
终于开始了,笔者都有些迫不及待了,不过真的要吐槽一下这本书,讲的极为简单,对自学的学渣可能不太友好,代码还没有注释,看得想撕书。
// 0-1背包问题
// m[i][j]记录不同情况下的最佳值
#include<iostream>
#include<algorithm>
using namespace std;
void Knapspack(int v[], int w[], int c, int n, int m[][101]) {
/* v是价值向量,w是质量向量,c是背包容量,n物品数量,m是最价值矩阵*/
int jMax = min(w[n] - 1, c);
/*从递归关系的公式中,我们可以看到最后一层递归是可选物品为n,此时的子问题中,背包容量有两种可能:
容量小于物品n的质量,容量大于或等于物品n的质量,这与递归关系中的两种情况相同。
此处w[n]-1表示的就是小于n的质量,选择w[n]-1和c中小的那个,就是先给容量小于w[n]的情况赋值*/
for (int j = 0; j <= jMax; j++) // jMax小于w[n],所以此处对m[n][j]赋值0
m[n][j] = 0;
for (int j = w[n]; j <= c; j++) // 此处j从w[n]开始增长,
m[n][j] = v[n]; //表示的情况是子问题中背包容量足够装下物品n,将n放入背包
for (int i = n - 1; i > 1; i--) {
/*可选物品为n的情况已经确定了,就已经具备了逐层求解的条件。求解从可选物品为n-1,n开始。*/
jMax = min(w[i] - 1, c); // 先确定 0<= j <w[i]时的m[i][j]
for (int j = 0; j <= jMax; j++)
m[i][j] = m[i + 1][j];
for (int j = w[i]; j <= c; j++) // 确定j>=w[i]时的m[i][j]
m[i][j] = max(m[i + 1][j], m[i + 1][j - w[i]] + v[i]);
}
m[1][c] = m[2][c]; //w[1]>c时的m[1][j]
if (c >= w[1]) //c>=w[1]时的m[1][j]
m[1][c] = max(m[1][c], m[2][c - w[1]] + v[1]);
}
这一部分笔者在看书的时候看了好久,困了看,看了困,理解之后就发现,我去,这么简单!也希望大家在学习的时候不要害怕,不难的,学他!
- 通过上面的函数,我们可以求出矩阵,但是并没有求出最优解,不过我们可以利用矩阵求出最优解,这一部分是从倒推,比如我们有5个商品,,如果,则1未放入背包中,反之放入,依此倒推。
下面为求最优解的代码:
void Traceback(int m[][101], 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; //m[n][c]不为0,x[n]赋值1,为0,赋值0
}
- 主函数
int main() {
int c;
cout << "输入背包容量: ";
cin >> c;
int w[101], v[101], m[101][101], x[101];
cout << "输入物品数量: ";
cin >> w[0]; // w[0]存储物品数量
v[0] = w[0];
cout << "输入各物品质量: ";
for (int i = 1; i <= w[0]; ++i)
cin >> w[i];
cout << "输入各物品价值: ";
for (int i = 1; i <= w[0]; ++i)
cin >> v[i];
Knapspack(v, w, c, w[0], m);
Traceback(m, w, c, w[0], x);
for (int i = 1; i <= w[0]; ++i)
cout << x[i] << ' ';
cout <<endl<< m[1][c];
return 0;
}
运行结果:
4.时间复杂度分析
上述代码时间复杂度最大的一部分为矩阵赋值二重循环部分,外层循环次数为n-2,内层循环次数为c+1,复杂度为,不考虑常数,复杂度为。
以上是动态规划思想的0-1背包问题的求解过程,有错误之处欢迎留言指出,下一篇是对算法的改进。
来源:CSDN
作者:Potato_Shy
链接:https://blog.csdn.net/weixin_41147610/article/details/103746381