动态规划01背包与完全背包的C语言实现

本文详细介绍了01背包和完全背包问题的基础概念及解决方法,包括状态转移方程和代码实现,并通过实例帮助理解两种背包问题的区别与联系。

01背包是基础的背包问题,即容量为v的背包, 给你n件物品, 每件物品只有一件, 每件物品所占体积vi, 价值wi已知,求此背包所能容纳的前提下,让在其中物品价值最大。

此问题状态方程为发f[i][v] = max(f[i-1][v], f[i-1][v-vi[i]]+wi[i]), 即前i件物品放入容积为v的背包中得到的最大价值。

因为每件物品只有一件,所以第i件物品有两种选择, 即放或不放。

max(f[i-1][v], f[i-1][v-vi[i]]+wi[i])中,f[i-1][v]即选择不放, 价值保持放入i-1 个物品时的价值;
而f[i-1][v-vi[i]]+wi[i]即选择将第i件物品放入, 从其中选择较大的值就是即为前i件物品放入容积为v的背包得到的最大价值,最后f[n][v]就是要求的价值最大值;

code:
for(i = 0;i<n;i++)
        {
            for(j = vi[i];j<=v;j++)
            {
                f[i][j] = max(f[i-1][j], f[i-1][j-vi[i]]+wi[i]);
            }
        }

另外, 还可用一维数组代替二维数组的方法实现空间优化,即用最新的一组数据代替原先的上一组数据,不过此方法写循环式需要逆序循环, 即二层循环从v开始一直到vi[i],这样就保证了每个物品只拿一次,而顺序则不能保证,可能拿多次。
于是f[v] = max(f[v], f[v-vi]+wi[i], 最后的f[v]即为要求的最大价值。

code:
for(i = 0;i<n;i++)
        {
            for(j = v;j>=vi[i];j--)
            {
                f[j] = max(f[j], f[j-vi[i]]+wi[i]);
            }
        }

例题1:
Problem Description

多组输入。

对于每组输入,第一行有两个整数N,X(1 < = N < = 100,1 < = X < = 1000),分别表示哈士奇的数量和高数巨的钱数

对于每组数据,输出一个整数,表示高数巨最多可以获得的萌值,每组输出占一行

Example Input
2 100
50 20
60 40
3 100
20 55
20 35
90 95
1 10
20 50
Example Output
40
95
0
明显的01背包问题,这里所带的钱就相当于背包的体积,萌值就是价值,每条二哈的价钱可看做每个物品所占的体积。
code:
#include<stdio.h>
#include<string.h>
int max(int a, int b)
{
    if(a>b) return a;
    else return b;
}
int main()
{
    int b[1200];
    int n, x, pi[1200], mi[1200], i, j, a;
    while(~scanf("%d%d", &n, &x))
    {
        memset(b, 0, sizeof(b));
        for(i = 0;i<n;i++)
        {
            scanf("%d%d", &pi[i], &mi[i]);
        }
        for(i = 0;i<n;i++)
        {
            for(j = x;j>=pi[i];j--)
            {
                b[j] = max(b[j], b[j-pi[i]]+mi[i]);
            }
        }
        printf("%d\n", b[v]);
    }
}

有了01背包基础,接下来的完全背包聚会简单很多,完全背包实际上就是将每种物品的数量从一件变为无限件。那么设某i件物品拿k件,那么状态方程为

f[i][v]=max(f[i-1][v-k*v[i]]+k*w[i]|0<=k*v[i]<=v),这样需要三层循环,找出k为何值时使得f得到最大值;

另外完全背包也可以使用一维数组进行空间优化, 可以用01背包提到过的正序循环,这样就可以保证每件物品拿多次。

01背包中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1] [v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的 子结果f[i-1][v-v[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-v[i]],所以就可以并且必须采用v=0..V的顺序循环。(本段节录自背包九讲)
code:
for(i = 0;i<n;i++)
        {
            for(j = vi[i];j<=v;j++)
            {
                f[j] = max(f[j], f[j-vi[i]]+wi[i]);
            }
        }

例题1:

Problem Description
话说,上次小P到伊利哇呀国旅行得到了一批宝藏。他是相当开心啊,回来就告诉了他的好基友小鑫,于是他们又结伴去伊利哇呀国寻宝。
这次小P的寻宝之路可没有那么的轻松,他们走到了一个森林,小鑫一不小心被触发了机关,被困在了一个大笼子里面,笼子旁边上有一道题目和一个密码锁,上面说只要解出此题输入密码即可救出被困人。小鑫不是很聪明,所以他做不出来,他知道小P很笨,更解不出来。所以他就让小P独自回去,不用管他。但是小P重情重义不会抛弃他离去。他说:“不,好基友一起走!”。于是就感动了上帝,上帝特派你来替他们解决问题。聪明的你要加油了啊!
题目描述:给你n种物品和一个体积为v的包包。每种物品有无数种,体积是vi价值是wi。求出包包v所能装的最大价值的东西。
Input
多组输入。第一行有两个正整数n(0<n<=10000), v(0<v<= 10000)。接下来两行每行有n个数字。第一行表示每种物品的价值wi(0<wi<100),第二行表示每种物品的体积vi(0<vi<100)。
Output
输出最多可以得到的价值。输出结果救出小鑫。
Example Input
5 20
1 2 3 4 5
2 6 3 5 4
Example Output
25
code:
#include<stdio.h>
#include<string.h>
int max(int a, int b)
{
    if(a>b) return a;
    else return b;
}
int main()
{
    int n, v, vi[10010], wi[10010], a[10010];
    int i, j;
    while(~scanf("%d%d", &n, &v))
    {
        memset(a, 0, sizeof(a));
        for(i = 0;i<n;i++)
        {
            scanf("%d", &wi[i]);
        }
        for(i = 0;i<n;i++)
        {
            scanf("%d", &vi[i]);
        }
        for(i = 0;i<n;i++)
        {
            for(j = vi[i];j<=v;j++)
            {
                a[j] = max(a[j], a[j-vi[i]]+wi[i]);
            }
        }
        printf("%d\n", a[v]);
    }
}




<think>好的,我现在需要帮助用户寻找关于0-1背包问题的C语言实现或解决方法。首先,我得回想一下0-1背包问题的基本概念。0-1背包问题是一个经典的动态规划问题,目标是在不超过背包容量的情况下,选择物品使得总价值最大。每个物品只能选择放或不放,不能分割。 用户需要C语言实现,所以我得考虑如何用动态规划来解决这个问题。动态规划通常需要一个二维数组来存储子问题的解,比如dp[i][w]表示前i个物品在容量w下的最大价值。状态转移方程是关键,应该是dp[i][w] = max(dp[i-1][w], dp[i-1][w - weight[i]] + value[i]),前提是当前物品的重量不超过当前容量。 接下来,我需要考虑如何将这个逻辑转化为C代码。首先,定义物品的数量、容量、重量数组和价值数组。然后初始化一个二维数组,可能需要处理边界条件,比如当没有物品或容量为0时的值。然后通过嵌套循环遍历每个物品和每个可能的容量,应用状态转移方程。 还需要注意空间优化的问题。标准的动态规划使用二维数组,但可以优化为一维数组,因为当前状态只依赖于上一行的前部分数据。这样可以将空间复杂度从O(nW)降低到O(W),这在处理大容量时会节省内存。 然后,我需要检查示例是否正确。比如,用户提供的引用中提到Compile enum kp and run it on easy.20.1.txt,这可能是一个测试案例,但用户没有给出具体数据。所以,在代码示例中可能需要假设一些输入数据,或者让用户自行输入。 此外,用户可能对动态规划和递归的区别感兴趣,因为引用2中提到了动态规划递归的关系。虽然这里的问题用动态规划更高效,但可能需要解释为什么递归不是最优选择,即重复计算导致的高时间复杂度。 最后,生成相关问题时要围绕0-1背包问题的扩展,比如不同解法、空间优化、处理大数据量的方法,以及其他背包问题的对比。确保问题有助于用户深入理解该主题。 现在,我需要确保代码的正确性,比如数组索引是否正确,循环范围是否恰当,以及是否正确处理了物品从1开始还是0开始的问题。同时,要提供注释,让代码更易理解。可能还需要测试一下示例输入,比如假设有3个物品,重量和价值数组,以及容量为5,验证输出是否正确。</think>### 0-1背包问题的C语言实现 #### 问题描述 0-1背包问题要求在$n$个物品中选择若干物品装入容量为$W$的背包,使得总价值最大。每个物品只能选择放入(1)或不放入(0),且不可分割。 #### 动态规划解法 1. **定义状态**: - 用二维数组`dp[i][w]`表示前$i$个物品在容量$w$限制下的最大价值。 2. **状态转移方程**: $$ dp[i][w] = \begin{cases} \max(dp[i-1][w],\ dp[i-1][w - \text{weight}[i]] + \text{value}[i]) & \text{if } w \geq \text{weight}[i] \\ dp[i-1][w] & \text{otherwise} \end{cases} $$ 3. **边界条件**: $$ dp[0][w] = 0 \quad \text{(无物品时价值为0)} $$ #### C语言实现代码 ```c #include <stdio.h> #include <stdlib.h> int max(int a, int b) { return (a > b) ? a : b; } int knapsack(int W, int weight[], int value[], int n) { int **dp = (int **)malloc((n+1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = (int *)malloc((W+1) * sizeof(int)); } for (int i = 0; i <= n; i++) { for (int w = 0; w <= W; w++) { if (i == 0 || w == 0) dp[i][w] = 0; else if (weight[i-1] <= w) dp[i][w] = max(value[i-1] + dp[i-1][w - weight[i-1]], dp[i-1][w]); else dp[i][w] = dp[i-1][w]; } } int result = dp[n][W]; for (int i = 0; i <= n; i++) free(dp[i]); free(dp); return result; } int main() { int value[] = {60, 100, 120}; // 物品价值数组 int weight[] = {10, 20, 30}; // 物品重量数组 int W = 50; // 背包容量 int n = sizeof(value) / sizeof(value[0]); printf("最大价值为: %d\n", knapsack(W, weight, value, n)); return 0; } ``` #### 代码说明 1. **空间优化**:上述代码使用二维数组,空间复杂度为$O(nW)$。可通过滚动数组优化为一维数组,将空间复杂度降为$O(W)$[^2]。 2. **输入输出**:示例代码中物品数据为硬编码,实际应用可从文件读取(如`easy.20.1.txt`格式的测试数据)[^1]。 3. **时间复杂度**:$O(nW)$,适用于中等规模数据。
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值