动态规划详细讲解c++|经典例题讲解认识动态规划|0-1背包问题详解

这篇具有很好参考价值的文章主要介绍了动态规划详细讲解c++|经典例题讲解认识动态规划|0-1背包问题详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引言

uu们,你们好!这次的分享是动态规划,其中介绍了动态规划的相关概念和做题模板(三要素),同时为了uu们对动态规划方法有更加形象的认识,特地找了两个经典问题,和大家一起分析。并且呢,为了大家检验自己的学习成果,我专门在常用的oj上为uu们找到了相关题目的链接,大家可以自行编写代码、提交检验。我在首页创建了一个动态规划的专栏,本文章是第一篇。之后,会在该专栏更新自己的刷题日记,每次都会给出oj题目链接和AC过的思路、代码分享。如果有想一起学习的小伙伴,可以点赞&收藏&关注,为咱们互联网上的这份缘分加把锁,以防轻易走丢。谢谢大家咯嘻嘻!!
不过,让我没想到的是这篇文章足足写了小两天,之前学动态规划和用动态规划的时候,一直以为自己还算掌握了,可真要自己一字一句讲清楚逻辑,还是费了不少功夫的,当然在这个过程中也有新的收获,温故而知新如是也。所以呢,在此也推荐uu们平常也可以坚持分享博客,记录自己的成长历程,而且这也能不断push我们个人前进!

基本概念

动态规划(Dynamic Programming,常称dp)又称为记录结果再利用的方法,求问题的最优解

贝尔曼等人提出最优化原理:

  • 一个最优化策略的子策略总是最优的;
  • 一个问题满足最优化原理称其具有最优子结构性质

一个问题如果满足最优子结构性质子问题重叠性质,那么就可以使用动态规划法解决。

  • 对于最优化原理的简单理解:假如有问题 a,a 由两个子问题 b,c 组成;对于问题 a 有解(即上边的最优化策略) x,x 可以分解成 y 和 z 两部分;如果y、z 分别是 b、c 的解(最优化策略),那么就成该问题具有最优子结构性质。
  • 子问题重叠性质即在不同父问题的解中包含相同子问题的解。

因为拥有最优子结构性质,所以可以由子问题的解得到原问题的解;因为子问题重叠性质,所以记录结果就有了意义,我们可以在第一次求解问题后记录下解的值,这样在之后用到该问题的解时,直接访问就行不需要再次求解了。

求解阶乘

接下来,我们将通过分析阶乘问题的求解,来进一步理解最优子结构和子问题重叠的性质。

当我们使用递归来求阶乘时:

int f(int n){// 假设n是正整数,这里不考虑0的阶乘
    if(n <= 2) return n;
    return f(n - 1) * n;
}

假设我们分别求3和4的阶乘,那么计算机的求解过程为:

f(3) = f(2) * 3 = 2 * 3 = 6;
f(4) = f(3) * 4 = f(2) * 3 * 4 = 2 * 3 * 4 = 24;

可以看到:

  • 在求 f(4) 时有 f(4) = f(3) * 4,求 f(3) 时有 f(3) = f(2) * 3,也就是说当前问题的解可以由其子问题的解求出,这就是最优子结构性质。
  • 求解 f(3) 需要求 f(2),求 f(4) 也需要求 f(2),这就是子问题重叠性质——在求 f(3) 和 f(4) 时都需要求解 f(2)。假如我们记录下已经求解过的 f(3) 和 f(2) 那么在求解 f(4) 时就可以使用f(3) 的值了,直接返回 f(4) = f(3) * 4 = 24 。
  • 在求解 f(3) 时,已经计算了 f(2) 的值并由此得到了 f(3) 的值。而当求解 f(4) 时,又分别计算了 f(3) 和 f(2) 的值,

动态规划

承接上文的阶乘求解,使用递归方法我们会重复求解许多子问题,这会浪费大量的时间和栈资源,可能引发超时和爆栈的问题。因此我们可以使用数组在每一次求解过某一个问题时,就记录下它的解,那么在之后需要使用到该问题时,就可以直接取值而不用再递归到底求解咯。

int dp[10010];
void f(int n){
    // 通过dp数组就将每个数的阶乘值保存下来了
    dp[1] = 1;
    dp[2] = 2;
    for (int i = 3; i <= n; i++){
        dp[i] = i * dp[i - 1]; 
    }
}

这样的话,我们可以直接通过访问 dp[n] 来获得 n 的阶乘值。如此,不仅避免了重复求解相同的子问题,也不会出现递归爆栈的问题。以上问题是一个一维的动态规划问题,不过我们通常见到的是二维动态规划问题,即使用二维数组 dp[][]来记录答案。先不用着急知道二维的题目是啥样的,按顺序看下去,最后会在本文末尾的例题讲解中狠狠感受到滴。

三要素

截止到现在,我想你大概已经了解了动态规划是个什么东西。接下来,我们将一起来看看当使用动态规划求解某一个问题时,需要哪几个步骤(即求解模板)。

  1. 明确数组的含义,我们需要知道这个数组每个状态代表了什么意思,如上文的阶乘问题中 dp[n] 代表 n 的阶乘。个人觉得,这是相对较难的一步,因为对于动态规划而言,步骤都是一样的,直白点说就是填数组得到答案,但是如何建模数组,用数组去表示题目中的状态是以下两部的基础,尤其是第二步的找递推式。
  2. 找出数组元素之间的递推关系式,即原问题的解与其子问题的解之间的关系。依旧是看阶乘问题,这一步对应于找到 dp[i] = i * dp[i - 1];这一关系式,这一步也不简单。
  3. 找出初始值或者说是处理边界值,我们要为 dp 数组的部分元素位置进行初始化、赋初始值,如 dp[1] = 1; dp[2] = 2;。这一步也是为了保证我们访问数组时不会访问到不合理的位置或者无效的随机值。

以上这就是使用动态规划方法解决问题时的步骤,正确解决了以上三步,那也就意味着你离AC只差提交代码了!!

例题

首先,请uu们注意,即使是看完这篇文章并且全部掌握这里的每一句话,也会了接下来的例题,当你去刷动态规划的题目时,依旧会觉得难以上手这是很正常的。算法无易事,虽然我不喜欢鸡汤,但很多事情真的本身就是不容易做成的。无论是我们学习算法,还是日常生活学习中做其他事情,如果我们想做出点样子,唯有百炼成钢!

“往往有这种情形,有利的情况和主动的恢复,产生于再坚持一下的努力之中”,伟大的教员如是说。不要去怀疑自己的努力和能力,慢慢来,会开花,也会结果的。

青蛙跳台阶

问题描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

这是一个不算难的经典题目,如果你是第一次接触动态规划,可以自己按照上面提到的三个步骤做一下。

思路分析
  1. 明确数组的含义:题目读罢,我们应该能反应过来,可以使用数组来表示 “跳上一个n级台阶的总跳法数” ,因为只有一个变量 n,很自然有 dp[n] —— 青蛙跳上第 n 级台阶的跳法数
  2. 找出数组元素之间的递推关系式:由题意可知,青蛙一次可以跳上1级台阶,也可以跳上2级台阶。那么我们考虑如果青蛙要跳到第 n 阶台阶,它只有两种可能:从第 n-1 阶跳 1 阶到第 n 阶,或者从第 n-2 阶跳 2 阶到第 n 阶,因此我们可以得到 dp[n] = dp[n-1] + dp[n-2]。
  3. 边界处理:由递推式我们可以知道,需要对 dp[1] 和 dp[2] 进行初始化,令 dp[1] = 1,dp[2] = 1。(这里假定 n 为正整数,不考虑0)。

uu们可以自行分析该问题的最优子结构性质和子问题重叠性质。

核心代码
int f(int n){
    dp[1] = 1;
    dp[2] = 2;
    for(int i = 3; i <= n; i++){
        dp[i] = dp[i -1] + dp[i - 2];
    }
    // dp[n] 即为最终结果
}

力扣传送门——70. 爬楼梯 - 力扣(LeetCode)

以下是我在力扣AC的代码,简单的解释一下:我们知道递推式为dp[i] = dp[i -1] + dp[i - 2],也就是说当我们在求某一层阶梯的方法数时,只用到了前边两层阶梯,既然如此,那么我们就可以只使用两个变量来记录这两个值,并在每一轮的过程中进行更新。

这其实是动态规划的优化(时间复杂度不变,对空间复杂度进行优化),之后会再单独拿例题来给大家分享一维、二维的优化。不过建议uu们可以先使用咱上边的一维动态规划方法来解决,尤其是对于刚接触动态规划的uu来说,先练习普通动态规划方法,优化不着急掌握的,就像是先学走路再学跑步,慢慢来,会更快!

class Solution {
public:
    int climbStairs(int n) {
        int a = 1, b = 2;
        int c = 0;
        if(n <= 2) return n;
        for(int i = 3; i <= n; i++){
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
};

0-1 背包问题

背包问题是动态规划里非常经典的题目,一如一位伟大的哲学家曾言,“如果你不知道 0-1 背包问题,那你就相当于没学过动态规划”,啧啧,箴言!!

0-1背包问题:给定 n 种物品和一背包。物品 i 的重量是 w ,其价值为 v ,背包的容量为 c 。问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大?

在选择装入背包的物品时,对每种物品只有两种选择,即装入背包或不装入背包。不能将物品 i 装入背包多次,也不能只装入部分的物品i。因此,该问题称为0-1背包问题。

思路分析
最优子结构

这里我们先一起分析0-1背包问题的最优子结构性质:
关于 0 − 1 背包问题的形式化描述是,给定背包容量 c > 0 , w i > 0 , v i > 0 ( 1 ≤ i ≤ n ) , 要求找出一个 n 元 0 − 1 向量 ( x 1 , x 2 , … , x n ) , 其中 x i ∈ { 0 , 1 } , 0 代表不选, 1 代表选 ( 1 ≤ i ≤ n ) ,使得 ∑ i = 1 n w i x i ≤ c 而且使得 ∑ i = 1 n x i v i 达到最大 关于0-1背包问题的形式化描述是,给定背包容量c > 0,w_i > 0,v_i > 0 ( 1 ≤ i ≤ n ),要求找出一个 n 元 0-1 向量(x_1,x_2,…,x_n),\\其中x_i∈\{0,1\} ,0代表不选,1代表选(1 ≤ i ≤ n),使得\sum_{i=1}^{n}{w_ix_i}\leq c 而且使得\sum_{i=1}^{n}{x_iv_i}达到最大 关于01背包问题的形式化描述是,给定背包容量c>0,wi>0,vi>0(1in),要求找出一个n01向量(x1,x2,,xn)其中xi{0,1}0代表不选,1代表选(1in),使得i=1nwixic而且使得i=1nxivi达到最大
由于公式太难敲了(比如求和公式呜呜),为了节约时间,我翻了我们学校几本讲到0-1背包问题的教材,找了一个讲的最清晰的截图放在了这里,请uu们笑纳

动态规划实现0-1背包问题c++,动态规划,动态规划,c++,算法,数据结构

寻找递推式

过程略显抽象,可以先看完一遍该三个步骤,再结合着下方的AcWing实际题目举例一起看。

  1. 在以上的分析中,我们知道对于每一个物品都只有两种情况,放进背包(在最优解中)或者不放进背包(不在最优解中)。很自然的,我们应该遍历一遍所有物品,分别考虑它们的情况。

  2. 我们可以考虑到一件物品是不是可以放进背包的先决条件是背包当前的容量是不是大于该物品的体积:如果当前背包的可用容量小于物品的体积很显然不能放进背包;而如果当前背包可用容量大于物品体积,那么我们要考虑为了最终能获得最大价值——是把当前物品放进背包获得的价值更大还是不将该物品放进背包获得的价值大。比如,有两个物品a和b,第一个物品a的体积为1,价值为2;第二个物品b的体积为2,价值为1。假设此时我们遍历到物品b,背包容量为2,很显然我们只放a不放b获得的价值更大。

    由此,我们可以考虑构造一个二维数组dp[][],第一维考虑物品,第二维从0到c考虑背包容量,对于dp[i][j]代表考虑到第 i 个物品,背包可用容量为 j 时可以获得的最大价值。如果你是首次接触0-1背包问题,那么觉得以上这段话抽象是再正常不过的,不要着急,结合着下边的例题多看两遍,你肯定会掌握的!

  3. 由2得到递推式(考虑到公式难敲,我就手写了哈兄弟们,望体谅):

动态规划实现0-1背包问题c++,动态规划,动态规划,c++,算法,数据结构

举例

acwing背包问题传送门 —— 2. 01背包问题 - AcWing题库,兄弟们学完后可以自己敲一下在该链接中提交。

我们在这就使用AcWing题目中的数据,有4个物品,背包容量为5,4个物品的体积、价值数据如表格所示:

物品标号(i) 1 2 3 4
体积(w[i]) 1 2 3 4
价值(v[i]) 2 4 4 5

二维表格dp的填表过程:

  1. 对表格的第一列第一行初始化为0
    动态规划实现0-1背包问题c++,动态规划,动态规划,c++,算法,数据结构

  2. 考虑第一个物品,背包容量为0时,能获得的最大价值为0,背包容量为1~5时,物品1可以放进背包,获得的最大价值为2
    动态规划实现0-1背包问题c++,动态规划,动态规划,c++,算法,数据结构

  3. 遍历到第二个物品,背包容量为0时,两个物品都无法放进背包,价值为0;背包容量为1时,物品1可以放进背包,价值为2;背包容量为2时,此时物品2有放进背包的可能,此时考虑要不要放进,由递推公式可知,选择只放进物品2,获得的价值较大为4;当背包容量为3~5时,物品1和物品2都放进背包时有最大价值6.

动态规划实现0-1背包问题c++,动态规划,动态规划,c++,算法,数据结构

表格剩下的两行就留给兄弟们自己根据递推公式填上了,填完后可以讲下方打印表格的代码片段取消注释,把表格打印出来后和自己填的答案对一对。

代码实现

以下代码已在AcWing提交并通过,请uu们放心食用文章来源地址https://www.toymoban.com/news/detail-847607.html

#include<bits/stdc++.h>
using namespace std;
const int N = 1007;
int dp[N][N] = {0};
// w[]存每个物品的体积,v[]存储每个物品的价值
int w[N] = {0}, v[N] = {0};
int num, vol; // num是物品个数,vol是背包的体积
int main() {
	scanf("%d%d", &num, &vol);
	for (int i = 1; i <= num; i++) {
		scanf("%d%d", &w[i], &v[i]);
	}
	for (int i = 1; i <= num; i++) {
		for (int j = 1; j <= vol; j++) {
			dp[i][j] = dp[i - 1][j];
			if (j >= w[i]) {
				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
			}
		}
	}
//	// 打印出来整个表格 
//	for(int i=0;i<=num;i++){
//		for(int j=0;j<=vol;j++){
//			printf("%d ", dp[i][j]);
//		}
//		printf("\n");
//	}
	printf("%d", dp[num][vol]);
	return 0;
}
/*4 5
1 2
2 4
3 4
4 5
output: 8*/

到了这里,关于动态规划详细讲解c++|经典例题讲解认识动态规划|0-1背包问题详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 动态规划:0-1背包、完全背包问题 | 详细原理解释 | 代码及优化(C++)

    目录 01背包 问题描述: 简单描述就是: 解析: 递推公式: dp数组的初始化: 遍历顺序: 图解: 实现代码: dp数组初始化: 遍历: 优化: 原理: 递推公式: 遍历顺序: 实现代码: 初始化: 遍历: 完全背包 问题描述: 解析: 实现代码:         01背包是在M件物品

    2024年02月11日
    浏览(28)
  • 微机原理 || 8253 芯片 (详细讲解 + 经典例题)

    一点点看!一定可以看懂!考试没有问题的!加油💪 前面知识写的详细,看不懂可以先看 典例 ,回头来梳理就明白了【典例就是常考的题】 目录 Part 1: 芯片知识总结  (一)8253 芯片特点 (二) 8253芯片引脚功能      知道才好编程 (三) 8253编程 (1)8253 初始化 ① 工

    2024年02月01日
    浏览(42)
  • 算法 动态规划 及Java例题讲解

    动态规划 (英语:Dynamic programming,简称 DP ),是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有重叠子问题和最优子结构性质的问题 。 简单来说,动态规划其

    2024年01月22日
    浏览(33)
  • c语言经典例题讲解(输出菱形,喝汽水问题)

    目录 一、输出菱形 二、喝汽水问题 方法1:一步一步来   方法二:直接套公式   输出类似于下图的菱形:    通过分析:1、先分为上下两部分输出                    2.在输出前先输出空格                   3.找规律进行输出 可知,可令上半部分line行,下半部分便是

    2024年02月13日
    浏览(25)
  • 动态规划-背包问题-完全背包

    对比01背包,完全背包中的每件物品有无数件。 也就是说,每件物品可以拿0,1,…,k,…件。 dp[i][j]表示前i种物品,体积为j时的最大价值 对于第i件物品: 不拿:dp[i][j]⇐dp[i-1][j] 拿一件:dp[i][j]⇐dp[i-1][j-w[i]]+v[i] 拿两件:dp[i][j]⇐dp[i-1][j-2w[i]]+2v[i] … 拿k件:dp[i]][j]⇐dp[i

    2024年04月08日
    浏览(38)
  • 动态规划之背包问题——完全背包

    算法相关数据结构总结: 序号 数据结构 文章 1 动态规划 动态规划之背包问题——01背包 动态规划之背包问题——完全背包 动态规划之打家劫舍系列问题 动态规划之股票买卖系列问题 动态规划之子序列问题 算法(Java)——动态规划 2 数组 算法分析之数组问题 3 链表 算法

    2024年02月03日
    浏览(30)
  • 完全背包&多重背包问题(动态规划)

    完全背包问题: 每个物品使用次数没有限制,与0-1背包的不同之处在于 遍历背包的顺序 是正序。 多重背包问题: 与完全背包的区别在于,每一种物品是有个数限制的,不能无限选择。这篇博客讲解的非常详细,可以参考学习: 多重背包问题---超详细讲解+优化(不懂你揍我

    2024年04月10日
    浏览(36)
  • 动态规划DP之背包问题3---多重背包问题

    目录 DP分析: 优化:  二进制优化 例题:         01背包是每个物品只有一个,完全背包问题是每个物品有无限个。         那么多重背包问题就是 每个物品有有限个 。 有 N 种物品和一个容量是 V 的背包。 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。 求解

    2024年03月20日
    浏览(35)
  • 动态规划-----背包类问题(0-1背包与完全背包)详解

    目录 什么是背包问题? 动态规划问题的一般解决办法: 0-1背包问题: 0 - 1背包类问题  分割等和子集:  完全背包问题:  完全背包类问题 零钱兑换II: 背包问题(Knapsack problem)是一种组合优化的NP完全问题。 问题可以描述为:给定一组物品,每种物品都有自己的重量和价格

    2024年04月17日
    浏览(29)
  • 算法系列--动态规划--背包问题(1)--01背包介绍

    💕\\\"趁着年轻,做一些比较cool的事情\\\"💕 作者:Lvzi 文章主要内容:算法系列–动态规划–背包问题(1)–01背包介绍 大家好,今天为大家带来的是 算法系列--动态规划--背包问题(1)--01背包介绍 背包问题是动态规划中经典的一类问题,经常在笔试面试中出现,是非常 具有区分度 的题

    2024年04月16日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包