0-1背包问题-动态规划 解释与C语言实现

算法思路

动态规划
  • 动态规划算法与分治法类似,其基本思想是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
  • 可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。
  • 具体的动态规划算法多种多样,但它们具有相同的填表格式。

问题描述

物品数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是怎么来的:

  1. A过程
    • 物品B质量为3,当前背包容量为5,3<5,不符合A。
  2. B过程
    • B价值为上格:2。
    • A价值为当前背包容量5 - 当前物品重量(自重)3 = 2 ,容量2所对应的价值,即在(A,2)的价值2 再加上自己的价值3 = 5
      因为 2 < 5 所以取5填入当前空

//举例:例如(5,C)格的5是怎么来的:

  1. A过程
    • C质量为4,当前背包容量为5,不符合A。
  2. B过程
    • N价值为上格:5。
    • M价值为当前背包容量5-当前物品重量(自重)4 = 1 容量所对应的价值
      即在(A,1)的价值0 再加上自己的价值4 = 4
      因为 4 < 5所以取5填入当前空

2. 回溯找到相同的格子

//起点设置在右下角

  • a.如果该格 与 上面一格价值相同

    • 那么上移 直到不相同 然后到b步
  • b.如果该格 与 上面一格价值不同

    1. 则将当前物品记录到 选中的集合
    2. 然后找到左边 (当前容量减去当前物品重量) 的背包容量处
    3. 接着回到第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()
}

运行结果

原创文章 3 获赞 2 访问量 145

猜你喜欢

转载自blog.csdn.net/qq_39500052/article/details/105572224
今日推荐