c语言背包算法怎么实现

作者:袖梨 2026-09-09

在前端开发内容学习中,c语言背包算法怎么实现?0/1背包思路与代码示例是常见主题。很多人在阅读时会遇到概念分散、步骤不清和注意点难以归纳的问题。本文按照基础概念、操作流程和关键细节,对相关内容进行整理。

img_6aa14c51df0d330.webp

很多人搜c语言背包算法,真正想解决的通常不是名词解释,而是如何把问题拆成状态、写出转移并在C语言里落地。本文围绕0/1背包讲清建模思路、代码结构和常见易错点。

先弄清背包问题在求什么

背包问题的核心是,在总容量有限的条件下,从若干物品里选择一部分,让总价值尽量大。每件物品通常有两个属性:重量和价值,算法要回答的是怎样选最划算。

如果题目说明每件物品只能取一次,通常就是0/1背包;如果同一种物品可以重复取,通常就是完全背包;如果每种物品只能取有限件数,通常就是多重背包。本文下面给出的状态设计、代码模板、输入输出示例,都是围绕0/1背包展开的。

写C语言程序前,先确认题型,否则循环方向一错,结果就会直接失真。0/1背包一般是容量倒序,完全背包一般是容量正序;多重背包则常见做法是拆分成多个0/1物品,或者再配合其他优化方法处理。

  1. 这部分内容的完整代码示例对应的是0/1背包,不是所有背包题都能原样直接套。
  2. 如果题目允许同一物品重复选,核心区别通常不在公式本身,而在容量循环方向。
  3. 如果题目限制每种物品件数,先看件数和数据范围,再决定是否做二进制拆分。

状态设计和转移怎么写

0/1背包最常见的设计是用dp[j]表示容量恰好或不超过j时能够得到的最大价值。这样做的好处是数组结构简单,C语言实现直接,空间也比二维写法更省。

转移时要枚举每件物品,再判断当前容量j能不能放下它。若能放下,就比较"不选当前物品"和"选当前物品"两种结果,取其中较大值。

一维优化时,容量必须从大到小循环。因为每件物品只能用一次,倒序才能保证本轮更新时读取到的是上一轮物品留下的旧状态,而不是已经被当前物品污染过的新状态。比如处理第i件物品时,dp[j - w[i]]必须表示"前i-1件物品在容量j-w[i]时的最优值",这样加上v[i]才是合法的"选第i件"方案。

  1. 状态定义:dp[j]表示容量为j时的最大总价值。
  2. 初始条件:dp数组先全部置为0,表示什么都不选时价值为0。
  3. 转移公式:dp[j] = max(dp[j], dp[j - w[i]] + v[i])
  4. 循环顺序:物品在外层,容量j从bag逆序到w[i]。
  5. 如果把0/1背包写成容量正序,当前物品会在同一轮被重复利用,结果就偏成完全背包。

C语言完整示例

下面这份代码演示的是标准0/1背包。输入部分给出物品数量、背包容量、每件物品的重量和价值,程序输出最大总价值,适合作为练习和改写模板。

先看这份模板的适用前提:代码里写了MAX_N 105和MAX_W 1005,所以要求n不能超过MAX_N - 1,也就是104;bag不能超过MAX_W - 1,也就是1004。如果题目数据更大,要么把数组上限开大,要么改成动态分配,不能直接照抄。

输入格式也要对齐:第一行输入n和bag;接下来n行,每行输入两个整数,分别表示第i件物品的重量w[i]和价值v[i]。如果你的题目不是从标准输入读取,也可以保留核心循环不动,只替换数据来源。

  • 输入格式:第一行是物品数量n和背包容量bag。
  • 接下来n行:每行两个整数,依次是重量和价值。
  • 适用范围:n <= 104,bag <= 1004。超出这个范围时,这份固定数组模板不能直接使用。
  • 完整示例

    #include <stdio.h>
    
    #define MAX_N 105
    #define MAX_W 1005
    
    int max(int a, int b) {
        return a > b ? a : b;
    }
    
    int main(void) {
        int n, bag;
        int w[MAX_N], v[MAX_N];
        int dp[MAX_W] = {0};
    
        if (scanf("%d %d", &n, &bag) != 2) {
            return 0;
        }
    
        for (int i = 1; i <= n; i++) {
            scanf("%d %d", &w[i], &v[i]);
        }
    
        for (int i = 1; i <= n; i++) {
            for (int j = bag; j >= w[i]; j--) {
                dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
            }
        }
    
        printf("%dn", dp[bag]);
        return 0;
    }
  • 编译命令:cc -std=c11 knapsack.c -o knapsack
  • 运行命令:./knapsack < input.txt

这段代码怎么输入和怎么看输出

如果你想直接把代码跑起来,可以先准备一组最小可验证样例。下面这个例子里,一共有3件物品,背包容量是4。

第1件重量1、价值15;第2件重量3、价值20;第3件重量4、价值30。因为每件物品只能选一次,所以最优方案是选第1件和第2件,总重量4,总价值35。程序输出35,就说明这组数据跑通了。

  • 示例输入

    3 4
    1 15
    3 20
    4 30
  • 对应输出

    35
  • 如果你使用命令./knapsack < input.txt,那么input.txt里的内容就按上面的格式填写。
  • 如果你直接运行./knapsack,程序会等待你在终端手动输入这些数字,输完后再回车结束。

关键循环怎么对应题意

很多人会背公式,但一进代码就不知道双重循环到底在干什么。其实可以把外层和内层直接翻译成题目动作。

外层第i轮,表示"现在考虑第i件物品,要不要把它放进背包"。内层从bag倒着枚举到w[i],表示"尝试把这件物品放到每个放得下它的容量里"。

还是以上面的样例说明。开始时dp全是0。处理第1件物品(重量1,价值15)后,容量1到4的位置都可以得到15。

处理第2件物品(重量3,价值20)时,先看j=4,dp[4]会比较原来的15和dp[1]+20,也就是35,所以更新成35;接着j=3,dp[3]会从15更新到20。到了第3件物品(重量4,价值30)时,j=4会比较35和dp[0]+30,最终仍保留35。

这就是倒序更新的意义:当第2件物品更新dp[4]时,用到的dp[1]还是上一轮第1件物品留下的值15,而不是本轮刚被第2件物品改写的新值。这样才能保证第2件物品只被算一次。

  1. 外层循环控制"当前处理哪一件物品"。
  2. 内层倒序循环控制"这件物品是否放入不同容量的背包"。
  3. 比较dp[j]和dp[j - w[i]] + v[i],本质上就是比较"不选它"和"选它"。
  4. 倒序的目标不是写法好看,而是防止同一件物品在同一轮被重复使用。

把模板套到题目里的实用步骤

实际做题时,不要一上来就抄代码。先用固定步骤把题意翻译成模板字段,能大幅减少出错。

最常见的落地方式,是先判断题目是不是"每件物品最多选一次、求最大价值",如果是,就把数据填进w数组、v数组和bag,再直接使用这份核心循环。若题目只改了输入来源、增加了多组测试,或者要求输出是否能装满,本质上都是在模板外围做小改动。

  1. 先看题目是否明确说明每件物品只能选一次;如果不是,就别直接套这份0/1背包模板。
  2. 从题目里提取三个量:物品数量n、背包容量bag、每件物品的重量和价值。
  3. 先用样例手算一个答案,再喂给程序,确认输出一致后再提交。
  4. 如果题目数据范围更大,优先检查数组上限是否够用,再决定是否改成更大的数组或动态分配。
  5. 如果题目要输出选了哪些物品,需要在这个价值模板基础上继续记录路径,而不是改动转移方向。

容易出错的地方和检查方法

背包题最常见的错误不是公式不会写,而是细节没对齐。比如把0/1背包写成顺序枚举容量,程序能编译也能输出,但答案会悄悄变成"可重复选取"的效果。

另一个高频问题是数组范围不足。背包容量上限如果比数组开得更大,轻则结果异常,重则直接越界。写C语言时要先看题目数据范围,再决定数组大小,不要拿固定模板直接套。

调试时可以先用很小的数据手算答案,再对照程序输出。只要两三个样例都能对上,状态定义、转移公式和循环方向通常就已经基本正确。

  • 可以先测只有1件物品的情况,检查基础转移是否正确。
  • 可以测背包容量小于所有物品重量的情况,结果应为0。
  • 可以测两件物品取舍冲突的情况,确认程序确实在比较最优价值。
  • 可以专门测bag或n接近数组上限的情况,确认模板没有越界风险。

c语言背包算法并不难,关键是先分清题型,再把状态、转移和循环方向对应好。本文给出的完整代码和样例主要解决的是0/1背包落地问题;把这个模板吃透后,再去看完全背包和多重背包,会更容易建立完整思路。

相关文章

精彩推荐