【动态规划】01背包问题——算法设计与分析


一、问题定义

1.1 实例引入

若超市允许顾客使用一个体积大小为13的背包,选择一件或多件商品带走,则如何选择可以使得收益最高?

商品价格体积
啤酒2410
汽水23
饼干94
面包105
牛奶94

1.2 形式化定义

0-1 Knapsack Problem

输入:

\quad - n n n个商品组成集合 O O O,每个商品有属性价格 p i p_i pi和体积 v i v_i vi

\quad - 背包容量为 C C C

输出:

\quad - 求解一个商品子集 S ⊆ O S\subseteq O SO,使得

\quad \quad 优化目标: m a x ∑ i ∈ S p i max\sum_{i \in S}p_i maxiSpi

\quad \quad 约束条件: ∑ i ∈ S v i ≤ C \sum_{i \in S}v_i\leq C iSviC



二、问题求解

2.1 蛮力枚举

很自然的想法便是将所有商品一一列举出来,然后将不符合容量限制的组合去掉,在剩余中找到最大值,即为最优方案。

在这里插入图片描述

定义递归函数:

K n a p s a c k S R ( h , i , c ) KnapsackSR(h, i, c) KnapsackSR(h,i,c)

表示在第 h h h个到第 i i i个商品中,容量为 c c c时的最优解

在这里插入图片描述

例如,

在这里插入图片描述

我们可以发现如下的递推式:
K n a p s a c k S R ( h , i , c ) = m a x { K n a p s a c k S R ( h , i − 1 , c − v i ) + p i , K n a p s a c k S R ( h , i − 1 , c ) } KnapsackSR(h,i,c)=max\left \{ KnapsackSR(h,i-1,c-v_i)+p_i, KnapsackSR(h,i-1,c) \right \} KnapsackSR(h,i,c)=max{KnapsackSR(h,i1,cvi)+pi,KnapsackSR(h,i1,c)}
为了方便,将函数修改为

K n a p s a c k S R ( i , c ) KnapsackSR(i, c) KnapsackSR(i,c)

表示在前 i i i个商品中,容量为 c c c时的最优解

观察其递归树,我们会发现存在大量重叠子问题:

在这里插入图片描述

那么如何进一步优化以减少计算量?接下来引入带备忘录的递归。


2.2 带备忘递归

只需构造备忘录 P [ i , c ] P[i,c] P[i,c],表示在前 i i i个商品中选择,背包容量为 c c c时的最优解。

此时的递归树便可以被优化至下图所示:

在这里插入图片描述

给出其伪代码:

在这里插入图片描述

那么我们是否可以不进行递归,直接求解 P [ i , c ] P[i,c] P[i,c]?答案是肯定的,接下来使用动态规划来解决此问题。


2.3 动态规划

是否还记得我们之前发现的递推式?
K n a p s a c k M R ( i , c ) = m a x { K n a p s a c k M R ( i − 1 , c − v i ) + p i , K n a p s a c k M R ( i − 1 , c ) } KnapsackMR(i,c)=max\left \{ KnapsackMR(i-1,c-v_i)+p_i, KnapsackMR(i-1,c) \right \} KnapsackMR(i,c)=max{KnapsackMR(i1,cvi)+pi,KnapsackMR(i1,c)}
(1)首先,对备忘录进行初始化:

在这里插入图片描述

接着,根据递推式,确定计算顺序:

在这里插入图片描述

(2)于是,我们根据子问题的依赖关系,确定按照从左到右,从上到下的顺序进行计算。此时最优解出现在备忘录 P P P的右下角。

并且,为了记录选择了哪些商品,我们再引入一个 R e c Rec Rec数组,用来记录决策过程
R e c [ i , c ] = { 1 选择第 i 个商品 0 不选第 i 个商品 Rec[i,c]=\left\{\begin{matrix} 1 & 选择第i个商品\\ 0 &不选第i个商品 \end{matrix}\right. Rec[i,c]={10选择第i个商品不选第i个商品
根据上述关系式,最终可以求解出 P [ i ] [ c ] P[i][c] P[i][c] R e c [ i ] [ c ] Rec[i][c] Rec[i][c]

在这里插入图片描述

(3)根据 R e c [ i ] [ c ] Rec[i][c] Rec[i][c]数值,逆向搜索追踪最优解。

例如, R e c [ 5 ] [ 13 ] = 1 Rec[5][13]=1 Rec[5][13]=1,那么则选择了商品5,观察 R e c [ 5 − 1 ] [ 13 − v 5 ] = R e c [ 4 ] [ 9 ] = 1 Rec[5-1][13-v_5]=Rec[4][9]=1 Rec[51][13v5]=Rec[4][9]=1,则确定选择了商品4。

如此,最终可以得知选择了商品5,4,3。

其伪代码分为三部分:

①初始化 P [ i ] [ c ] P[i][c] P[i][c] R e c [ i ] [ c ] Rec[i][c] Rec[i][c]

在这里插入图片描述

②依次计算子问题,记录决策过程

在这里插入图片描述

③追踪最优方案

在这里插入图片描述

最终可以达到时间复杂度: O ( n C ) O(nC) O(nC),线性时间即可求解。



三、动态规划小结

实际上,带备忘递归和递推求解都叫做动态规划,他们的共同点都是分解问题来寻找关系,但是带备忘递归是自顶向下,而递推求解是自底向上,更加高效。

动态规划一般分为4步:

(1)分析问题结构

给出问题的形式化定义,明确原始问题。

分析问题的最优子结构,只有具有最优子结构性质和重叠子问题的问题才可以使用动态规划来进行求解。

最优子结构性质

①问题的最优解由子问题最优解组合而成

②子问题可以独立求解

(2)建立递推关系

(3)自底向上计算

(4)追踪最优方案

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在C语言中,可以使用动态规划算法来解决0-1背包问题动态规划算法的基本思想是将问题分解为子问题,并利用子问题的解来构建原问题的解。 下面是使用动态规划算法求解0-1背包问题的C语言代码示例: ```c #include <stdio.h> // 定义最大物品数量和背包容量 #define MAX_N 100 #define MAX_W 1000 // 物品的重量和价值 int weight[MAX_N]; int value[MAX_N]; // 动态规划表格 int dp[MAX_N][MAX_W]; // 求解0-1背包问题 int knapsack(int n, int W) { // 初始化第一行和第一列为0 for (int i = 0; i <= n; i++) { dp[i][0] = 0; } for (int j = 0; j <= W; j++) { dp[0][j] = 0; } // 填充动态规划表格 for (int i = 1; i <= n; i++) { for (int j = 1; j <= W; j++) { if (weight[i] <= j) { // 当前物品的重量小于等于背包容量,可以选择放入或不放入背包 dp[i][j] = (value[i] + dp[i - 1][j - weight[i]]) > dp[i - 1][j] ? (value[i] + dp[i - 1][j - weight[i]]) : dp[i - 1][j]; } else { // 当前物品的重量大于背包容量,只能选择不放入背包 dp[i][j] = dp[i - 1][j]; } } } // 返回最优解 return dp[n][W]; } int main() { int n; // 物品数量 int W; // 背包容量 printf("请输入物品数量和背包容量:"); scanf("%d %d", &n, &W); printf("请依次输入每个物品的重量和价值:\n"); for (int i = 1; i <= n; i++) { scanf("%d %d", &weight[i], &value[i]); } int max_value = knapsack(n, W); printf("背包中物品的最大总价值为:%d\n", max_value); return 0; } ``` 以上代码使用二维数组`dp`来表示动态规划表格,其中`dp[i][j]`表示前`i`个物品放入背包容量为`j`时的最大总价值。通过填充动态规划表格,最终得到背包中物品的最大总价值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

友人帐_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值