当前位置:首页 > C++知识 > 正文内容

完全背包问题

亿万年的星光1周前 (10-18)C++知识23

1. 问题定义

完全背包问题是经典的动态规划问题之一。它的基本描述如下:

  • 有一个容量为 V 的背包。

  • 有 N 种物品,每种物品有无限个可用。

  • 第 i 种物品的重量是 w[i],价值是 v[i]

  • 问题:如何选择物品放入背包,使得在不超过背包容量的前提下,背包内物品的总价值最大?

关键词: 每种物品无限件


2. 与 0-1 背包问题的区别

理解完全背包的关键是与 0-1 背包进行对比:

特性0-1 背包完全背包
物品数量每件物品只有1件每件物品有无限件
决策对于第 i 件物品,只有两种选择:放 (1)不放 (0)对于第 i 件物品,可以选择放 0 件、1 件、2 件... 直到放不下为止


3. 核心思路与状态转移方程(二维数组)


1. 二维数组的状态定义

dp[i][j] 表示:考虑前 i 种物品(即下标 0 到 i-1 的物品)时,背包承重为 j 所能获得的最大价值
  • 行索引 i:表示 “考虑的物品数量”(i=0 表示不考虑任何物品,i=3 表示考虑所有 3 种物品)。

  • 列索引 j:表示 “背包的当前承重”(j=0 表示承重 0,j=5 表示承重 5)。

2. 初始化

  • 当 i=0(不考虑任何物品)时,无论承重 j 是多少,最大价值都是 0(没有物品可选),因此 dp[0][j] = 0

3. 状态转移方程(核心区别)

对于第 i 种物品(下标 i-1):
  • 不选该物品:价值等于 “前 i-1 种物品在承重 j 时的最大价值”,即 dp[i][j] = dp[i-1][j]

  • 选该物品(需满足 j >= weight[i-1]):由于完全背包允许重复选取,因此价值等于 “前 i 种物品在承重 j - w[i-1] 时的最大价值 + 当前物品价值”,即 dp[i][j] = dp[i][j - w[i-1]] + v[i-1]

最终取两种情况的最大值:dp[i][j] = max(dp[i-1][j], dp[i][j - w[i-1]] + v[i-1])(若承重足够)。


【参考代码】

#include <iostream>
#include <algorithm>
using namespace std;

// 完全背包问题(二维数组版本)
// 参数:
// - weight:物品重量数组
// - value:物品价值数组
// - n:物品数量
// - W:背包最大承重
int completeKnapsack(int weight[], int value[], int n, int W) {
    // dp[i][j]:前i种物品(0~i-1),背包承重为j时的最大价值
    int dp[n + 1][W + 1]; // 行:物品数量(0~n),列:承重(0~W)

    // 初始化:第0行(0种物品),无论承重多少,价值都为0
    for (int j = 0; j <= W; j++) {
        dp[0][j] = 0;
    }

    // 填充dp数组
    for (int i = 1; i <= n; i++) { // 遍历前i种物品(i从1到n)
        for (int j = 0; j <= W; j++) { // 遍历所有可能的承重(0到W)
            // 情况1:不选第i-1种物品(因为数组从0开始,第i种对应下标i-1)
            dp[i][j] = dp[i - 1][j];

            // 情况2:选第i-1种物品(若承重足够)
            if (j >= weight[i - 1]) {
                // 完全背包的核心:可以重复选,因此用dp[i][j - weight[i-1]](仍包含第i种物品)
                dp[i][j] = max(dp[i][j], dp[i][j - weight[i - 1]] + value[i - 1]);
            }
        }
    }

    return dp[n][W]; // 前n种物品,承重W时的最大价值
}

int main() {
    // 示例:3种物品,背包最大承重为5
    int weight[] = {2, 3, 1}; // 物品重量
    int value[] = {3, 5, 2};  // 物品价值
    int n = 3;                // 物品数量
    int W = 5;                // 背包最大承重

    int maxVal = completeKnapsack(weight, value, n, W);
    cout << "完全背包的最大价值为:" << maxVal << endl; // 输出:10

    return 0;
}




4. 核心思路与状态转移方程(一维数组)

1. 状态定义
设 dp[j] 表示:背包承重为 j 时,能获得的最大价值。
2. 状态转移方程
对于每种物品 i(从 0 到 n-1),以及每个可能的承重 j(从 w[i] 到 W):dp[j] = max(dp[j], dp[j - w[i]] + v[i])
  • 解释:对于承重 j,有两种选择:

    • 不选物品 i:价值保持 dp[j]

    • 选物品 i:此时承重变为 j - w[i],价值为 dp[j - w[i]] + v[i](由于可重复选,dp[j - w[i]] 可能已包含物品 i)。

3. 遍历顺序(关键)
  • 物品顺序:从第 1 种到第 n 种(每种物品单独处理)。

  • 承重顺序:从 weight[i] 到 W(正向遍历,允许重复选取当前物品)。

    (0-1 背包用逆向遍历(一维数组),防止重复选取,此处是核心差异)。


【参考代码】

#include <iostream>
#include <algorithm> // 用于max函数
using namespace std;
// 完全背包问题:返回最大价值
// 参数:
// - weight:物品重量数组
// - value:物品价值数组
// - n:物品数量
// - W:背包最大承重
int completeKnapsack(int weight[], int value[], int n, int W) {
    // dp[j]:承重为j的背包的最大价值(下标0~W)
    int dp[1000] = {0}; // 假设背包最大承重不超过999,初始值全为0

    // 遍历每种物品
    for (int i = 0; i < n; i++) {
        // 正向遍历承重(从物品重量到最大承重,允许重复选取)
        for (int j = weight[i]; j <= W; j++) {
            // 状态转移:取“不选当前物品”和“选当前物品”的最大值
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    return dp[W]; // 返回最大承重下的最大价值
}
int main() {
    // 示例:3种物品,背包最大承重为5
    int weight[] = {2, 3, 1}; // 物品重量数组
    int value[] = {3, 5, 2};  // 物品价值数组
    int n = 3;                // 物品数量
    int W = 5;                // 背包最大承重
    int maxVal = completeKnapsack(weight, value, n, W);
    cout << "完全背包的最大价值为:" << maxVal << endl; // 输出:10
    return 0;
}


4.例子
  • 物品信息:

    • 物品 0:重量 2,价值 3

    • 物品 1:重量 3,价值 5

    • 物品 2:重量 1,价值 2

  • 背包承重 W=5

计算过程
  1. 处理物品 0(重量 2,价值 3):
    • j=2dp[2] = max(0, dp[0]+3) = 3

    • j=4dp[4] = max(0, dp[2]+3) = 6

  2. 处理物品 1(重量 3,价值 5):
    • j=3dp[3] = max(0, dp[0]+5) =5

    • j=5dp[5] = max(0, dp[2]+5) = 3+5=8

  3. 处理物品 2(重量 1,价值 2):
    • j=1dp[1] = 2

    • j=2max(3, dp[1]+2)=4

    • j=3max(5, dp[2]+2)=4+2=6

    • j=4max(6, dp[3]+2)=6+2=8

    • j=5max(8, dp[4]+2)=8+2=10

最终 dp[5]=10,对应方案:选 5 个物品 2(重量 1×5=5,价值 2×5=10)


扫描二维码推送至手机访问。

版权声明:本文由青少年编程知识记录发布,如需转载请注明出处。

分享给朋友:

相关文章

【C++图形化编程】小游戏——打砖块(1)

【C++图形化编程】小游戏——打砖块(1)

0.前言这篇文章我们尝试创建一个打砖块的小游戏。1.游戏框架根据我们前面做的一些游戏的框架,这个小游戏的框架也可以分为下面这样的框架。int main() { startup();&n...

质数(素数)的判断

一、定义法// 1 定义法(除了1和他本身之外,没有任何一个数能被整除)(试除法) bool is_prime3(unsigned long lon...

判断闰年

代码参考:#include<iostream>  using namespace std; //判断闰年的函数  int leap(...

【算法】分治算法

前言所谓分治算法就是指分而治之,即将较大规模的问题分解成几个较小规模的问题,通过对较小问题的求解达到对整个问题的求解。当我们将问题分解成两个较小问题求解时的分治方法称为二分法。比如,我们玩过最简单的猜...

NOIP2013年普及组初赛题目及答案分析

NOIP2013年普及组初赛题目及答案分析

一、单项选择题1. 一个 32 位整型变量占用( A )个字节。 A. 4    B. 8      C. 32     &nbs...

CSP-J2021年普及组复赛T2——插入排序

CSP-J2021年普及组复赛T2——插入排序

【题目描述】插入排序是一种非常常见且简单的排序算法。小 Z 是一名大一的新生,今天 H 老 师刚刚在上课的时候讲了插入排序算法。 假设比较两个元素的时间为 O(1),则插入排序可以以 O(n 2...