首页 > 代码库 > 经典算法宝典——动态规划思想(六)(2)

经典算法宝典——动态规划思想(六)(2)

1、01背包问题

有N件物品和一个容量为V的背包,第i件物品的体积是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

解析:
这是最基础的背包问题,特点是每种物品仅有一件,可以选择放或不放。用子问题定义状态,即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。其状态转移方程便是f[i][v] = max{f[i-1][v], f[i-1][v-c[i]]+w[i]},这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的,所以有必要将它详细解释一下:“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只关系前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,其价值为f[i-1][v];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。

算法实现:

(1)二维数组实现背包

#include<stdio.h>
#include<string.h>
#define N 1000
int f[N][N];

int main()
{
	int w[N], c[N], i, j, n, v;
	// 输入物品的个数;输入背包的体积
	scanf_s("%d%d", &n, &v);
	// 输入n种物品的价值;
	for(i = 1; i <= n; i++)
		scanf_s("%d", &w[i]);
	// 输入n种物品的体积
	for(i = 1; i <= n; i++)
		scanf_s("%d", &c[i]);

	// 初始化数组f的元素为0
	memset(f, 0, sizeof(f));

	for(i = 1; i <= n; i++)
		for(j = 0; j <= v; j++)
		{
			// 如果当前物品的体积小于背包体积,
			// 且当前物品的价值加上背包剩下的空间能放下的物品的价值大于上一次选择的最佳方案,则更新f[i][j]
			if(c[i] <= j && f[i-1][j] < (f[i-1][j-c[i]] + w[i]))
				f[i][j] = f[i-1][j-c[i]] + w[i];
			else
				f[i][j] = f[i-1][j];
		}
	printf("%d\n", f[n][v]);

	return 0;
}
结果输出:

(2)一维数组实现背包

#include<stdio.h>
#include<string.h>
#define N 1000

int main()
{
	int w[N], c[N], f[N];
	int i, j, n, v;
	// 输入物品的个数;输入背包的体积
	scanf_s("%d%d", &n, &v);
	// 输入n种物品的价值;
	for(i = 1; i <= n; i++)
		scanf_s("%d", &w[i]);
	// 输入n种物品的体积
	for(i = 1; i <= n; i++)
		scanf_s("%d", &c[i]);

	// 初始化数组f的元素为0
	memset(f, 0, sizeof(f));

	for(i = 1; i <= n; i++)
		for(j = v; j >= c[i]; j--)
			if(f[j] < f[j-c[i]] + w[i])
				f[j] = f[j-c[i]] + w[i];

	printf("%d\n", f[v]);
	return 0;
}

结果输出:


总结:

动态规划的核心是状态和状态转移方程。01背包问题是最基本的背包问题,它包含了背包问题中状态、方程的最基本思想,另外,别的类型的背包问题往往也可以转换成01背包问题求解。