一篇文章吃透背包问题!!!

薛定谔了么
2024-08-23 18:20:04
运筹优化
算法解析
本帖最后由 薛定谔了么 于 2025-1-23 16:55 编辑




什么是背包问题?


● 简单来说就是:一个小偷背了一个背包潜进了金店,包就那么大,他如果保证他背出来所有物品加起来的价值最大


● 规范描述就是:有一个容量为 W 的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积 w 和价值 v


背包问题分类:


最常见的背包问题有0-1背包完全背包多重背包分组背包这四种:


● 按照每个物品的数量分类。


● 其中最重要的是 0 - 1背包 完全背包



0 - 1背包



问题描述:一个最多能背体积为 W 背包 n物品。第 i 件物品的体积是 weight ,价值是 value 每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大



解题思路:

定义一个二维数组 dp 存储最大价值,其中 dp[j] 表示前 i 件物品体积不超过 j 的情况下能达到的最大价值。


设第 i 件物品体积为 w[i],价值为 v,根据第 i 件物品 是否添加 到背包中,可以分两种情况讨论:


1.  i 件物品 没添加 到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i -1 件物品的最大价值,dp[j] = dp[i-1][j]


2.  i 件物品 添加 到背包中,dp[j] = dp[i-1][j-w] + v



i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。因此,0 -1 背包的状态转移公式为:



万能统一代码:(Java、C++)

Java:


// W 为背包总体积
// N 为物品数量
// weights 数组存储 N 个物品的重量
// values 数组存储 N 个物品的价值
public int knapsack(int W, int N, int[] weights, int[] values) {
    int[][] dp = new int[N + 1][W + 1];
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= W; j++) {
            if (j >= weights[i - 1]) {
                dp[j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
            } else {
                dp[j] = dp[i - 1][j];
            }
        }
    }
    return dp[N][W];
}

C++ :


// W 为背包总体积
// N 为物品数量
// weights 数组存储 N 个物品的重量
// values 数组存储 N 个物品的价值
int knapsack(int W, int N, vector<int> weights, vector<int> values) {
    vector<vector<int>> dp(N + 1, vector<int>(W + 1));
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= W; j++) {
            if (j >= weights[i - 1]) {
                dp[j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
            } else {
                dp[j] = dp[i - 1][j];
            }
        }
    }
    return dp[N][W];
}

空间优化

在程序实现时可以对 0 - 1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅与前 i - 1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[j]。此时,



因为要使用 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[j-w]防止将 dp[i-1][j-w] 覆盖。


也就是说要先计算 dp[j] 再计算 dp[j-w],在程序实现时需要按 倒序 来循环求解。


Java:


public int knapsack(int W, int N, int[] weights, int[] values) {
    int[] dp = new int[W + 1];
    for (int i = 1; i <= N; i++) {
        for (int j = W; j >= 1; j--) {
            if (j >= weights[i - 1]) {
                dp[j] = Math.max(dp[j], dp[j - weights[i - 1]] + values[i - 1]);
            }
        }
    }
    return dp[W];
}

C++:


int knapsack(int W, int N, vector<int> weights, vector<int> values) {
    vector<int> dp(W + 1);
    for (int i = 1; i <= N; i++) {
        for (int j = W; j >= 1; j--) {
            if (j >= weights[i - 1]) {
                dp[j] = Math.max(dp[j], dp[j - weights[i - 1]] + values[i - 1]);
            }
        }
    }
    return dp[W];
}

 

完全背包



与 0 - 1 背包问题相似,但在完全背包问题中,物品的数量是无限的,可以选择任意多个相同的物品放入背包。



解题思路:


和 0 - 1 背包类似,我们可以定义一个二维数组 dp,其中 dp[j] 表示在前 i 个物品中,背包容量为 j 时能够获得的最大价值。


动态规划的状态转移公式如下:



● 其中 w 表示第 i 个物品的重量,v 表示第 i 个物品的价值。


● 第一项 dp[i-1][j] 表示 不选择 i 个物品;


● 第二项 dp[j-w] + v 表示 选择 i 个物品(可能不止一个,和0-1背包相区别!)。


万能统一代码:(Java、C++)


Java:


// W 为背包总体积
// N 为物品的种类
// weights 数组存储 N 个物品的重量
// values 数组存储 N 个物品的价值
public int knapsack(int W, int N, int[] weights, int[] values) {
    int[][] dp = new int[N + 1][W + 1];
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= W; j++) {
            if (j >= weights[i - 1]) {
                dp[j] = Math.max(dp[i - 1][j], dp[j - weights[i - 1]] + values[i - 1]);
            } else {
                dp[j] = dp[i - 1][j];
            }
        }
    }
    return dp[N][W];
}

 



C++ :


// W 为背包总体积
// N 为物品的种类
// weights 数组存储 N 个物品的重量
// values 数组存储 N 个物品的价值
int knapsack(int W, int N, vector<int> weights, vector<int> values) {
    vector<vector<int>> dp(N + 1, vector<int>(W + 1));
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= W; j++) {
            if (j >= weights[i - 1]) {
                dp[j] = Math.max(dp[i - 1][j], dp[j - weights[i - 1]] + values[i - 1]);
            } else {
                dp[j] = dp[i - 1][j];
            }
        }
    }
    return dp[N][W];
}



 



空间优化




 

和 0 - 1 背包类似,可以优化空间复杂度为一维数组,使用 dp[j] 表示背包容量为 j 时的最大价值。状态转移公式为:



和0-1背包不同,在程序实现时按 正序 来循环求解。


Java:


public int knapsack(int W, int N, int[] weights, int[] values) {
    int[] dp = new int[W + 1];
    for (int i = 1; i <= N; i++) {
       for (int j = 1; j <= W; j++) {
            if (weights[i - 1] <= j) {
                dp[j] = Math.max(dp[j], dp[j - weights[i - 1]] + values[i - 1]);
            }
        }
    }
    return dp[W];
}

 

C++:


int knapsack(int W, int N, vector<int> weights, vector<int> values) {
    vector<int> dp(W + 1);
    for (int i = 1; i <= N; i++) {
       for (int j = 1; j <= W; j++) {
            if (weights[i - 1] <= j) {
                dp[j] = max(dp[j], dp[j - weights[i - 1]] + values[i - 1]);
            }
        }
    }
    return dp[W];

 

无法使用贪心算法的解释



0-1 背包问题无法使用贪心算法来求解,也就是说不能按照先添加性价比最高的物品来达到最优,这是因为这种方式可能造成背包空间的浪费,从而无法达到最优



考虑下面的物品和一个容量为 5 的背包:


● 如果先添加物品 0 再添加物品 1,那么只能存放的价值为 16,浪费了大小为 2 的空间。


● 最优的方式是存放物品 1 物品 2,价值为 22.



 



所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的;


只需记住:动规是由前一个状态推导出来的,而贪心是局部直接选最优的。



————————————————



本文转载自CSDN平台博主酷酷的懒虫


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。


原文链接:https://blog.csdn.net/weixin_43412762/article/details/129485663




876
0
0
0
关于作者
相关文章
  • 对比GAN 与 VAE :两大生成模型如何各显神通,又为何要 “联手” ...
    《 Comparative Study of GAN and VAE 》发表于 International Journal of Computer Application ...
    了解详情 
  • AI 助力揭示蟾毒灵作为 ERα 分子胶降解剂逆转他莫昔芬耐药乳腺 ...
    《 Harnessing artificial intelligence to identify Bufalin as a molecular glue degrader of ...
    了解详情 
  • 汽车测试数据的 “解码器”——TeVAE 与多变量时序异常检测的突 ...
    在汽车智能化发展进程中,动力系统测试的异常检测至关重要。由奔驰团队研究的《 TeVAE: A Variat ...
    了解详情 
  • 量子变分自编码器(QVAE):量子玻尔兹曼机解锁生成模型新潜力 ...
    《Quantum Variational Autoencoder》一文提出量子变分自编码器(QVAE),将量子玻尔兹曼机(QBM ...
    了解详情 
  • 【论文精读】量子退火启发的时空编码超表面优化 ...
    概要:本研究提出量子退火启发的时空编码超表面优化算法,将散射问题转化为二进制自旋模型,用模 ...
    了解详情 
联系我们
二维码
在本版发帖返回顶部
快速回复 返回顶部 返回列表
玻色有奖小调研
填写问卷,将免费赠送您5个100bit真机配额
(单选) 您是从哪个渠道得知我们的?*
您是从哪个社交媒体得知我们的?*
您是通过哪个学校的校园宣讲得知我们的呢?
取消

提交成功

真机配额已发放到您的账户,可前往【云平台】查看