【动态规划】经典0-1背包问题
0. 先推荐引流一篇文章~~
推荐文章:《动态规划之0-1背包问题(详解+分析+原码)》
写的很不错。但是动态数组的定义写得不如我下面:
这道题关键在于理解动态规划公式的定义:
可以定义一个二维数组dp[N][C+1],N是物品的种类,C是背包的承重(或者体积)
dp[i][j]是这个数组的一个元素,其值就表示从前i件物品进行选择,在不超过容量j的前提下所满足最大的物品总价值。(注:此处的第i件物品对应与数组下标i)。
最核心的代码如下:
/**
*
* @param N 物品数
* @param C 背包容量
* @param v 每件的体积
* @param w 每件物品的价值
* @return 最大价值
*/
public int zoKnapsack(int N, int C, int[] v, int[] w) {
//0-1背包朴素
int[][] dp = new int[N][C+1];
//初始化
for (int j = 0; j <= C; j++) {
dp[0][j] = j >= v[0] ? w[0] : 0;
}
//处理剩余元素
for (int i = 1; i < N; i++) {
for (int j = 0; j <= C; j++) {
//不选
int x = dp[i-1][j];
//选
int y = j >= v[i] ? dp[i-1][j-v[i]] + w[i] : 0;
//取两者中的最大值
dp[i][j] = Math.max(x, y);
}
}
return dp[N-1][C];
}
1. 我的实现代码(C语言实现)
前面的宏定义、包含等等。
#include <stdio.h>
#include <stdlib.h>
#define MAX(x, y) ((x)>(y))?(x):(y)
typedef struct
{
int weight;
int value;
}Bagobj;
int SolveBagProb(int N, int M, Bagobj *objlist);
int SolveBagProbPlus(int N, int M, Bagobj *objlist);
int SolveBagProbPlusPlus(int N, int M, Bagobj *objlist);
main 函数如下:
用于输入数据、调用核心运算函数、打印结果。
int main() {
//输入数据
int N = 0;
int M = 0;
//1. 物品的数量
scanf("%d", &N);
scanf("%d", &M);
Bagobj *objlist = (Bagobj *)malloc(sizeof(Bagobj)*N);//使用动态数组
for(int i=0; i<N; i++)
{
scanf("%d", &objlist[i].weight);
}
for(int i=0; i<N; i++)
{
scanf("%d", &objlist[i].value);
}
#if 1
//int res = SolveBagProb(N, M, objlist);
//int res = SolveBagProbPlus(N, M, objlist);
int res = SolveBagProbPlusPlus(N, M, objlist);
printf("%d", res);
#endif
}
核心运算函数:朴素解法(版本V1.0)
空间复杂度比较大。为o(m·n)。
//解决背包问题:
//所需要的数据:物品种类N,背包承重M,每种物品的重量和价值(w,v)
//解法1
int SolveBagProb(int N, int M, Bagobj *objlist)
{
//初始化一个动态规划数组,dp[N][M+1],初始化其第一行。
//对于dp[i][j]而言:i是前i个物件,j是背包的承重(变)。
int dp[N][M+1];
for(int j=0; j<=M; j++)
{
//初始化第一行
dp[0][j] = (j>=(objlist[0].weight))?(objlist[0].value):(0);
}
//对下面每一行[1, N-1],求动态规划式子
int alt_full = 0;
int alt_notfull = 0;
for(int i=1; i<N; i++)
{
for(int j=0; j<=M; j++)
{
alt_full = dp[i-1][j];
int temp_weight = objlist[i].weight;
if(j >= temp_weight)
{
alt_notfull = dp[i-1][j-temp_weight]+objlist[i].value;
}
else {
alt_notfull = 0;
}
dp[i][j] = MAX(alt_full, alt_notfull);
//printf("dp[%d][%d] = %d\n",i,j, dp[i][j]);
}
}
return dp[N-1][M];
}
核心运算函数:2维数组解法(版本V2.0 )
改进空间复杂度的解法,空间复杂度为 O(M)。
//解法2
int SolveBagProbPlus(int N, int M, Bagobj *objlist)
{
//初始化一个动态规划数组,dp[N][M+1],初始化其第一行。
//对于dp[i][j]而言:i是前i个物件,j是背包的承重(变)。
int dp[2][M+1];
for(int j=0; j<=M; j++)
{
//求价值,下面这处错误
//dp[0][j] = (j>=objlist[0].weight)?(objlist[0].weight):(0);
dp[0][j] = (j>=(objlist[0].weight))?(objlist[0].value):(0);
//printf("dp[0][%d] = %d\n",j, dp[0][j]);
}
//对下面每一行[1, N-1],求动态规划式子
int alt_full = 0;
int alt_notfull = 0;
for(int i=1; i<N; i++)
{
for(int j=0; j<=M; j++)
{
alt_full = dp[(i-1)&0x01][j];
int temp_weight = objlist[i].weight;
if(j >= temp_weight)
{
//alt_notfull = dp[i-1][j-temp_weight]+temp_weight;
alt_notfull = dp[(i-1)&0x01][j-temp_weight]+objlist[i].value;
}
else {
alt_notfull = 0;
}
dp[i&0x01][j] = MAX(alt_full, alt_notfull);
//printf("dp[%d][%d] = %d\n",i,j, dp[i][j]);
}
}
return dp[(N-1)&0x01][M];
}
核心运算函数:1维数组解法(版本V3.0 )
//解法3
int SolveBagProbPlusPlus(int N, int M, Bagobj *objlist)
{
//初始化一个动态规划数组,dp[N][M+1],初始化其第一行。
//对于dp[i][j]而言:i是前i个物件,j是背包的承重(变)。
int dp[M+1];
for(int j=0; j<=M; j++)
{
//初始化第一行
dp[j] = (j>=(objlist[0].weight))?(objlist[0].value):(0);
}
//对下面每一行[1, N-1],求动态规划式子
int alt_full = 0;
int alt_notfull = 0;
for(int i=1; i<N; i++)
{
for(int j=M; j>=0; j--)//注意此处:j是从M开始递减遍历的
{
alt_full = dp[j];
int temp_weight = objlist[i].weight;
if(j >= temp_weight)
{
alt_notfull = dp[j-temp_weight]+objlist[i].value;
}
else {
alt_notfull = 0;
}
dp[j] = MAX(alt_full, alt_notfull);
//printf("dp[%d][%d] = %d\n",i,j, dp[i][j]);
}
}
return dp[M];
}
核心运算函数:1维数组-小改进解法(版本V3.1 )
首先,其中的第二维度检查可以少一点, j不用到0结束遍历,而是到 w[i] 结束遍历。改为for(int j=M; j>=w[i]; j++)
因为当 j在[0,w[i])的范围之内,由于 j<temp_weight
,因此一定 alt_notfull = 0
, 因此一定 dp[j] = alt_full = dp[j]
。相当于 j在[0,w[i])的范围这部分dp[j]根本没变,因此不需要遍历了。
另外,第一行初始化的代码可以融入动态规划的循环中,因为在 j<temp_weight
的时候,dp[j] = 0, 之前已经 memset(0),可以不用设置。
当j>=w[i]
的时候,"上一轮"的dp[j](初始化的dp[j])=0 ,MAX的结果肯定是 alt_notfull。
最后,阅读其他人的代码里没有 alt_full 这部分,是因为 alt_full = dp[j]
,因此可以直接写 dp[j] = MAX(dp[j] , alt_notfull);
修改好的代码如下
//解法3.1(仅仅5行代码)
int SolveBagProbPlusPlus(int N, int M, Bagobj *objlist)
{
//初始化一个动态规划数组,dp[N][M+1],初始化其第一行。
//对于dp[i][j]而言:i是前i个物件,j是背包的承重(变)。
int dp[M+1];
//对下面每一行[1, N-1],求动态规划式子
for(int i=0; i<N; i++)
{
int temp_weight = objlist[i].weight;
for(int j=M; j>=temp_weight; j--)
{
int temp_max = dp[j-temp_weight]+objlist[i].value;
dp[j] = MAX(dp[j], temp_max);
}
}
return dp[M];
}