用C语言实现背包问题(01背包、完全背包)

【动态规划】背包问题(详细总结,很全)-优快云博客

背包问题总结

暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来进行优化!
背包问题是动态规划(Dynamic Planning) 里的非常重要的一部分,关于几种常见的背包,其关系如下:

在解决背包问题的时候,我们通常都是按照如下五部来逐步分析,把这五步都搞透了,算是对动规来理解深入了。

1)动规四部曲:
(1) 确定dp数组及其下标的含义
(2) 确定递推公式
(3) dp数组的初始化
(4) 确定遍历顺序

01背包问题

给定n种物品,和一个容量为C的背包,物品i的重量是w[i],其价值为v[i]。问如何选择装入背包的物品,使得装入背包中的总价值最大?(面对每个物品,只能有选择拿取或者不拿两种选择,不能选择装入某物品的一部分,也不能装入物品多次)

声明一个数组f[n][c]的二维数组,f[i][j]表示在面对第i件物品,且背包容量为j时所能获得的最大价值。
根据题目要求进行打表查找相关的边界和规律
根据打表列写相关的状态转移方程
用程序实现状态转移方程


真题演练:

       一个旅行者有一个最多能装M公斤的背包,现在有n件物品,它们的重量分别是W1、W2、W3、W4、…、Wn。它们的价值分别是C1、C3、C2、…、Cn,求旅行者能获得最大价值。

输入描述:

       第一行:两个整数,M(背包容量,M<= 200)和N(物品数量,N<=30);
       第2…N+1行:每行两个整数Wi,Ci,表示每个物品的质量与价值。

输出描述:

       仅一行,一个数,表示最大总价值

样例:

输入:
       10 4

        2 1

        3 3

        4 5

        7 9
输出:

        12

解题步骤:

动规四部曲:
1) 确定dp数组及其下标的含义

dp[i][j] 表示从下标为 [0 - i] 的物品里任意取,放进容量为j的背包,价值总和最大是多少。

2) 确定递推公式

不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以被背包内的价值依然和前面相同。)

放物品i:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值

所以递归公式:

如果当前背包的容量可以装下物品:
	dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
如果当前背包的容量装不下该物品:
	dp[i][j]=dp[i-1][j];

3)dp数组的初始化

* 首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0

* 状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。

* dp[0][j]:存放编号0的物品的时候,各个容量的背包所能存放的最大重量j。

那么很明显当 j < weight[0]时,dp[0][j] 应该是 0(背包容量比编号0的物品重量还小)

同理,当j >= weight[0]时,dp[0][j] 应该是value[0](背包容量足够放编号0物品)

源码:
#include<stdio.h>
int max(int x,int y)
{
    if(x>=y) return x;
    else return y;
}
int main()
{
    int N,V;
    scanf("%d %d",&N,&V);
    int v[1001]={0},w[1001]={0};
    for(int i=1;i<=N;i++)
    {
        scanf("%d %d",&v[i],&w[i]);
    }
    int dp[1001][1001]={0};
    for(int i=1;i<=N;i++)
    {
        for(int j=1;j<=V;j++)
        {
            if(v[i]>j){
                dp[i][j]=dp[i-1][j];
            }
            else{
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
            }
        }
    }
    printf("%d",dp[N][V]);
    return 0;
}

完全背包

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式:

第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。

输出格式:

输出一个整数,表示最大价值。

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例:
4 5
1 2
2 4
3 4
4 5
输出样例:
10
解题思路:

朴素写法(三重循环):

动态规划数组定义:

使用二维数组f[i][j],其中f[i][j]表示考虑前i件物品,当背包容量为j时的最大价值。(和01背包一样

状态转移方程:

f [ i ] [ j ] = max( f [ i ] [ j ], f [ i - 1][ j - v [ i ] * k ] + w [ i ] * k),这个方程考虑了不选取当前物品和选取当前物品k次两种情况下的最大价值,并取这些情况的最大值更新DP数组。

循环遍历:

外层循环遍历所有物品,中层循环遍历所有可能的背包容量。
内层循环遍历每个物品的可能选取次数k(即物品i可以被选取0次、1次、2次,直到k * v[i]超过当前背包容量j)。
对于每种情况,更新f[i][j]为max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k),表示考虑选取k次物品i时的最大价值。

源码:
#include<stdio.h>
int max(int a,int b){
    if(a>=b) return a;
    else return b;
}
int main()
{
    int N,V0;
    scanf("%d %d",&N,&V0);
    int v[1001],w[1001];
    for(int i=1;i<=N;i++){
        scanf("%d %d",&v[i],&w[i]);
    }
    int dp[1001][1001]={0};
    for(int i=1;i<=N;i++){
        for(int j=1;j<=V0;j++){
            if(j>=v[i]){
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
                for(int k=1;k<=j/v[i];k++){
                    dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]*k]+w[i]*k);
                }
            }
            else{
                dp[i][j]=dp[i-1][j];
            }
        }
    }
    printf("%d",dp[N][V0]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值