一篇文章吃透背包问题!!!【动态规划】

什么是背包问题?

  • 简单来说就是:一个小偷背了一个背包潜进了金店,包就那么大,他如果保证他背出来所有物品加起来的价值最大
  • 规范描述就是:有一个容量为 W 的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积 w 和价值 v

在这里插入图片描述

背包问题分类:

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

  • 按照每个物品的数量分类。
  • 其中最重要的是 0 - 1背包完全背包

在这里插入图片描述

0 - 1背包

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

解题思路:

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

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

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

在这里插入图片描述

i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。因此,0 -1 背包的状态转移公式为:
d p [ i ] [ j ] = max ⁡ ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) d p[i][j]=\max (dp[i-1][j], dp[i-1][j-w[i]]+v[i]) dp[i][j]=max(dp[i1][j],dp[i1][jw[i]]+v[i])

万能统一代码:(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[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
            } else {
                dp[i][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[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    return dp[N][W];
}

空间优化

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

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

也就是说要先计算 dp[i][j] 再计算 dp[i][j-w[i]],在程序实现时需要按 倒序 来循环求解。
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[i][j] 表示在前 i 个物品中,背包容量为 j 时能够获得的最大价值。

动态规划的状态转移公式如下:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − w [ i ] ] + v [ i ] ) dp[i][j] = max(dp[i-1][j], dp[i][j-w[i]] + v[i]) dp[i][j]=max(dp[i1][j],dp[i][jw[i]]+v[i])

  • 其中 w[i] 表示第 i 个物品的重量,v[i] 表示第 i 个物品的价值。
  • 第一项 dp[i-1][j] 表示 不选择i 个物品;
  • 第二项 dp[i][j-w[i]] + v[i] 表示 选择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[i][j] = Math.max(dp[i - 1][j], dp[i][j - weights[i - 1]] + values[i - 1]);
            } else {
                dp[i][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[i][j] = Math.max(dp[i - 1][j], dp[i][j - weights[i - 1]] + values[i - 1]);
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    return dp[N][W];
}

空间优化

  • 和 0 - 1 背包类似,可以优化空间复杂度为一维数组,使用 dp[j] 表示背包容量为 j 时的最大价值。状态转移公式为:
    d p [ j ] = m a x ( d p [ j ] , d p [ j − w [ i ] ] + v [ i ] ) dp[j] = max(dp[j], dp[j-w[i]] + v[i]) dp[j]=max(dp[j],dp[jw[i]]+v[i])

和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.

在这里插入图片描述

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

LeetCode题目 —— 实战 !!!:

416. 分割等和子集
494. 目标和
474. 一和零
322. 零钱兑换
518. 零钱兑换 II
139.单词拆分
377. 组合总和 Ⅳ
1049. 最后一块石头的重量 II

注:仅供学习参考!

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
要快速理解并掌握PFC(Power Factor Correction,功率因数校正)电源原理分析的技巧,可以按照以下4个步骤进行: 第一步是了解基本概念。PFC电源的目标是改善电源系统的功率因数,减小谐波失真,提高能源利用率。了解基本的PFC电源概念,如谐波、功率因数、能源利用等,是理解其原理分析的基础。 第二步是学习PFC电源的工作原理。PFC电源的核心组成是PFC控制器和PFC电路。通过学习PFC电源的工作原理,了解其如何校正和改善功率因数,以及各个元件和电路的作用和相互关系。 第三步是深入研究PFC电源的拓扑结构和控制方式。了解不同的PFC电路拓扑结构(如Boost型PFC、Bridgeless PFC等)和控制方式,对于理解PFC电源原理分析非常重要。研究拓扑结构和控制方式的优劣势、特点和适用范围,能够帮助快速抓住重点和关键问题。 最后一步是实践和案例分析。通过实际的项目实践和相关案例分析,深入理解PFC电源的原理和实际应用。可以通过仿真软件进行PFC电源的设计和验证,或者参考相关的研究论文和技术报告,从实践中获取经验和知识。 通过以上4个步骤,可以快速吃透PFC电源原理分析的技巧。但需要注意,PFC电源的原理和分析涉及一定的专业知识和技巧,需要较强的电力电子背景和电路分析能力。建议在了解基础知识后,多与领域专家和同行交流,不断学习和实践,进一步提升自己的能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

酷酷的懒虫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值