算法思路
动态规划
- 动态规划算法与分治法类似,其基本思想是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
- 可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。
- 具体的动态规划算法多种多样,但它们具有相同的填表格式。
问题描述
物品数n=5,背包容量c=10。
物品重量序列w={2,2,6,5,4},物品价值序列v={6,3,5,4,6}。
求最佳装包序列。
解决思路
//假设简单题目:背包容量5,物品ABC
A | B | C | |
---|---|---|---|
重量 | 2 | 3 | 4 |
价值 | 2 | 2 | 3 |
(可以直接推出:选AB价值总和最高,可最后验证是否正确)
1. 构建动态规划表
//表横轴为背包当前容量12345、纵轴为物品序号ABC。
//表格内容意义为当前容量(横轴)的最优价值
1. 将第一行第一列设为0.
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
A | 0 | |||||
B | 0 | |||||
C | 0 |
2. 从(1,A)处开始填数,
-
A过程:如果该物品质量(ABC)大于当前背包容量(123456…)
- 则不放入:现行背包总价值为之前的价值,即上格内容。
-
B过程:如果该物品质量(ABC)小于等于背包容量(123456…)
-
则判断价值AB大小,如果A>B则装入,否则不装入。
-
M:(装自己的重量)的最优价值
-
N:(不装自己的重量)的最优价值
即M : W =当前背包容量 - 当前物品重量(自重)的容量 M = 表格上行当前容量W 对应的价值 + 自己的价值。 即N = 不放自己的价值,即上面表格的价值。
-
-
该步骤完成可得下表:题目参考: ABC重量分别为234、价值分别为223
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
A | 0 | 0 | 2 | 2 | 2 | 2 |
B | 0 | 0 | 2 | 2 | 2 | 5 |
C | 0 | 0 | 2 | 3 | 3 | 5 |
//举例:例如(5,B)格的5是怎么来的:
- 到A过程
- 物品B质量为3,当前背包容量为5,3<5,不符合A。
- 到B过程:
- B价值为上格:2。
- A价值为当前背包容量5 - 当前物品重量(自重)3 = 2 ,容量2所对应的价值,即在(A,2)的价值2 再加上自己的价值3 = 5
因为 2 < 5 所以取5填入当前空
//举例:例如(5,C)格的5是怎么来的:
- 到A过程
- C质量为4,当前背包容量为5,不符合A。
- 到B过程:
- N价值为上格:5。
- M价值为当前背包容量5-当前物品重量(自重)4 = 1 容量所对应的价值
即在(A,1)的价值0 再加上自己的价值4 = 4
因为 4 < 5所以取5填入当前空
2. 回溯找到相同的格子
//起点设置在右下角
-
a.如果该格 与 上面一格价值相同
- 那么上移 直到不相同 然后到b步
-
b.如果该格 与 上面一格价值不同
- 则将当前物品记录到 选中的集合 中
- 然后找到左边 (当前容量减去当前物品重量) 的背包容量处
- 接着回到第a步,一直到边界
//过程如下(深绿色为要取的物品,要记录的值。淡绿色为移动过程)
3. 得到要装入的物品:AB
总结
先构造动态规划表,然后回溯获得最优解。
代码解决
C语言代码
#include<stdio.h>
#define MAX_SIZE 5//物品数量
#define BAG_SIZE 10//背包容量
int main() {
int w[MAX_SIZE] = {2,2,6,5,4 };//重量
int v[MAX_SIZE ] = {6,3,5,4,6 };//价值
int table[MAX_SIZE + 1][BAG_SIZE + 1];//table表定义 每个元素都是当前容量下背包的最优价值
int yes[MAX_SIZE]; //选中的物品集
int yes_size = 0; //选中集大小
//建立 表
for (int i = 0; i <= MAX_SIZE; i++)//i是商品序号 从1开始
for (int j = 0; j <= BAG_SIZE; j++) {//j是当前背包容量 从0开始
if (i == 0 || j == 0) {
table[i][j] = 0;//使第一行以及第一列全为0
continue;
}
if (w[i-1] > j)
table[i][j] = table[i - 1][j];//如果质量大于背包容量,则不放入。 现行背包总价值为之前的价值
else
table[i][j] = table[i - 1][j] > (table[i - 1][j - w[i-1]] + v[i-1]) ?
table[i - 1][j] : (table[i - 1][j - w[i - 1]] + v[i - 1]);
//如果质量小于等于背包容量,则判断价值大小
//(装自己的重量)的最优价值A 与 (不装自己的重量)的最优价值B
//即A = (当前背包容量j减去当前物品重量(自重)的容量W)所对应价值,即上面表格那个容量W对应的价值
//再加上自己的价值。
//即B = 不放自己的价值,即上面表格的价值。
}
//接下来回溯table表获取选中的物品
int i = MAX_SIZE;//纵轴物品最下侧
int j = BAG_SIZE;//横轴容量最右侧
while (i > 0 && j > 0) {//如果定位点不在边界
while (table[i][j] == table[i - 1][j])//如果该格 与 上面一格价值相同
i--;//上移 一直到不相同
yes[yes_size++] = --i;//将该物品(因为物品数组所以下标要-1)记录到选中的集合中
j = j - w[i-1];//横向跳动,跳到减去刚才记录的物品的重量后的容量的横轴位置
}
printf("被选中的物品有:\n");
for (int i = 0; i < yes_size; i++)
printf("重量为%d,价值为%d的物品\n", w[yes[i]], v[yes[i]]);//循环输出刚才选中的物品
return 0;//end main()
}