问题描述:
给定n种物品和一背包。物品i的重量为wi,其价值为vi, 背包容量为c。问应如何选择装入背包中的物品,使得背入背包的物品的总价值最大?
解析:
此问题形式化的描述是,给定c > 0, wi, vi, 1 <= i <= n(c为背包容量), 要找出一个n元0-1向量(x1, x2, ... , xn),xi ∈ {0, 1}, 1 <= i <= n, 使得∑ wixi (i 从 1 到 n 求和)<= c ,而且∑ wixi (i 从 1 到 n 求和)达到最大。因此0-1背包问题是一个特殊的整数规划问题。
方法1:
递归关系:(这里与课本的描述不同个人感觉课本的“加”比较难理解, 这里用的“减”, 相信我继续看下去QAQ, 方法2用课本的方法“加”)
设所给0-1背包问题的子问题的最优值为m[i][j], 即m[i][j]的含义是是在背包容量为j,可选物品为1, 2, 3, ..., i 时的0-1背包问题的最优值。由0-1背包问题的最优子结构性质,可建立计算m[i][j]的递归式如下:
m[i][j] = max{m[i - 1][j], m[i - 1][j - w[i]] + v[i]} j >= w[i];
= m[i - 1][j] j < w[i];
递归关系流程化:
1:如果不放入第 i 件物品,则问题转换为“前i - 1件物品放入容量为 j 的背包中”;
2:如果放入第 i 件物品,则问题转化为“前i - 1件物品放入容量为 j - w[i] 的背包中”,此时的最大值为 max{m[i - 1][j], m[i - 1][j - w[i]] + v[i] }.
代码:
/*
输入样例 1:
3 10
4 3
5 4
6 5
输出为:
最优解为 : 11
选择的物品的序号为 :
2 3
输入样例 2:
5 10
6 2
3 2
5 6
4 5
6 4
输出为:
最优解为 : 15
选择的物品的序号为 :
1 2 5
*/
#include <bits/stdc++.h>
using namespace std;
const int MAX = 1024;
int n; //物品个数
int c; //包的容量
int value[MAX]; //物品的价值
int weight[MAX]; //物品的重量
int x[MAX]; //n元0-1向量
int m[MAX][MAX]; //解的容器
void Input()
{
scanf("%d %d", &n, &c);
for(int i = 1; i <= n; ++i)
scanf("%d %d", &value[i], &weight[i]);
}
//创建最优解
void Knapsack()
{
memset(m, 0, sizeof(m));
for(int i = 1; i <= n; ++i) //逐行填表,i表示当前可选物品数,j表示当前背包的容量, 也就是从低到顶。
{
for(int j = 1; j <= c; ++j)
{
if(j < weight[i])
m[i][j] = m[i - 1][j];
else
{
m[i][j] = max(m[i - 1][j], m[i - 1][j - weight[i]] + value[i]);
}
}
}
}
//获取最优解(即设法将求得的最优解输出出来)
void Traceback()
{
int cc = c;
for(int i = n; i > 1; --i)
{
if(m[i][cc] == m[i - 1][cc])
x[i] = 0;
else
{
x[i] = 1;
cc -= weight[i];
}
}
if(cc >= weight[1])
x[1] = 1;
}
void Output()
{
cout << "最优解为 : " << m[n][c] << endl;
cout << "选择的物品的序号为 :" << endl;
for(int i = 1; i <= n; ++i)
if(x[i] == 1)
cout << i << " ";
cout << endl;
}
int main()
{
Input();
Knapsack();
Traceback();
Output();
}
方法2:(解释见代码注释)
递归关系:
设所给0-1背包问题的子问题的最优值为m[i][j], 即m[i][j]的含义是背包容量为j,可选物品为i, i + 1, ... n 时的0-1背包问题的最优值。由0-1背包问题的最优子结构性质,可建立计算m[i][j]的递归式如下:
m[i][j] = max{m[i + 1][j], m[i + 1][j - w[i]] + v[i]} j >= w[i];
= m[i + 1][j] j < w[i];
递归关系流程化:
1:如果不放入第 i 件物品,则问题转换为“i + 1, i + 2, ..... , n件物品放入容量为 j 的背包中”;
2:如果放入第 i 件物品,则问题转化为“i + 1, i + 2, ..... , n 件物品放入容量为 j - w[i] 的背包中”,此时的最大值为 max{m[i + 1][j], m[i + 1][j - w[i]] + v[i]}.
与方法1的区别:
请先务必先看懂方法1~。
首先我们知道求最优解的过程就是填表m的过程。
第一行开始填表m,而方法2是从第n行开始填表m即(两种方法都是是从底往上求解,不过方法一是可选物品从1-> i(从左到右),方法二是可选物品从i -> n(从右向左)。这就是区别。而思路完全一样。请务必好好想想,然后就会恍然大悟٩(๑>◡<๑)۶
代码:
总的来说方法2比方法1运行速度快。
/*
输入样例 1:
3 10
4 3
5 4
6 5
输出为:
最优解为 : 11
选择的物品的序号为 :
2 3
输入样例 2:
5 10
6 2
3 2
5 6
4 5
6 4
输出为:
最优解为 : 15
选择的物品的序号为 :
1 2 5
*/
#include <bits/stdc++.h>
using namespace std;
const int MAX = 1024;
int n; //物品个数
int c; //包的容量
int value[MAX]; //物品的价值
int weight[MAX]; //物品的重量
int x[MAX]; //n元0-1向量
int m[MAX][MAX]; //解的容器
void Input()
{
scanf("%d %d", &n, &c);
for(int i = 1; i <= n; ++i)
scanf("%d %d", &value[i], &weight[i]);
}
//创建最优解
void Knapsack()
{
int jMax = min(c, weight[n] - 1);
//这一块填最后m表的最后一行
/*
解释一下就是:“在可选的物品只有n即最后一个物品n,包的容量为c时” 的最优解。
第一个for循环:
容易知道在背包容量为0 ~ weight[n] - 1的时候背包是放不进去物品n的,如果背包
的容量小于物品n的质量,背包也是放不进去物品的,所以从weight[n] - 1 和 c 中
选择一个较小的,然后m[n][0:jMax]的值为零
第二个for循环:
自然可知当背包容量大于weigh[n]的时候,由于其可选则的物品只有 物品n,因此m[n][weight[n]:c]
的值全部为value[n].
*/
for(int j = 0; j <= jMax; ++j)
m[n][j] = 0;
for(int j = weight[n]; j <= c; ++j)
m[n][j] = value[n];
//这一块是填m表的2 ~ n - 1行,容易理解
for(int i = n - 1; i > 1; --i)
{
jMax = min(c, weight[i] - 1);
for(int j = 0; j <= jMax; ++j)
m[i][j] = m[i + 1][j];
for(int j = weight[i]; j <= c; ++j)
m[i][j] = max(m[i + 1][j], m[i + 1][j - weight[i]] + value[i]);
}
//这里是填m表的第一行,好好理解一下,不难,好好考虑一下 φ(>ω<*)
m[1][c] = m[2][c];
if(c >= weight[1])
m[1][c] = max(m[1][c], m[2][c - weight[1]] + value[1]);
}
//获取最优解(即设法将求得的最优解输出出来)
void Traceback()
{
int cc = c;
for(int i = 1; i < n; i++)
if(m[i][cc] == m[i + 1][cc])
x[i] = 0;
else
{
x[i] = 1;
cc -= weight[i];
}
x[n] = (m[n][cc]) ? 1 : 0;
}
void Output()
{
cout << "最优解为 : " << m[1][c] << endl;
cout << "选择的物品的序号为 :" << endl;
for(int i = 1; i <= n; ++i)
if(x[i] == 1)
cout << i << " ";
cout << endl;
}
int main()
{
Input();
Knapsack();
Traceback();
Output();
// cout << "*******" << endl;
// for(int i = 1; i <= n; ++i)
// {
// for(int j = 1; j <= c; ++j)
// cout << m[i][j] << " ";
// cout << endl;
// }
}
方法三:基于以方法一的路径压缩
思路:
定义m[j] : m[j]为背包容量为 j 时(注意不是剩余容量),背包装入的最大value。
解题流程:遍历 i (可选物品为 1 ~ i),m[j] = max{ m[j], m[j - weight[i]] + value[i] }
缺点:无法求出选中了哪些物品
#include <bits/stdc++.h>
using namespace std;
const int MAX = 1024;
int n; //物品个数
int c; //包的容量
int value[MAX]; //物品的价值
int weight[MAX]; //物品的重量
int x[MAX]; //n元0-1向量
int m[MAX]; //解的容器
void Input()
{
scanf("%d %d", &n, &c);
for(int i = 1; i <= n; ++i)
scanf("%d %d", &value[i], &weight[i]);
}
//创建最优解
void Knapsack()
{
memset(m, 0, sizeof(m));
for(int i = 1; i <= n; ++i)
{
for(int j = c; j >= weight[i]; --j)
{
m[j] = max(m[j], m[j - weight[i]] + value[i]);
}
}
}
void Output()
{
cout << "最优解为 : " << m[c] << endl;
cout << endl;
}
int main()
{
Input();
Knapsack();
Output();
}